/* 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_passkeys.h" #include "settings/cloud_password/settings_cloud_password_common.h" #include "settings/settings_common_session.h" #include "data/components/passkeys.h" #include "data/data_session.h" #include "window/window_session_controller.h" #include "main/main_session.h" #include "platform/platform_webauthn.h" #include "ui/layers/generic_box.h" #include "ui/painter.h" #include "ui/rect.h" #include "ui/text/text_utilities.h" #include "ui/vertical_list.h" #include "ui/widgets/buttons.h" #include "ui/wrap/vertical_layout.h" #include "lang/lang_keys.h" #include "lottie/lottie_icon.h" #include "base/unixtime.h" #include "styles/style_boxes.h" #include "styles/style_channel_earn.h" #include "styles/style_chat.h" #include "styles/style_layers.h" #include "styles/style_premium.h" #include "styles/style_settings.h" namespace Settings { class Passkeys : public Section { public: Passkeys( QWidget *parent, not_null controller); [[nodiscard]] rpl::producer title() override; [[nodiscard]] rpl::producer<> showFinishes() const { return _showFinished.events(); } private: void showFinished() override { _showFinished.fire({}); } void setupContent(not_null controller); rpl::event_stream<> _showFinished; }; void PasskeysNoneBox( not_null box, not_null session) { box->setWidth(st::boxWideWidth); box->setNoContentMargin(true); box->setCloseByEscape(true); box->addTopButton(st::boxTitleClose, [=] { box->closeBox(); }); const auto content = box->verticalLayout().get(); Ui::AddSkip(content); { const auto &size = st::settingsCloudPasswordIconSize; auto icon = CreateLottieIcon( content, { .name = u"passkeys"_q, .sizeOverride = { size, size } }, st::settingLocalPasscodeIconPadding); const auto animate = std::move(icon.animate); box->addRow(std::move(icon.widget), style::al_top); box->showFinishes() | rpl::take(1) | rpl::start_with_next([=] { animate(anim::repeat::once); }, content->lifetime()); } Ui::AddSkip(content); Ui::AddSkip(content); box->addRow( object_ptr( content, tr::lng_settings_passkeys_none_title(), st::boxTitle), style::al_top); Ui::AddSkip(content); box->addRow( object_ptr( content, tr::lng_settings_passkeys_none_about(), st::channelEarnLearnDescription), style::al_top); Ui::AddSkip(content); Ui::AddSkip(content); Ui::AddSkip(content); { const auto padding = QMargins( st::settingsButton.padding.left(), st::boxRowPadding.top(), st::boxRowPadding.right(), st::boxRowPadding.bottom()); const auto addEntry = [&]( rpl::producer title, rpl::producer about, const style::icon &icon) { const auto top = content->add( object_ptr( content, std::move(title), st::channelEarnSemiboldLabel), padding); Ui::AddSkip(content, st::channelEarnHistoryThreeSkip); content->add( object_ptr( content, std::move(about), st::channelEarnHistoryRecipientLabel), padding); const auto left = Ui::CreateChild( box->verticalLayout().get()); left->paintRequest( ) | rpl::start_with_next([=] { auto p = Painter(left); icon.paint(p, 0, 0, left->width()); }, left->lifetime()); left->resize(icon.size()); top->geometryValue( ) | rpl::start_with_next([=](const QRect &g) { left->moveToLeft( (g.left() - left->width()) / 2, g.top() + st::channelEarnHistoryThreeSkip); }, left->lifetime()); }; addEntry( tr::lng_settings_passkeys_none_info1_title(), tr::lng_settings_passkeys_none_info1_about(), st::settingsIconPasskeysAboutIcon1); Ui::AddSkip(content); Ui::AddSkip(content); addEntry( tr::lng_settings_passkeys_none_info2_title(), tr::lng_settings_passkeys_none_info2_about(), st::settingsIconPasskeysAboutIcon2); Ui::AddSkip(content); Ui::AddSkip(content); addEntry( tr::lng_settings_passkeys_none_info3_title(), tr::lng_settings_passkeys_none_info3_about(), st::settingsIconPasskeysAboutIcon3); Ui::AddSkip(content); Ui::AddSkip(content); } Ui::AddSkip(content); Ui::AddSkip(content); { const auto &st = st::premiumPreviewDoubledLimitsBox; box->setStyle(st); auto button = object_ptr( box, tr::lng_settings_passkeys_none_button(), st::defaultActiveButton); button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); button->resizeToWidth(box->width() - st.buttonPadding.left() - st.buttonPadding.left()); button->setClickedCallback([=] { session->passkeys().initRegistration([=]( const Data::Passkey::RegisterData &data) { Platform::WebAuthn::RegisterKey(data, [=]( Platform::WebAuthn::RegisterResult result) { if (!result.success) { return; } session->passkeys().registerPasskey(result, [=] { box->closeBox(); }); }); }); }); box->addButton(std::move(button)); } } Passkeys::Passkeys( QWidget *parent, not_null controller) : Section(parent) { setupContent(controller); } rpl::producer Passkeys::title() { return tr::lng_settings_passkeys_title(); } void Passkeys::setupContent( not_null controller) { const auto content = Ui::CreateChild(this); const auto session = &controller->session(); CloudPassword::SetupHeader( content, u"passkeys"_q, showFinishes(), rpl::single(QString()), tr::lng_settings_passkeys_about()); const auto container = content->add( object_ptr(content)); const auto &st = st::peerListBoxItem; const auto rebuild = [=] { while (container->count()) { delete container->widgetAt(0); } for (const auto &passkey : session->passkeys().list()) { const auto button = container->add( object_ptr(container)); button->resize(button->width(), st.height); button->paintRequest() | rpl::start_with_next([=, name = passkey.name, date = passkey.date] { auto p = QPainter(button); p.setFont(st.nameStyle.font); p.setPen(st.nameFg); p.drawText(st.namePosition.x(), st.namePosition.y() + st.nameStyle.font->ascent, name); p.setFont(st::contactsStatusFont); p.setPen(st.statusFg); const auto dateStr = base::unixtime::parse( date).toString(u"dd.MM.yyyy"_q); p.drawText(st.statusPosition.x(), st.statusPosition.y() + st::contactsStatusFont->ascent, dateStr); }, button->lifetime()); } }; session->passkeys().requestList( ) | rpl::start_with_next(rebuild, content->lifetime()); rebuild(); Ui::ResizeFitChild(this, content); } Type PasskeysId() { return Passkeys::Id(); } } // namespace Settings