Added support of suggestion to validate cloud password to settings.

This commit is contained in:
23rd 2025-05-28 13:33:09 +03:00
parent 727acca217
commit 1ae3122c20
12 changed files with 324 additions and 30 deletions

View file

@ -1446,6 +1446,8 @@ PRIVATE
settings/cloud_password/settings_cloud_password_start.h settings/cloud_password/settings_cloud_password_start.h
settings/cloud_password/settings_cloud_password_step.cpp settings/cloud_password/settings_cloud_password_step.cpp
settings/cloud_password/settings_cloud_password_step.h settings/cloud_password/settings_cloud_password_step.h
settings/cloud_password/settings_cloud_password_validate_icon.cpp
settings/cloud_password/settings_cloud_password_validate_icon.h
settings/settings_active_sessions.cpp settings/settings_active_sessions.cpp
settings/settings_active_sessions.h settings/settings_active_sessions.h
settings/settings_advanced.cpp settings/settings_advanced.cpp

View file

@ -840,6 +840,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_suggestion_phone_number_about" = "Keep your number up to date to ensure you can always log into Telegram. {link}"; "lng_settings_suggestion_phone_number_about" = "Keep your number up to date to ensure you can always log into Telegram. {link}";
"lng_settings_suggestion_phone_number_about_link" = "https://telegram.org/faq#q-i-have-a-new-phone-number-what-do-i-do"; "lng_settings_suggestion_phone_number_about_link" = "https://telegram.org/faq#q-i-have-a-new-phone-number-what-do-i-do";
"lng_settings_suggestion_phone_number_change" = "Please change your phone number in the official Telegram app on your phone as soon as possible. {emoji}"; "lng_settings_suggestion_phone_number_change" = "Please change your phone number in the official Telegram app on your phone as soon as possible. {emoji}";
"lng_settings_suggestion_password_title" = "Your password";
"lng_settings_suggestion_password_about" = "Your account is protected by 2-Step Veritifaction. Do you still remember your password?";
"lng_settings_suggestion_password_yes" = "Yes, definitely";
"lng_settings_suggestion_password_no" = "Not sure";
"lng_settings_suggestion_password_step_input_title" = "Enter your password";
"lng_settings_suggestion_password_step_input_about" = "Do you still remember your password?";
"lng_settings_suggestion_password_step_finish_title" = "Perfect!";
"lng_settings_suggestion_password_step_finish_about" = "You still remember your password.";
"lng_settings_power_menu" = "Battery and Animations"; "lng_settings_power_menu" = "Battery and Animations";
"lng_settings_power_title" = "Power Usage"; "lng_settings_power_title" = "Power Usage";

View file

@ -9,6 +9,7 @@
<file alias="cloud_password/password_input.tgs">../../animations/cloud_password/password_input.tgs</file> <file alias="cloud_password/password_input.tgs">../../animations/cloud_password/password_input.tgs</file>
<file alias="cloud_password/hint.tgs">../../animations/cloud_password/hint.tgs</file> <file alias="cloud_password/hint.tgs">../../animations/cloud_password/hint.tgs</file>
<file alias="cloud_password/email.tgs">../../animations/cloud_password/email.tgs</file> <file alias="cloud_password/email.tgs">../../animations/cloud_password/email.tgs</file>
<file alias="cloud_password/validate.tgs">../../animations/cloud_password/validate.tgs</file>
<file alias="ttl.tgs">../../animations/ttl.tgs</file> <file alias="ttl.tgs">../../animations/ttl.tgs</file>
<file alias="discussion.tgs">../../animations/discussion.tgs</file> <file alias="discussion.tgs">../../animations/discussion.tgs</file>
<file alias="stats.tgs">../../animations/stats.tgs</file> <file alias="stats.tgs">../../animations/stats.tgs</file>

View file

@ -306,4 +306,9 @@ std::optional<UserIds> PromoSuggestions::knownBirthdaysToday() const {
return _contactBirthdaysToday; return _contactBirthdaysToday;
} }
QString PromoSuggestions::SugValidatePassword() {
static const auto key = u"VALIDATE_PASSWORD"_q;
return key;
}
} // namespace Data } // namespace Data

View file

