diff --git a/Telegram/Resources/icons/menu/passcode_finger.png b/Telegram/Resources/icons/menu/passcode_finger.png new file mode 100644 index 000000000..ee267f708 Binary files /dev/null and b/Telegram/Resources/icons/menu/passcode_finger.png differ diff --git a/Telegram/Resources/icons/menu/passcode_finger@2x.png b/Telegram/Resources/icons/menu/passcode_finger@2x.png new file mode 100644 index 000000000..a0a34240b Binary files /dev/null and b/Telegram/Resources/icons/menu/passcode_finger@2x.png differ diff --git a/Telegram/Resources/icons/menu/passcode_finger@3x.png b/Telegram/Resources/icons/menu/passcode_finger@3x.png new file mode 100644 index 000000000..291fc8eaf Binary files /dev/null and b/Telegram/Resources/icons/menu/passcode_finger@3x.png differ diff --git a/Telegram/Resources/icons/menu/passcode_winhello.png b/Telegram/Resources/icons/menu/passcode_winhello.png new file mode 100644 index 000000000..e3effa23a Binary files /dev/null and b/Telegram/Resources/icons/menu/passcode_winhello.png differ diff --git a/Telegram/Resources/icons/menu/passcode_winhello@2x.png b/Telegram/Resources/icons/menu/passcode_winhello@2x.png new file mode 100644 index 000000000..32bc819f5 Binary files /dev/null and b/Telegram/Resources/icons/menu/passcode_winhello@2x.png differ diff --git a/Telegram/Resources/icons/menu/passcode_winhello@3x.png b/Telegram/Resources/icons/menu/passcode_winhello@3x.png new file mode 100644 index 000000000..6ac7445c1 Binary files /dev/null and b/Telegram/Resources/icons/menu/passcode_winhello@3x.png differ diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 80441cd03..01a1e7cf5 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -683,6 +683,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_privacy_premium_link" = "Telegram Premium"; "lng_settings_passcode_disable" = "Disable Passcode"; "lng_settings_passcode_disable_sure" = "Are you sure you want to disable passcode?"; +"lng_settings_use_winhello" = "Unlock with Windows Hello"; +"lng_settings_use_winhello_about" = "You need to enter your passcode once before unlocking Telegram with Windows Hello."; +"lng_settings_use_touchid" = "Unlock with Touch ID"; +"lng_settings_use_touchid_about" = "You need to enter your passcode once before unlocking Telegram with Touch ID."; "lng_settings_password_disable" = "Disable Cloud Password"; "lng_settings_password_abort" = "Abort two-step verification setup"; "lng_settings_about_bio" = "Any details such as age, occupation or city.\nExample: 23 y.o. designer from San Francisco"; @@ -992,6 +996,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_passcode_ph" = "Your passcode"; "lng_passcode_submit" = "Submit"; "lng_passcode_logout" = "Log out"; +"lng_passcode_winhello" = "You need to enter your passcode\nbefore you can use Windows Hello."; +"lng_passcode_touchid" = "You need to enter your passcode\nbefore you can use Touch ID."; +"lng_passcode_winhello_unlock" = "Telegram wants to unlock with Windows Hello."; +"lng_passcode_touchid_unlock" = "unlock"; "lng_passcode_create_button" = "Save Passcode"; "lng_passcode_check_button" = "Submit"; "lng_passcode_change_button" = "Save Passcode"; diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index f17389f1b..364d8f289 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -290,6 +290,28 @@ passcodeTextLine: 28px; passcodeLittleSkip: 5px; passcodeAboutSkip: 7px; passcodeSkip: 23px; +passcodeSystemUnlock: IconButton(defaultIconButton) { + width: 32px; + height: 32px; + rippleAreaSize: 24px; + rippleAreaPosition: point(4px, 4px); + ripple: RippleAnimation(defaultRippleAnimation) { + color: lightButtonBgOver; + } +} +passcodeSystemWinHello: IconButton(passcodeSystemUnlock) { + icon: icon{{ "menu/passcode_winhello", lightButtonFg }}; + iconOver: icon{{ "menu/passcode_winhello", lightButtonFg }}; +} +passcodeSystemTouchID: IconButton(passcodeSystemUnlock) { + icon: icon{{ "menu/passcode_finger", lightButtonFg }}; + iconOver: icon{{ "menu/passcode_finger", lightButtonFg }}; +} +passcodeSystemUnlockLater: FlatLabel(defaultFlatLabel) { + align: align(top); + textFg: windowSubTextFg; +} +passcodeSystemUnlockSkip: 12px; newGroupAboutFg: windowSubTextFg; newGroupPadding: margins(4px, 6px, 4px, 3px); diff --git a/Telegram/SourceFiles/core/core_settings.cpp b/Telegram/SourceFiles/core/core_settings.cpp index daaff5acf..efd0ba4d5 100644 --- a/Telegram/SourceFiles/core/core_settings.cpp +++ b/Telegram/SourceFiles/core/core_settings.cpp @@ -220,7 +220,7 @@ QByteArray Settings::serialize() const { + Serialize::bytearraySize(ivPosition) + Serialize::stringSize(noWarningExtensions) + Serialize::stringSize(_customFontFamily) - + sizeof(qint32); + + sizeof(qint32) * 2; auto result = QByteArray(); result.reserve(size); @@ -371,7 +371,8 @@ QByteArray Settings::serialize() const { << qint32(std::clamp( qRound(_dialogsNoChatWidthRatio.current() * 1000000), 0, - 1000000)); + 1000000)) + << qint32(_systemUnlockEnabled ? 1 : 0); } Ensures(result.size() == size); @@ -491,6 +492,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) { qint32 ttlVoiceClickTooltipHidden = _ttlVoiceClickTooltipHidden.current() ? 1 : 0; QByteArray ivPosition; QString customFontFamily = _customFontFamily; + qint32 systemUnlockEnabled = _systemUnlockEnabled ? 1 : 0; stream >> themesAccentColors; if (!stream.atEnd()) { @@ -788,6 +790,9 @@ void Settings::addFromSerialized(const QByteArray &serialized) { 0., 1.); } + if (!stream.atEnd()) { + stream >> systemUnlockEnabled; + } if (stream.status() != QDataStream::Ok) { LOG(("App Error: " "Bad data for Core::Settings::constructFromSerialized()")); @@ -995,6 +1000,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) { _ivPosition = Deserialize(ivPosition); } _customFontFamily = customFontFamily; + _systemUnlockEnabled = (systemUnlockEnabled == 1); } QString Settings::getSoundPath(const QString &key) const { diff --git a/Telegram/SourceFiles/core/core_settings.h b/Telegram/SourceFiles/core/core_settings.h index 2588923c6..3412c4588 100644 --- a/Telegram/SourceFiles/core/core_settings.h +++ b/Telegram/SourceFiles/core/core_settings.h @@ -884,6 +884,13 @@ public: _customFontFamily = value; } + [[nodiscard]] bool systemUnlockEnabled() const { + return _systemUnlockEnabled; + } + void setSystemUnlockEnabled(bool enabled) { + _systemUnlockEnabled = enabled; + } + [[nodiscard]] static bool ThirdColumnByDefault(); [[nodiscard]] static float64 DefaultDialogsWidthRatio(); @@ -1014,6 +1021,7 @@ private: rpl::variable _ttlVoiceClickTooltipHidden = false; WindowPosition _ivPosition; QString _customFontFamily; + bool _systemUnlockEnabled = false; bool _tabbedReplacedWithInfo = false; // per-window rpl::event_stream _tabbedReplacedWithInfoValue; // per-window diff --git a/Telegram/SourceFiles/settings/settings_local_passcode.cpp b/Telegram/SourceFiles/settings/settings_local_passcode.cpp index 9a766f16d..db21f717b 100644 --- a/Telegram/SourceFiles/settings/settings_local_passcode.cpp +++ b/Telegram/SourceFiles/settings/settings_local_passcode.cpp @@ -8,6 +8,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_local_passcode.h" #include "base/platform/base_platform_last_input.h" +#include "base/platform/base_platform_info.h" +#include "base/system_unlock.h" #include "boxes/auto_lock_box.h" #include "core/application.h" #include "core/core_settings.h" @@ -23,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/fields/password_input.h" #include "ui/widgets/labels.h" #include "ui/wrap/vertical_layout.h" +#include "ui/wrap/slide_wrap.h" #include "window/window_session_controller.h" #include "styles/style_boxes.h" #include "styles/style_layers.h" @@ -199,11 +202,11 @@ void LocalPasscodeEnter::setupContent() { content, object_ptr( content, - isCreate + (isCreate ? tr::lng_passcode_create_button() : isCheck ? tr::lng_passcode_check_button() - : tr::lng_passcode_change_button(), + : tr::lng_passcode_change_button()), st::changePhoneButton)), st::settingLocalPasscodeButtonPadding)->entity(); button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); @@ -239,6 +242,8 @@ void LocalPasscodeEnter::setupContent() { } SetPasscode(_controller, newText); if (isCreate) { + Core::App().settings().setSystemUnlockEnabled(true); + Core::App().saveSettingsDelayed(); _showOther.fire(LocalPasscodeManageId()); } else if (isChange) { _showBack.fire({}); @@ -506,6 +511,46 @@ void LocalPasscodeManage::setupContent() { divider->skipEdge(Qt::BottomEdge, shown); }, divider->lifetime()); + const auto systemUnlockWrap = content->add( + object_ptr>( + content, + object_ptr(content)) + )->setDuration(0); + const auto systemUnlockContent = systemUnlockWrap->entity(); + + Ui::AddSkip(systemUnlockContent); + + AddButtonWithIcon( + systemUnlockContent, + (Platform::IsWindows() + ? tr::lng_settings_use_winhello() + : tr::lng_settings_use_touchid()), + st::settingsButton, + { Platform::IsWindows() + ? &st::menuIconWinHello + : &st::menuIconTouchID } + )->toggleOn( + rpl::single(Core::App().settings().systemUnlockEnabled()) + )->toggledChanges( + ) | rpl::filter([=](bool value) { + return value != Core::App().settings().systemUnlockEnabled(); + }) | rpl::start_with_next([=](bool value) { + Core::App().settings().setSystemUnlockEnabled(value); + Core::App().saveSettingsDelayed(); + }, systemUnlockContent->lifetime()); + + Ui::AddSkip(systemUnlockContent); + + Ui::AddDividerText( + systemUnlockContent, + (Platform::IsWindows() + ? tr::lng_settings_use_winhello_about() + : tr::lng_settings_use_touchid_about())); + + using namespace rpl::mappers; + systemUnlockWrap->toggleOn(base::SystemUnlockStatus( + ) | rpl::map(_1 == base::SystemUnlockAvailability::Available)); + Ui::ResizeFitChild(this, content); } diff --git a/Telegram/SourceFiles/settings/settings_privacy_security.cpp b/Telegram/SourceFiles/settings/settings_privacy_security.cpp index ab8f32217..daea2e9ee 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_security.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_security.cpp @@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_premium.h" // Settings::ShowPremium. #include "settings/settings_privacy_controllers.h" #include "settings/settings_websites.h" +#include "base/system_unlock.h" #include "base/timer_rpl.h" #include "boxes/passcode_box.h" #include "boxes/sessions_box.h" @@ -1079,6 +1080,8 @@ PrivacySecurity::PrivacySecurity( not_null controller) : Section(parent) { setupContent(controller); + + [[maybe_unused]] auto preload = base::SystemUnlockStatus(); } rpl::producer PrivacySecurity::title() { diff --git a/Telegram/SourceFiles/ui/menu_icons.style b/Telegram/SourceFiles/ui/menu_icons.style index 8d08ff7de..ce1670203 100644 --- a/Telegram/SourceFiles/ui/menu_icons.style +++ b/Telegram/SourceFiles/ui/menu_icons.style @@ -155,6 +155,8 @@ menuIconTagRename: icon{{ "menu/tag_rename", menuIconColor }}; menuIconGroupsHide: icon {{ "menu/hide_members", menuIconColor }}; menuIconFont: icon {{ "menu/fonts", menuIconColor }}; menuIconFactcheck: icon {{ "menu/factcheck", menuIconColor }}; +menuIconWinHello: icon {{ "menu/passcode_winhello", menuIconColor }}; +menuIconTouchID: icon {{ "menu/passcode_finger", menuIconColor }}; menuIconTTLAny: icon {{ "menu/auto_delete_plain", menuIconColor }}; menuIconTTLAnyTextPosition: point(11px, 22px); diff --git a/Telegram/SourceFiles/window/window_lock_widgets.cpp b/Telegram/SourceFiles/window/window_lock_widgets.cpp index 58f4881fa..e6453fbf2 100644 --- a/Telegram/SourceFiles/window/window_lock_widgets.cpp +++ b/Telegram/SourceFiles/window/window_lock_widgets.cpp @@ -7,6 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "window/window_lock_widgets.h" +#include "base/platform/base_platform_info.h" +#include "base/call_delayed.h" +#include "base/system_unlock.h" #include "lang/lang_keys.h" #include "storage/storage_domain.h" #include "mainwindow.h" @@ -27,6 +30,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_boxes.h" namespace Window { +namespace { + +constexpr auto kSystemUnlockDelay = crl::time(1000); + +} // namespace LockWidget::LockWidget(QWidget *parent, not_null window) : RpWidget(parent) @@ -99,6 +107,108 @@ PasscodeLockWidget::PasscodeLockWidget( _logout->setClickedCallback([=] { window->showLogoutConfirmation(); }); + + using namespace rpl::mappers; + if (Core::App().settings().systemUnlockEnabled()) { + _systemUnlockAvailable = base::SystemUnlockStatus() + | rpl::map(_1 == base::SystemUnlockAvailability::Available); + if (Core::App().domain().started()) { + _systemUnlockAllowed = _systemUnlockAvailable.value(); + setupSystemUnlock(); + } else { + setupSystemUnlockInfo(); + } + } +} + +void PasscodeLockWidget::setupSystemUnlockInfo() { + const auto info = Ui::CreateChild( + this, + (Platform::IsWindows() + ? tr::lng_passcode_winhello() + : tr::lng_passcode_touchid()), + st::passcodeSystemUnlockLater); + _logout->geometryValue( + ) | rpl::start_with_next([=](QRect logout) { + info->resizeToWidth(width() + - st::boxRowPadding.left() + - st::boxRowPadding.right()); + info->moveToLeft( + st::boxRowPadding.left(), + logout.y() + logout.height() + st::passcodeSystemUnlockSkip); + }, info->lifetime()); + info->showOn(_systemUnlockAvailable.value()); +} + +void PasscodeLockWidget::setupSystemUnlock() { + windowActiveValue() | rpl::skip(1) | rpl::filter([=](bool active) { + return active + && !_systemUnlockSuggested + && !_systemUnlockCooldown.isActive(); + }) | rpl::start_with_next([=](bool) { + [[maybe_unused]] auto refresh = base::SystemUnlockStatus(); + suggestSystemUnlock(); + }, lifetime()); + + const auto button = Ui::CreateChild( + _passcode.data(), + (Platform::IsWindows() + ? st::passcodeSystemWinHello + : st::passcodeSystemTouchID)); + button->showOn(_systemUnlockAllowed.value()); + _passcode->sizeValue() | rpl::start_with_next([=](QSize size) { + button->moveToRight(0, size.height() - button->height()); + }, button->lifetime()); + button->setClickedCallback([=] { + const auto delay = st::passcodeSystemUnlock.ripple.hideDuration; + base::call_delayed(delay, this, [=] { + suggestSystemUnlock(); + }); + }); +} + +void PasscodeLockWidget::suggestSystemUnlock() { + InvokeQueued(this, [=] { + if (_systemUnlockSuggested) { + return; + } + _systemUnlockCooldown.cancel(); + + using namespace base; + _systemUnlockAllowed.value( + ) | rpl::filter( + rpl::mappers::_1 + ) | rpl::take(1) | rpl::start_with_next([=] { + const auto weak = Ui::MakeWeak(this); + const auto done = [weak](SystemUnlockResult result) { + crl::on_main([=] { + if (const auto strong = weak.data()) { + strong->systemUnlockDone(result); + } + }); + }; + SuggestSystemUnlock( + this, + (::Platform::IsWindows() + ? tr::lng_passcode_winhello_unlock(tr::now) + : tr::lng_passcode_touchid_unlock(tr::now)), + done); + }, _systemUnlockSuggested); + }); +} + +void PasscodeLockWidget::systemUnlockDone(base::SystemUnlockResult result) { + if (result == base::SystemUnlockResult::Success) { + Core::App().unlockPasscode(); + return; + } + _systemUnlockCooldown.callOnce(kSystemUnlockDelay); + _systemUnlockSuggested.destroy(); + if (result == base::SystemUnlockResult::FloodError) { + _error = tr::lng_flood_error(tr::now); + _passcode->setFocusFast(); + update(); + } } void PasscodeLockWidget::paintContent(QPainter &p) { diff --git a/Telegram/SourceFiles/window/window_lock_widgets.h b/Telegram/SourceFiles/window/window_lock_widgets.h index d2967d47f..8c48850fb 100644 --- a/Telegram/SourceFiles/window/window_lock_widgets.h +++ b/Telegram/SourceFiles/window/window_lock_widgets.h @@ -11,6 +11,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/animations.h" #include "ui/layers/box_content.h" #include "base/bytes.h" +#include "base/timer.h" + +namespace base { +enum class SystemUnlockResult; +} // namespace base namespace Ui { class PasswordInput; @@ -61,15 +66,25 @@ protected: private: void paintContent(QPainter &p) override; + + void setupSystemUnlockInfo(); + void setupSystemUnlock(); + void suggestSystemUnlock(); + void systemUnlockDone(base::SystemUnlockResult result); void changed(); void submit(); void error(); + rpl::variable _systemUnlockAvailable = false; + rpl::variable _systemUnlockAllowed = false; object_ptr _passcode; object_ptr _submit; object_ptr _logout; QString _error; + rpl::lifetime _systemUnlockSuggested; + base::Timer _systemUnlockCooldown; + }; struct TermsLock { diff --git a/Telegram/lib_base b/Telegram/lib_base index 1be2a262a..b512eead3 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit 1be2a262a6524d6dec3b616c2e9fd2ce42c9e61a +Subproject commit b512eead302cb7b509869778348d60fef64bc19b diff --git a/cmake b/cmake index b92244f0c..104406258 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit b92244f0c21f157600484498c33a3566087526dd +Subproject commit 104406258fa89f007aad69348cef9b433f128fc2