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,