@ -51,6 +51,8 @@ public:
[[nodiscard]] auto knownBirthdaysToday() const [[nodiscard]] auto knownBirthdaysToday() const
-> std::optional<std::vector<UserId>>; -> std::optional<std::vector<UserId>>;
[[nodiscard]] static QString SugValidatePassword();
private: private:
void setTopPromoted( void setTopPromoted(
History *promoted, History *promoted,

View file

@ -34,6 +34,7 @@ struct StepData {
QString email; QString email;
int unconfirmedEmailLengthCode; int unconfirmedEmailLengthCode;
bool setOnlyRecoveryEmail = false; bool setOnlyRecoveryEmail = false;
bool suggestionValidate = false;
struct ProcessRecover { struct ProcessRecover {
bool setNewPassword = false; bool setNewPassword = false;

View file

@ -12,20 +12,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/timer.h" #include "base/timer.h"
#include "base/unixtime.h" #include "base/unixtime.h"
#include "core/core_cloud_password.h" #include "core/core_cloud_password.h"
#include "data/components/promo_suggestions.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "lottie/lottie_icon.h" #include "lottie/lottie_icon.h"
#include "main/main_session.h"
#include "settings/cloud_password/settings_cloud_password_common.h" #include "settings/cloud_password/settings_cloud_password_common.h"
#include "settings/cloud_password/settings_cloud_password_email_confirm.h" #include "settings/cloud_password/settings_cloud_password_email_confirm.h"
#include "settings/cloud_password/settings_cloud_password_hint.h" #include "settings/cloud_password/settings_cloud_password_hint.h"
#include "settings/cloud_password/settings_cloud_password_manage.h" #include "settings/cloud_password/settings_cloud_password_manage.h"
#include "settings/cloud_password/settings_cloud_password_step.h" #include "settings/cloud_password/settings_cloud_password_step.h"
#include "settings/cloud_password/settings_cloud_password_validate_icon.h"
#include "ui/boxes/boost_box.h" // Ui::StartFireworks.
#include "ui/boxes/confirm_box.h" #include "ui/boxes/confirm_box.h"
#include "ui/rect.h"
#include "ui/text/format_values.h" #include "ui/text/format_values.h"
#include "ui/ui_utility.h"
#include "ui/vertical_list.h"
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/widgets/fields/password_input.h" #include "ui/widgets/fields/password_input.h"
#include "ui/widgets/labels.h" #include "ui/widgets/labels.h"
#include "ui/wrap/vertical_layout.h" #include "ui/wrap/vertical_layout.h"
#include "ui/vertical_list.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "styles/style_boxes.h" #include "styles/style_boxes.h"
#include "styles/style_layers.h" #include "styles/style_layers.h"
@ -53,6 +59,10 @@ RecreateResetPassword:
Continue to RecreateResetHint. Continue to RecreateResetHint.
Clear password and Back to Settings. Clear password and Back to Settings.
Back to Settings. Back to Settings.
ValidatePassword:
- Submit to show good validate.
- Back to Main Settings.
*/ */
namespace Settings { namespace Settings {
@ -72,9 +82,7 @@ Icon CreateInteractiveLottieIcon(
const auto raw = object.data(); const auto raw = object.data();
const auto width = descriptor.sizeOverride.width(); const auto width = descriptor.sizeOverride.width();
raw->resize(QRect( raw->resize((Rect(descriptor.sizeOverride) + padding).size());
QPoint(),
descriptor.sizeOverride).marginsAdded(padding).size());
auto owned = Lottie::MakeIcon(std::move(descriptor)); auto owned = Lottie::MakeIcon(std::move(descriptor));
const auto icon = owned.get(); const auto icon = owned.get();
@ -118,7 +126,10 @@ public:
using TypedAbstractStep::TypedAbstractStep; using TypedAbstractStep::TypedAbstractStep;
[[nodiscard]] rpl::producer<QString> title() override; [[nodiscard]] rpl::producer<QString> title() override;
[[nodiscard]] QPointer<Ui::RpWidget> createPinnedToTop(
not_null<QWidget*> parent) override;
void setupContent(); void setupContent();
void setupValidateGood();
protected: protected:
[[nodiscard]] rpl::producer<std::vector<Type>> removeTypes() override; [[nodiscard]] rpl::producer<std::vector<Type>> removeTypes() override;
@ -130,6 +141,8 @@ private:
not_null<Ui::FlatLabel*> info, not_null<Ui::FlatLabel*> info,
Fn<void()> recoverCallback); Fn<void()> recoverCallback);
QWidget *_parent = nullptr;
rpl::variable<std::vector<Type>> _removesFromStack; rpl::variable<std::vector<Type>> _removesFromStack;
rpl::lifetime _requestLifetime; rpl::lifetime _requestLifetime;
@ -143,12 +156,58 @@ rpl::producer<QString> Input::title() {
return tr::lng_settings_cloud_password_password_title(); return tr::lng_settings_cloud_password_password_title();
} }
QPointer<Ui::RpWidget> Input::createPinnedToTop(
not_null<QWidget*> parent) {
_parent = parent;
return nullptr;
}
void Input::setupValidateGood() {
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
if (_parent) {
Ui::StartFireworks(_parent);
}
if (auto owned = CreateValidateGoodIcon(&controller()->session())) {
owned->setParent(content);
content->add(
object_ptr<Ui::CenterWrap<>>(
content,
std::move(owned)),
QMargins(0, st::lineWidth * 75, 0, 0));
}
SetupHeader(
content,
QString(),
rpl::never<>(),
tr::lng_settings_suggestion_password_step_finish_title(),
tr::lng_settings_suggestion_password_step_finish_about());
const auto button = AddDoneButton(content, tr::lng_share_done());
button->setClickedCallback([=] {
showBack();
});
Ui::ToggleChildrenVisibility(this, true);
Ui::ResizeFitChild(this, content);
content->resizeToWidth(width());
Ui::SendPendingMoveResizeEvents(content);
}
void Input::setupContent() { void Input::setupContent() {
if (QWidget::children().count() > 0) {
return;
}
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this); const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
auto currentStepData = stepData(); auto currentStepData = stepData();
const auto currentStepDataPassword = base::take(currentStepData.password); const auto currentStepDataPassword = base::take(currentStepData.password);
const auto currentStepProcessRecover = base::take( const auto currentStepProcessRecover = base::take(
currentStepData.processRecover); currentStepData.processRecover);
const auto currentStepValidate = base::take(
currentStepData.suggestionValidate);
setStepData(currentStepData); setStepData(currentStepData);
const auto currentState = cloudPassword().stateCurrent(); const auto currentState = cloudPassword().stateCurrent();
@ -167,11 +226,10 @@ void Input::setupContent() {
const auto icon = CreateInteractiveLottieIcon( const auto icon = CreateInteractiveLottieIcon(
content, content,
{ {
.name = u"cloud_password/password_input"_q, .name = currentStepValidate
.sizeOverride = { ? u"cloud_password/validate"_q
st::settingsCloudPasswordIconSize, : u"cloud_password/password_input"_q,
st::settingsCloudPasswordIconSize .sizeOverride = Size(st::settingsCloudPasswordIconSize),
},
}, },
st::settingLocalPasscodeIconPadding); st::settingLocalPasscodeIconPadding);
@ -179,12 +237,16 @@ void Input::setupContent() {
content, content,
QString(), QString(),
rpl::never<>(), rpl::never<>(),
isCheck currentStepValidate
? tr::lng_settings_suggestion_password_step_input_title()
: isCheck
? tr::lng_settings_cloud_password_check_subtitle() ? tr::lng_settings_cloud_password_check_subtitle()
: hasPassword : hasPassword
? tr::lng_settings_cloud_password_manage_password_change() ? tr::lng_settings_cloud_password_manage_password_change()
: tr::lng_settings_cloud_password_password_subtitle(), : tr::lng_settings_cloud_password_password_subtitle(),
isCheck currentStepValidate
? tr::lng_settings_suggestion_password_step_input_about()
: isCheck
? tr::lng_settings_cloud_password_manage_about1() ? tr::lng_settings_cloud_password_manage_about1()
: tr::lng_cloud_password_about()); : tr::lng_cloud_password_about());
@ -340,7 +402,9 @@ void Input::setupContent() {
Ui::AddSkip(content); Ui::AddSkip(content);
} }
if (!newInput->text().isEmpty()) { if (currentStepValidate) {
icon.icon->animate(icon.update, 0, icon.icon->framesCount() - 1);
} else if (!newInput->text().isEmpty()) {
icon.icon->jumpTo(icon.icon->framesCount() / 2, icon.update); icon.icon->jumpTo(icon.icon->framesCount() / 2, icon.update);
} }
@ -376,10 +440,18 @@ void Input::setupContent() {
} }
} }
auto data = stepData(); if (currentStepValidate) {
data.currentPassword = pass; controller()->session().promoSuggestions().dismiss(
setStepData(std::move(data)); Data::PromoSuggestions::SugValidatePassword());
showOther(CloudPasswordManageId()); setupValidateGood();
delete content;
} else {
auto data = stepData();
data.currentPassword = pass;
setStepData(std::move(data));
showOther(CloudPasswordManageId());
}
}); });
}; };
@ -412,17 +484,19 @@ void Input::setupContent() {
} }
}); });
base::qt_signal_producer( if (!currentStepValidate) {
newInput.get(), base::qt_signal_producer(
&QLineEdit::textChanged // Covers Undo. newInput.get(),
) | rpl::map([=] { &QLineEdit::textChanged // Covers Undo.
return newInput->text().isEmpty(); ) | rpl::map([=] {
}) | rpl::distinct_until_changed( return newInput->text().isEmpty();
) | rpl::start_with_next([=](bool empty) { }) | rpl::distinct_until_changed(
const auto from = icon.icon->frameIndex(); ) | rpl::start_with_next([=](bool empty) {
const auto to = empty ? 0 : (icon.icon->framesCount() / 2 - 1); const auto from = icon.icon->frameIndex();
icon.icon->animate(icon.update, from, to); const auto to = empty ? 0 : (icon.icon->framesCount() / 2 - 1);
}, content->lifetime()); icon.icon->animate(icon.update, from, to);
}, content->lifetime());
}
const auto submit = [=] { const auto submit = [=] {
if (!reenterInput || reenterInput->hasFocus()) { if (!reenterInput || reenterInput->hasFocus()) {
@ -437,7 +511,7 @@ void Input::setupContent() {
QObject::connect(reenterInput, &MaskedInputField::submitted, submit); QObject::connect(reenterInput, &MaskedInputField::submitted, submit);
} }
setFocusCallback([=] { setFocusCallback(crl::guard(content, [=] {
if (isCheck || newInput->text().isEmpty()) { if (isCheck || newInput->text().isEmpty()) {
newInput->setFocus(); newInput->setFocus();
} else if (reenterInput->text().isEmpty()) { } else if (reenterInput->text().isEmpty()) {
@ -445,7 +519,7 @@ void Input::setupContent() {
} else { } else {
newInput->setFocus(); newInput->setFocus();
} }
}); }));
Ui::ResizeFitChild(this, content); Ui::ResizeFitChild(this, content);
} }
@ -586,10 +660,33 @@ void Input::setupRecoverButton(
}); });
} }
class SuggestionInput : public Input {
public:
SuggestionInput(
QWidget *parent,
not_null<Window::SessionController*> controller)
: Input(parent, controller)
, _stepData(StepData{ .suggestionValidate = true }) {
setStepDataReference(_stepData);
}
[[nodiscard]] static Type Id() {
return SectionFactory<SuggestionInput>::Instance();
}
private:
std::any _stepData;
};
} // namespace CloudPassword } // namespace CloudPassword
Type CloudPasswordInputId() { Type CloudPasswordInputId() {
return CloudPassword::Input::Id(); return CloudPassword::Input::Id();
} }
Type CloudPasswordSuggestionInputId() {
return CloudPassword::SuggestionInput::Id();
}
} // namespace Settings } // namespace Settings

