diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt
index 50dcfec3e6..92ce8f31a3 100644
--- a/Telegram/CMakeLists.txt
+++ b/Telegram/CMakeLists.txt
@@ -1446,6 +1446,8 @@ PRIVATE
settings/cloud_password/settings_cloud_password_start.h
settings/cloud_password/settings_cloud_password_step.cpp
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.h
settings/settings_advanced.cpp
diff --git a/Telegram/Resources/animations/cloud_password/validate.tgs b/Telegram/Resources/animations/cloud_password/validate.tgs
new file mode 100644
index 0000000000..fd5161d885
Binary files /dev/null and b/Telegram/Resources/animations/cloud_password/validate.tgs differ
diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index 6c8d3e117a..e0e7281441 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -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_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_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_title" = "Power Usage";
diff --git a/Telegram/Resources/qrc/telegram/animations.qrc b/Telegram/Resources/qrc/telegram/animations.qrc
index 50cf186e02..ae94041686 100644
--- a/Telegram/Resources/qrc/telegram/animations.qrc
+++ b/Telegram/Resources/qrc/telegram/animations.qrc
@@ -9,6 +9,7 @@
../../animations/cloud_password/password_input.tgs
../../animations/cloud_password/hint.tgs
../../animations/cloud_password/email.tgs
+ ../../animations/cloud_password/validate.tgs
../../animations/ttl.tgs
../../animations/discussion.tgs
../../animations/stats.tgs
diff --git a/Telegram/SourceFiles/data/components/promo_suggestions.cpp b/Telegram/SourceFiles/data/components/promo_suggestions.cpp
index 99ec5f5b2f..33c0b860a5 100644
--- a/Telegram/SourceFiles/data/components/promo_suggestions.cpp
+++ b/Telegram/SourceFiles/data/components/promo_suggestions.cpp
@@ -306,4 +306,9 @@ std::optional PromoSuggestions::knownBirthdaysToday() const {
return _contactBirthdaysToday;
}
+QString PromoSuggestions::SugValidatePassword() {
+ static const auto key = u"VALIDATE_PASSWORD"_q;
+ return key;
+}
+
} // namespace Data
diff --git a/Telegram/SourceFiles/data/components/promo_suggestions.h b/Telegram/SourceFiles/data/components/promo_suggestions.h
index 33f943a958..e57c1162cf 100644
--- a/Telegram/SourceFiles/data/components/promo_suggestions.h
+++ b/Telegram/SourceFiles/data/components/promo_suggestions.h
@@ -51,6 +51,8 @@ public:
[[nodiscard]] auto knownBirthdaysToday() const
-> std::optional>;
+ [[nodiscard]] static QString SugValidatePassword();
+
private:
void setTopPromoted(
History *promoted,
diff --git a/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_common.h b/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_common.h
index 586e58a9dd..a87c1aaed5 100644
--- a/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_common.h
+++ b/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_common.h
@@ -34,6 +34,7 @@ struct StepData {
QString email;
int unconfirmedEmailLengthCode;
bool setOnlyRecoveryEmail = false;
+ bool suggestionValidate = false;
struct ProcessRecover {
bool setNewPassword = false;
diff --git a/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_input.cpp b/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_input.cpp
index 0064dcbda7..7719fc2ddd 100644
--- a/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_input.cpp
+++ b/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_input.cpp
@@ -12,20 +12,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/timer.h"
#include "base/unixtime.h"
#include "core/core_cloud_password.h"
+#include "data/components/promo_suggestions.h"
#include "lang/lang_keys.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_email_confirm.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_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/rect.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/fields/password_input.h"
#include "ui/widgets/labels.h"
#include "ui/wrap/vertical_layout.h"
-#include "ui/vertical_list.h"
#include "window/window_session_controller.h"
#include "styles/style_boxes.h"
#include "styles/style_layers.h"
@@ -53,6 +59,10 @@ RecreateResetPassword:
– Continue to RecreateResetHint.
– Clear password and Back to Settings.
– Back to Settings.
+
+ValidatePassword:
+- Submit to show good validate.
+- Back to Main Settings.
*/
namespace Settings {
@@ -72,9 +82,7 @@ Icon CreateInteractiveLottieIcon(
const auto raw = object.data();
const auto width = descriptor.sizeOverride.width();
- raw->resize(QRect(
- QPoint(),
- descriptor.sizeOverride).marginsAdded(padding).size());
+ raw->resize((Rect(descriptor.sizeOverride) + padding).size());
auto owned = Lottie::MakeIcon(std::move(descriptor));
const auto icon = owned.get();
@@ -118,7 +126,10 @@ public:
using TypedAbstractStep::TypedAbstractStep;
[[nodiscard]] rpl::producer title() override;
+ [[nodiscard]] QPointer createPinnedToTop(
+ not_null parent) override;
void setupContent();
+ void setupValidateGood();
protected:
[[nodiscard]] rpl::producer> removeTypes() override;
@@ -130,6 +141,8 @@ private:
not_null info,
Fn recoverCallback);
+ QWidget *_parent = nullptr;
+
rpl::variable> _removesFromStack;
rpl::lifetime _requestLifetime;
@@ -143,12 +156,58 @@ rpl::producer Input::title() {
return tr::lng_settings_cloud_password_password_title();
}
+QPointer Input::createPinnedToTop(
+ not_null parent) {
+ _parent = parent;
+ return nullptr;
+}
+
+void Input::setupValidateGood() {
+ const auto content = Ui::CreateChild(this);
+
+ if (_parent) {
+ Ui::StartFireworks(_parent);
+ }
+
+ if (auto owned = CreateValidateGoodIcon(&controller()->session())) {
+ owned->setParent(content);
+ content->add(
+ object_ptr>(
+ 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() {
+ if (QWidget::children().count() > 0) {
+ return;
+ }
+
const auto content = Ui::CreateChild(this);
auto currentStepData = stepData();
const auto currentStepDataPassword = base::take(currentStepData.password);
const auto currentStepProcessRecover = base::take(
currentStepData.processRecover);
+ const auto currentStepValidate = base::take(
+ currentStepData.suggestionValidate);
setStepData(currentStepData);
const auto currentState = cloudPassword().stateCurrent();
@@ -167,11 +226,10 @@ void Input::setupContent() {
const auto icon = CreateInteractiveLottieIcon(
content,
{
- .name = u"cloud_password/password_input"_q,
- .sizeOverride = {
- st::settingsCloudPasswordIconSize,
- st::settingsCloudPasswordIconSize
- },
+ .name = currentStepValidate
+ ? u"cloud_password/validate"_q
+ : u"cloud_password/password_input"_q,
+ .sizeOverride = Size(st::settingsCloudPasswordIconSize),
},
st::settingLocalPasscodeIconPadding);
@@ -179,12 +237,16 @@ void Input::setupContent() {
content,
QString(),
rpl::never<>(),
- isCheck
+ currentStepValidate
+ ? tr::lng_settings_suggestion_password_step_input_title()
+ : isCheck
? tr::lng_settings_cloud_password_check_subtitle()
: hasPassword
? tr::lng_settings_cloud_password_manage_password_change()
: 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_cloud_password_about());
@@ -340,7 +402,9 @@ void Input::setupContent() {
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);
}
@@ -376,10 +440,18 @@ void Input::setupContent() {
}
}
- auto data = stepData();
- data.currentPassword = pass;
- setStepData(std::move(data));
- showOther(CloudPasswordManageId());
+ if (currentStepValidate) {
+ controller()->session().promoSuggestions().dismiss(
+ Data::PromoSuggestions::SugValidatePassword());
+ 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(
- newInput.get(),
- &QLineEdit::textChanged // Covers Undo.
- ) | rpl::map([=] {
- return newInput->text().isEmpty();
- }) | rpl::distinct_until_changed(
- ) | rpl::start_with_next([=](bool empty) {
- const auto from = icon.icon->frameIndex();
- const auto to = empty ? 0 : (icon.icon->framesCount() / 2 - 1);
- icon.icon->animate(icon.update, from, to);
- }, content->lifetime());
+ if (!currentStepValidate) {
+ base::qt_signal_producer(
+ newInput.get(),
+ &QLineEdit::textChanged // Covers Undo.
+ ) | rpl::map([=] {
+ return newInput->text().isEmpty();
+ }) | rpl::distinct_until_changed(
+ ) | rpl::start_with_next([=](bool empty) {
+ const auto from = icon.icon->frameIndex();
+ const auto to = empty ? 0 : (icon.icon->framesCount() / 2 - 1);
+ icon.icon->animate(icon.update, from, to);
+ }, content->lifetime());
+ }
const auto submit = [=] {
if (!reenterInput || reenterInput->hasFocus()) {
@@ -437,7 +511,7 @@ void Input::setupContent() {
QObject::connect(reenterInput, &MaskedInputField::submitted, submit);
}
- setFocusCallback([=] {
+ setFocusCallback(crl::guard(content, [=] {
if (isCheck || newInput->text().isEmpty()) {
newInput->setFocus();
} else if (reenterInput->text().isEmpty()) {
@@ -445,7 +519,7 @@ void Input::setupContent() {
} else {
newInput->setFocus();
}
- });
+ }));
Ui::ResizeFitChild(this, content);
}
@@ -586,10 +660,33 @@ void Input::setupRecoverButton(
});
}
+class SuggestionInput : public Input {
+public:
+ SuggestionInput(
+ QWidget *parent,
+ not_null controller)
+ : Input(parent, controller)
+ , _stepData(StepData{ .suggestionValidate = true }) {
+ setStepDataReference(_stepData);
+ }
+
+ [[nodiscard]] static Type Id() {
+ return SectionFactory::Instance();
+ }
+
+private:
+ std::any _stepData;
+
+};
+
} // namespace CloudPassword
Type CloudPasswordInputId() {
return CloudPassword::Input::Id();
}
+Type CloudPasswordSuggestionInputId() {
+ return CloudPassword::SuggestionInput::Id();
+}
+
} // namespace Settings
diff --git a/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_input.h b/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_input.h
index 1103047df4..0c1ae4f89c 100644
--- a/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_input.h
+++ b/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_input.h
@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Settings {
Type CloudPasswordInputId();
+Type CloudPasswordSuggestionInputId();
} // namespace Settings
diff --git a/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_validate_icon.cpp b/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_validate_icon.cpp
new file mode 100644
index 0000000000..7ea244e8ff
--- /dev/null
+++ b/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_validate_icon.cpp
@@ -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 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 CreateValidateGoodIcon(
+ not_null session) {
+ const auto document = EmojiValidateGood(session);
+ if (!document) {
+ return nullptr;
+ }
+
+ auto owned = object_ptr((QWidget*)nullptr);
+ const auto widget = owned.data();
+
+ struct State {
+ std::unique_ptr emoji;
+ };
+ const auto state = widget->lifetime().make_state();
+ const auto size = st::settingsCloudPasswordIconSize;
+ state->emoji = std::make_unique(
+ 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
diff --git a/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_validate_icon.h b/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_validate_icon.h
new file mode 100644
index 0000000000..54280d7360
--- /dev/null
+++ b/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_validate_icon.h
@@ -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
+class object_ptr;
+
+namespace Main {
+class Session;
+} // namespace Main
+
+namespace Ui {
+class RpWidget;
+} // namespace Ui
+
+namespace Settings {
+
+[[nodiscard]] object_ptr CreateValidateGoodIcon(
+ not_null session);
+
+} // namespace Settings
+
diff --git a/Telegram/SourceFiles/settings/settings_main.cpp b/Telegram/SourceFiles/settings/settings_main.cpp
index 07ad5e1f99..496e5e2933 100644
--- a/Telegram/SourceFiles/settings/settings_main.cpp
+++ b/Telegram/SourceFiles/settings/settings_main.cpp
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "settings/settings_main.h"
+#include "settings/cloud_password/settings_cloud_password_input.h"
#include "api/api_credits.h"
#include "core/application.h"
#include "core/click_handler_types.h"
@@ -467,7 +468,7 @@ void SetupValidatePhoneNumberSuggestion(
yes->setClickedCallback([=] {
controller->session().promoSuggestions().dismiss(
kSugValidatePhone.utf8());
- mainWrap->toggle(false, anim::type::normal);
+ mainWrap->toggle(false, anim::type::normal);
});
const auto no = Ui::CreateChild(
wrap,
@@ -522,6 +523,77 @@ void SetupValidatePhoneNumberSuggestion(
Ui::AddSkip(content);
}
+void SetupValidatePasswordSuggestion(
+ not_null controller,
+ not_null container,
+ Fn showOther) {
+ if (!controller->session().promoSuggestions().current(
+ Data::PromoSuggestions::SugValidatePassword())
+ || controller->session().promoSuggestions().current(
+ kSugValidatePhone.utf8())) {
+ return;
+ }
+ const auto mainWrap = container->add(
+ object_ptr>(
+ container,
+ object_ptr(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(
+ content,
+ tr::lng_settings_suggestion_password_about(),
+ st::boxLabel),
+ st::boxRowPadding);
+
+ Ui::AddSkip(content);
+ Ui::AddSkip(content);
+
+ const auto wrap = content->add(
+ object_ptr(
+ content,
+ st::inviteLinkButton.height),
+ st::inviteLinkButtonsPadding);
+ const auto yes = Ui::CreateChild(
+ 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(
+ 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(
not_null controller,
not_null container,
@@ -533,6 +605,10 @@ void SetupSections(
controller,
container,
showOther);
+ SetupValidatePasswordSuggestion(
+ controller,
+ container,
+ showOther);
const auto addSection = [&](
rpl::producer label,