diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 3d7cc7f56d..dd4a7fe519 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1074,6 +1074,8 @@ PRIVATE profile/profile_cover_drop_area.h settings/cloud_password/settings_cloud_password_common.cpp settings/cloud_password/settings_cloud_password_common.h + settings/cloud_password/settings_cloud_password_input.cpp + settings/cloud_password/settings_cloud_password_input.h settings/cloud_password/settings_cloud_password_start.cpp settings/cloud_password/settings_cloud_password_start.h settings/settings_advanced.cpp diff --git a/Telegram/Resources/animations/cloud_password/password_input.tgs b/Telegram/Resources/animations/cloud_password/password_input.tgs new file mode 100644 index 0000000000..f16eca5ada Binary files /dev/null and b/Telegram/Resources/animations/cloud_password/password_input.tgs differ diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index bdd3018279..9060e836b1 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -699,8 +699,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_cloud_password_reset_cancel" = "Cancel password reset"; "lng_cloud_password_enter_old" = "Enter current password"; "lng_cloud_password_enter_first" = "Enter a password"; -"lng_cloud_password_enter_new" = "Enter new password"; -"lng_cloud_password_confirm_new" = "Re-enter new password"; +"lng_cloud_password_enter_new" = "Enter password"; +"lng_cloud_password_confirm_new" = "Re-enter password"; "lng_cloud_password_hint" = "Enter password hint"; "lng_cloud_password_change_hint" = "Enter new password hint"; "lng_cloud_password_bad" = "Password and hint cannot be the same."; diff --git a/Telegram/Resources/qrc/telegram/animations.qrc b/Telegram/Resources/qrc/telegram/animations.qrc index 889f17357b..3b435db305 100644 --- a/Telegram/Resources/qrc/telegram/animations.qrc +++ b/Telegram/Resources/qrc/telegram/animations.qrc @@ -5,5 +5,6 @@ ../../animations/filters.tgs ../../animations/local_passcode_enter.tgs ../../animations/cloud_password/intro.tgs + ../../animations/cloud_password/password_input.tgs diff --git a/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_common.cpp b/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_common.cpp index 8874b8d09f..fc9a3c9fc7 100644 --- a/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_common.cpp +++ b/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_common.cpp @@ -244,6 +244,24 @@ rpl::producer<> AbstractStep::sectionShowBack() { return _showBack.events(); } +void AbstractStep::setStepDataReference(std::any &data) { + _stepData = &data; +} + +StepData AbstractStep::stepData() const { + if (!_stepData || !_stepData->has_value()) { + StepData(); + } + const auto my = std::any_cast(_stepData); + return my ? (*my) : StepData(); +} + +void AbstractStep::setStepData(StepData data) { + if (_stepData) { + *_stepData = data; + } +} + AbstractStep::~AbstractStep() = default; } // namespace Settings::CloudPassword 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 9e676f76e4..19db8ab35d 100644 --- a/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_common.h +++ b/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_common.h @@ -19,6 +19,10 @@ class VerticalLayout; namespace Settings::CloudPassword { +struct StepData { + QString password; +}; + void SetupHeader( not_null content, const QString &lottie, @@ -79,6 +83,8 @@ public: [[nodiscard]] rpl::producer sectionShowOther() override; [[nodiscard]] rpl::producer<> sectionShowBack() override; + void setStepDataReference(std::any &data) override; + protected: [[nodiscard]] not_null controller() const; @@ -89,6 +95,9 @@ protected: [[nodiscard]] rpl::producer<> showFinishes() const; + StepData stepData() const; + void setStepData(StepData data); + private: const not_null _controller; @@ -98,15 +107,17 @@ private: rpl::event_stream _showOther; rpl::event_stream<> _showBack; + std::any *_stepData; + }; template class TypedAbstractStep : public AbstractStep { public: - TypedAbstractStep( - QWidget *parent, - not_null controller) - : AbstractStep(parent, controller) { + using AbstractStep::AbstractStep; + + void setStepDataReference(std::any &data) override final { + AbstractStep::setStepDataReference(data); static_cast(this)->setupContent(); } diff --git a/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_input.cpp b/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_input.cpp new file mode 100644 index 0000000000..907ced39c6 --- /dev/null +++ b/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_input.cpp @@ -0,0 +1,177 @@ +/* +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_input.h" + +#include "base/qt_signal_producer.h" +#include "lang/lang_keys.h" +#include "lottie/lottie_icon.h" +#include "settings/cloud_password/settings_cloud_password_common.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/input_fields.h" +#include "ui/widgets/labels.h" +#include "ui/wrap/vertical_layout.h" +#include "styles/style_boxes.h" +#include "styles/style_settings.h" + +namespace Settings { +namespace CloudPassword { +namespace { + +struct Icon { + not_null icon; + Fn update; +}; + +Icon CreateInteractiveLottieIcon( + not_null container, + Lottie::IconDescriptor &&descriptor, + style::margins padding) { + auto object = object_ptr(container); + const auto raw = object.data(); + + const auto width = descriptor.sizeOverride.width(); + raw->resize(QRect( + QPoint(), + descriptor.sizeOverride).marginsAdded(padding).size()); + + auto owned = Lottie::MakeIcon(std::move(descriptor)); + const auto icon = owned.get(); + + raw->lifetime().add([kept = std::move(owned)]{}); + + raw->paintRequest( + ) | rpl::start_with_next([=] { + auto p = QPainter(raw); + const auto left = (raw->width() - width) / 2; + icon->paint(p, left, padding.top()); + }, raw->lifetime()); + + container->add(std::move(object)); + return { .icon = icon, .update = [=] { raw->update(); } }; +} + +} // namespace + +class Input : public TypedAbstractStep { +public: + using TypedAbstractStep::TypedAbstractStep; + + [[nodiscard]] rpl::producer title() override; + void setupContent(); + +}; + +rpl::producer Input::title() { + return tr::lng_settings_cloud_password_password_title(); +} + +void Input::setupContent() { + const auto content = Ui::CreateChild(this); + auto currentStepData = stepData(); + const auto currentStepDataPassword = base::take(currentStepData.password); + setStepData(currentStepData); + + const auto icon = CreateInteractiveLottieIcon( + content, + { + .name = u"cloud_password/password_input"_q, + .sizeOverride = { + st::changePhoneIconSize, + st::changePhoneIconSize + }, + }, + st::settingLocalPasscodeIconPadding); + + SetupHeader( + content, + QString(), + rpl::never<>(), + tr::lng_settings_cloud_password_password_subtitle(), + tr::lng_cloud_password_about()); + + AddSkip(content, st::settingLocalPasscodeDescriptionBottomSkip); + + const auto newInput = AddPasswordField( + content, + tr::lng_cloud_password_enter_new(), + currentStepDataPassword); + const auto reenterInput = AddPasswordField( + content, + tr::lng_cloud_password_confirm_new(), + currentStepDataPassword); + const auto error = AddError(content, reenterInput); + + if (!newInput->text().isEmpty()) { + icon.icon->jumpTo(icon.icon->framesCount() / 2, icon.update); + } + + const auto button = AddDoneButton(content, tr::lng_continue()); + button->setClickedCallback([=] { + const auto newText = newInput->text(); + const auto reenterText = reenterInput->text(); + if (newText.isEmpty()) { + newInput->setFocus(); + newInput->showError(); + } else if (reenterText.isEmpty()) { + reenterInput->setFocus(); + reenterInput->showError(); + } else if (newText != reenterText) { + reenterInput->setFocus(); + reenterInput->showError(); + reenterInput->selectAll(); + error->show(); + error->setText(tr::lng_cloud_password_differ(tr::now)); + } else { + auto data = stepData(); + data.password = newText; + setStepData(std::move(data)); + } + }); + + 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->hasFocus()) { + button->clicked({}, Qt::LeftButton); + } else { + reenterInput->setFocus(); + } + }; + QObject::connect(newInput, &Ui::MaskedInputField::submitted, submit); + QObject::connect(reenterInput, &Ui::MaskedInputField::submitted, submit); + + setFocusCallback([=] { + if (newInput->text().isEmpty()) { + newInput->setFocus(); + } else if (reenterInput->text().isEmpty()) { + reenterInput->setFocus(); + } else { + newInput->setFocus(); + } + }); + + Ui::ResizeFitChild(this, content); +} + +} // namespace CloudPassword + +Type CloudPasswordInputId() { + return CloudPassword::Input::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 new file mode 100644 index 0000000000..1103047df4 --- /dev/null +++ b/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_input.h @@ -0,0 +1,17 @@ +/* +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 + +#include "settings/settings_type.h" + +namespace Settings { + +Type CloudPasswordInputId(); + +} // namespace Settings + diff --git a/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_start.cpp b/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_start.cpp index a728efee3e..0bb13778d0 100644 --- a/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_start.cpp +++ b/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_start.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "settings/cloud_password/settings_cloud_password_common.h" +#include "settings/cloud_password/settings_cloud_password_input.h" #include "ui/widgets/buttons.h" #include "ui/wrap/vertical_layout.h" #include "styles/style_settings.h" @@ -49,6 +50,7 @@ void Start::setupContent() { content, tr::lng_settings_cloud_password_password_subtitle() )->setClickedCallback([=] { + showOther(CloudPasswordInputId()); }); Ui::ResizeFitChild(this, content);