View file

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Settings { namespace Settings {
Type CloudPasswordInputId(); Type CloudPasswordInputId();
Type CloudPasswordSuggestionInputId();
} // namespace Settings } // namespace Settings

View file

@ -0,0 +1,74 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "settings/cloud_password/settings_cloud_password_validate_icon.h"
#include "apiwrap.h"
#include "base/object_ptr.h"
#include "chat_helpers/stickers_emoji_pack.h"
#include "data/data_session.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/stickers/data_stickers.h"
#include "main/main_session.h"
#include "ui/rect.h"
#include "ui/rp_widget.h"
#include "styles/style_settings.h"
namespace Settings {
namespace {
[[nodiscard]] DocumentData *EmojiValidateGood(
not_null<Main::Session*> session) {
auto emoji = TextWithEntities{
.text = (QString(QChar(0xD83D)) + QChar(0xDC4D)),
};
if (const auto e = Ui::Emoji::Find(emoji.text)) {
const auto sticker = session->emojiStickersPack().stickerForEmoji(e);
return sticker.document;
}
return nullptr;
}
} // namespace
object_ptr<Ui::RpWidget> CreateValidateGoodIcon(
not_null<Main::Session*> session) {
const auto document = EmojiValidateGood(session);
if (!document) {
return nullptr;
}
auto owned = object_ptr<Ui::RpWidget>((QWidget*)nullptr);
const auto widget = owned.data();
struct State {
std::unique_ptr<Ui::Text::CustomEmoji> emoji;
};
const auto state = widget->lifetime().make_state<State>();
const auto size = st::settingsCloudPasswordIconSize;
state->emoji = std::make_unique<Ui::Text::LimitedLoopsEmoji>(
session->data().customEmojiManager().create(
document,
[=] { widget->update(); },
Data::CustomEmojiManager::SizeTag::Normal,
size),
1,
true);
widget->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(widget);
state->emoji->paint(p, Ui::Text::CustomEmojiPaintContext{
.textColor = st::windowFg->c,
.now = crl::now(),
});
}, widget->lifetime());
const auto padding = st::settingLocalPasscodeIconPadding;
widget->resize((Rect(Size(size)) + padding).size());
return owned;
}
} // namespace Settings

