diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index c2a6e1b95..5b11282a3 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1092,6 +1092,8 @@ PRIVATE settings/settings_information.h settings/settings_intro.cpp settings/settings_intro.h + settings/settings_local_passcode.cpp + settings/settings_local_passcode.h settings/settings_main.cpp settings/settings_main.h settings/settings_notifications.cpp diff --git a/Telegram/Resources/animations/local_passcode_enter.tgs b/Telegram/Resources/animations/local_passcode_enter.tgs new file mode 100644 index 000000000..e46c05c6e Binary files /dev/null and b/Telegram/Resources/animations/local_passcode_enter.tgs differ diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 19f288c59..6e45bd766 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -647,6 +647,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_passcode_enter_new" = "Enter new passcode"; "lng_passcode_confirm_new" = "Re-enter new passcode"; "lng_passcode_about" = "When a local passcode is set, a lock icon appears at the top of your chats list. Click it to lock the app.\n\nNote: if you forget your local passcode, you'll need to relogin in Telegram Desktop."; +"lng_passcode_about1" = "When a local passcode is set, a lock icon appears at the top of your chats list."; +"lng_passcode_about2" = "Click it to lock Telegram Desktop."; +"lng_passcode_about3" = "Note: if you forget your passcode, you'll need to log out of Telegram Desktop and log in again."; "lng_passcode_differ" = "Passcodes are different"; "lng_passcode_wrong" = "Wrong passcode"; "lng_passcode_is_same" = "Passcode was not changed"; @@ -655,6 +658,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_passcode_submit" = "Submit"; "lng_passcode_logout" = "Log out"; "lng_passcode_need_unblock" = "You need to unlock me first."; +"lng_passcode_create_button" = "Save Passcode"; +"lng_passcode_check_button" = "Submit"; +"lng_passcode_change_button" = "Save Passcode"; +"lng_passcode_create_title" = "Create Local Passcode"; +"lng_passcode_check_title" = "Enter Passcode"; +"lng_passcode_change_title" = "Enter Passcode"; "lng_cloud_password_waiting_code" = "Confirmation code sent to {email}..."; "lng_cloud_password_confirm" = "Confirm recovery email"; diff --git a/Telegram/Resources/qrc/telegram/animations.qrc b/Telegram/Resources/qrc/telegram/animations.qrc index f228e20b0..abb233167 100644 --- a/Telegram/Resources/qrc/telegram/animations.qrc +++ b/Telegram/Resources/qrc/telegram/animations.qrc @@ -3,5 +3,6 @@ ../../animations/change_number.tgs ../../animations/blocked_peers_empty.tgs ../../animations/filters.tgs + ../../animations/local_passcode_enter.tgs diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style index 0ec8e9ea3..90336e8ba 100644 --- a/Telegram/SourceFiles/settings/settings.style +++ b/Telegram/SourceFiles/settings/settings.style @@ -142,6 +142,19 @@ settingsCloudPasswordLabel: FlatLabel(defaultFlatLabel) { } settingsCloudPasswordLabelPadding: margins(22px, 8px, 10px, 8px); +settingLocalPasscodeInputField: InputField(defaultInputField) { + width: 256px; +} +settingLocalPasscodeDescription: FlatLabel(changePhoneDescription) { + minWidth: 256px; +} +settingLocalPasscodeError: FlatLabel(changePhoneError) { + minWidth: 256px; +} +settingLocalPasscodeDescriptionBottomSkip: 15px; +settingLocalPasscodeIconPadding: margins(0px, 19px, 0px, 5px); +settingLocalPasscodeButtonPadding: margins(0px, 19px, 0px, 15px); + settingsInfoPhotoHeight: 161px; settingsInfoPhotoSize: 100px; settingsInfoPhoto: UserpicButton(defaultUserpicButton) { diff --git a/Telegram/SourceFiles/settings/settings_local_passcode.cpp b/Telegram/SourceFiles/settings/settings_local_passcode.cpp new file mode 100644 index 000000000..505625b28 --- /dev/null +++ b/Telegram/SourceFiles/settings/settings_local_passcode.cpp @@ -0,0 +1,195 @@ +/* +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/settings_local_passcode.h" + +#include "lang/lang_keys.h" +#include "lottie/lottie_icon.h" +#include "main/main_domain.h" +#include "main/main_session.h" +#include "storage/storage_domain.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/input_fields.h" +#include "ui/widgets/labels.h" +#include "ui/wrap/vertical_layout.h" +#include "window/window_session_controller.h" +#include "styles/style_settings.h" +#include "styles/style_boxes.h" + +namespace Settings { +namespace details { + +LocalPasscodeEnter::LocalPasscodeEnter( + QWidget *parent, + not_null controller) +: AbstractSection(parent) +, _controller(controller) { +} + +rpl::producer LocalPasscodeEnter::title() { + return tr::lng_settings_passcode_title(); +} + +void LocalPasscodeEnter::setupContent() { + const auto content = Ui::CreateChild(this); + + const auto isCreate = (enterType() == EnterType::Create); + const auto isCheck = (enterType() == EnterType::Check); + + auto icon = CreateLottieIcon( + content, + { + .name = u"local_passcode_enter"_q, + .sizeOverride = { + st::changePhoneIconSize, + st::changePhoneIconSize, + }, + }, + st::settingLocalPasscodeIconPadding); + content->add(std::move(icon.widget)); + _showFinished.events( + ) | rpl::start_with_next([animate = std::move(icon.animate)] { + animate(anim::repeat::once); + }, content->lifetime()); + + AddSkip(content); + + content->add( + object_ptr>( + content, + object_ptr( + content, + isCreate + ? tr::lng_passcode_create_title() + : isCheck + ? tr::lng_passcode_check_title() + : tr::lng_passcode_change_title(), + st::changePhoneTitle)), + st::changePhoneTitlePadding); + + const auto addDescription = [&](rpl::producer &&text) { + const auto &st = st::settingLocalPasscodeDescription; + content->add( + object_ptr>( + content, + object_ptr(content, std::move(text), st)), + st::changePhoneDescriptionPadding); + }; + + addDescription(tr::lng_passcode_about1()); + AddSkip(content); + addDescription(tr::lng_passcode_about2()); + + AddSkip(content, st::settingLocalPasscodeDescriptionBottomSkip); + + const auto addField = [&](rpl::producer &&text) { + const auto &st = st::settingLocalPasscodeInputField; + auto container = object_ptr(content); + container->resize(container->width(), st.heightMin); + const auto field = Ui::CreateChild( + container.data(), + st, + std::move(text)); + + container->geometryValue( + ) | rpl::start_with_next([=](const QRect &r) { + field->moveToLeft((r.width() - field->width()) / 2, 0); + }, container->lifetime()); + + content->add(std::move(container)); + return field; + }; + + const auto addError = [&](not_null input) { + const auto error = content->add( + object_ptr>( + content, + object_ptr( + content, + // Set any text to resize. + tr::lng_language_name(tr::now), + st::settingLocalPasscodeError)), + st::changePhoneDescriptionPadding)->entity(); + error->hide(); + QObject::connect(input.get(), &Ui::MaskedInputField::changed, [=] { + error->hide(); + }); + return error; + }; + + const auto newPasscode = addField(tr::lng_passcode_enter_first()); + + const auto reenterPasscode = isCheck + ? (Ui::PasswordInput*)(nullptr) + : addField(tr::lng_passcode_confirm_new()); + const auto reenterError = isCheck + ? (Ui::FlatLabel*)(nullptr) + : addError(reenterPasscode); + + const auto button = content->add( + object_ptr>( + content, + object_ptr( + content, + isCreate + ? tr::lng_passcode_create_button() + : isCheck + ? tr::lng_passcode_check_button() + : tr::lng_passcode_change_button(), + st::changePhoneButton)), + st::settingLocalPasscodeButtonPadding)->entity(); + button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); + button->setClickedCallback([=] { + const auto newText = newPasscode->text(); + const auto reenterText = reenterPasscode + ? reenterPasscode->text() + : QString(); + if (isCreate) { + if (newText.isEmpty()) { + newPasscode->setFocus(); + newPasscode->showError(); + } else if (reenterText.isEmpty()) { + reenterPasscode->setFocus(); + reenterPasscode->showError(); + } else if (newText != reenterText) { + reenterPasscode->setFocus(); + reenterPasscode->showError(); + reenterPasscode->selectAll(); + reenterError->show(); + reenterError->setText(tr::lng_passcode_differ(tr::now)); + } else { + // showOther + } + } + }); + + _setInnerFocus.events( + ) | rpl::start_with_next([=] { + if (newPasscode->text().isEmpty()) { + newPasscode->setFocus(); + } else if (reenterPasscode && reenterPasscode->text().isEmpty()) { + reenterPasscode->setFocus(); + } else { + newPasscode->setFocus(); + } + }, content->lifetime()); + + Ui::ResizeFitChild(this, content); +} + +void LocalPasscodeEnter::showFinished() { + _showFinished.fire({}); +} + +void LocalPasscodeEnter::setInnerFocus() { + _setInnerFocus.fire({}); +} + +LocalPasscodeEnter::~LocalPasscodeEnter() = default; + +} // namespace details +} // namespace Settings diff --git a/Telegram/SourceFiles/settings/settings_local_passcode.h b/Telegram/SourceFiles/settings/settings_local_passcode.h new file mode 100644 index 000000000..ad12e1ee4 --- /dev/null +++ b/Telegram/SourceFiles/settings/settings_local_passcode.h @@ -0,0 +1,108 @@ +/* +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_common.h" + +namespace Settings { +namespace details { + +class LocalPasscodeEnter : public AbstractSection { +public: + enum class EnterType { + Create, + Check, + Change, + }; + + LocalPasscodeEnter( + QWidget *parent, + not_null controller); + ~LocalPasscodeEnter(); + + void showFinished() override; + void setInnerFocus() override; + + [[nodiscard]] rpl::producer title() override; + +protected: + void setupContent(); + + [[nodiscard]] virtual EnterType enterType() const = 0; + +private: + + const not_null _controller; + + rpl::event_stream<> _showFinished; + rpl::event_stream<> _setInnerFocus; + +}; + +} // namespace details + +class LocalPasscodeCreate; +class LocalPasscodeCheck; +class LocalPasscodeChange; + +template +class TypedLocalPasscodeEnter : public details::LocalPasscodeEnter { +public: + TypedLocalPasscodeEnter( + QWidget *parent, + not_null controller) + : details::LocalPasscodeEnter(parent, controller) { + setupContent(); + } + + [[nodiscard]] static Type Id() { + return &SectionMetaImplementation::Meta; + } + [[nodiscard]] Type id() const final override { + return Id(); + } + +protected: + [[nodiscard]] EnterType enterType() const final override { + if constexpr (std::is_same_v) { + return EnterType::Create; + } + if constexpr (std::is_same_v) { + return EnterType::Check; + } + if constexpr (std::is_same_v) { + return EnterType::Change; + } + return EnterType::Create; + } + +}; + +class LocalPasscodeCreate final + : public TypedLocalPasscodeEnter { +public: + using TypedLocalPasscodeEnter::TypedLocalPasscodeEnter; + +}; + +class LocalPasscodeCheck final + : public TypedLocalPasscodeEnter { +public: + using TypedLocalPasscodeEnter::TypedLocalPasscodeEnter; + +}; + +class LocalPasscodeChange final + : public TypedLocalPasscodeEnter { +public: + using TypedLocalPasscodeEnter::TypedLocalPasscodeEnter; + +}; + +} // namespace Settings +