View file

@ -0,0 +1,27 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
template <typename Object>
class object_ptr;
namespace Main {
class Session;
} // namespace Main
namespace Ui {
class RpWidget;
} // namespace Ui
namespace Settings {
[[nodiscard]] object_ptr<Ui::RpWidget> CreateValidateGoodIcon(
not_null<Main::Session*> session);
} // namespace Settings

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "settings/settings_main.h" #include "settings/settings_main.h"
#include "settings/cloud_password/settings_cloud_password_input.h"
#include "api/api_credits.h" #include "api/api_credits.h"
#include "core/application.h" #include "core/application.h"
#include "core/click_handler_types.h" #include "core/click_handler_types.h"
@ -467,7 +468,7 @@ void SetupValidatePhoneNumberSuggestion(
yes->setClickedCallback([=] { yes->setClickedCallback([=] {
controller->session().promoSuggestions().dismiss( controller->session().promoSuggestions().dismiss(
kSugValidatePhone.utf8()); kSugValidatePhone.utf8());
mainWrap->toggle(false, anim::type::normal); mainWrap->toggle(false, anim::type::normal);
}); });
const auto no = Ui::CreateChild<Ui::RoundButton>( const auto no = Ui::CreateChild<Ui::RoundButton>(
wrap, wrap,
@ -522,6 +523,77 @@ void SetupValidatePhoneNumberSuggestion(
Ui::AddSkip(content); Ui::AddSkip(content);
} }
void SetupValidatePasswordSuggestion(
not_null<Window::SessionController*> controller,
not_null<Ui::VerticalLayout*> container,
Fn<void(Type)> showOther) {
if (!controller->session().promoSuggestions().current(
Data::PromoSuggestions::SugValidatePassword())
|| controller->session().promoSuggestions().current(
kSugValidatePhone.utf8())) {
return;
}
const auto mainWrap = container->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
container,
object_ptr<Ui::VerticalLayout>(container)));
const auto content = mainWrap->entity();
Ui::AddSubsectionTitle(
content,
tr::lng_settings_suggestion_password_title(),
QMargins(
st::boxRowPadding.left()
- st::defaultSubsectionTitlePadding.left(),
0,
0,
0));
const auto label = content->add(
object_ptr<Ui::FlatLabel>(
content,
tr::lng_settings_suggestion_password_about(),
st::boxLabel),
st::boxRowPadding);
Ui::AddSkip(content);
Ui::AddSkip(content);
const auto wrap = content->add(
object_ptr<Ui::FixedHeightWidget>(
content,
st::inviteLinkButton.height),
st::inviteLinkButtonsPadding);
const auto yes = Ui::CreateChild<Ui::RoundButton>(
wrap,
tr::lng_settings_suggestion_password_yes(),
st::inviteLinkButton);
yes->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
yes->setClickedCallback([=] {
controller->session().promoSuggestions().dismiss(
Data::PromoSuggestions::SugValidatePassword());
mainWrap->toggle(false, anim::type::normal);
});
const auto no = Ui::CreateChild<Ui::RoundButton>(
wrap,
tr::lng_settings_suggestion_password_no(),
st::inviteLinkButton);
no->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
no->setClickedCallback([=] {
showOther(Settings::CloudPasswordSuggestionInputId());
});
wrap->widthValue() | rpl::start_with_next([=](int width) {
const auto buttonWidth = (width - st::inviteLinkButtonsSkip) / 2;
yes->setFullWidth(buttonWidth);
no->setFullWidth(buttonWidth);
yes->moveToLeft(0, 0, width);
no->moveToRight(0, 0, width);
}, wrap->lifetime());
Ui::AddSkip(content);
Ui::AddSkip(content);
Ui::AddDivider(content);
Ui::AddSkip(content);
}
void SetupSections( void SetupSections(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<Ui::VerticalLayout*> container, not_null<Ui::VerticalLayout*> container,
@ -533,6 +605,10 @@ void SetupSections(
controller, controller,
container, container,
showOther); showOther);
SetupValidatePasswordSuggestion(
controller,
container,
showOther);
const auto addSection = [&]( const auto addSection = [&](
rpl::producer<QString> label, rpl::producer<QString> label,