From e0fb9ffbb025f2e6f610686ff8d57a18a5c4bd4c Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 14 Aug 2025 15:15:26 +0300 Subject: [PATCH] Added support of setting up of login email from intro. --- Telegram/CMakeLists.txt | 2 + Telegram/Resources/langs/lang.strings | 3 + Telegram/SourceFiles/intro/intro_code.cpp | 33 ++-- Telegram/SourceFiles/intro/intro_code.h | 2 + Telegram/SourceFiles/intro/intro_email.cpp | 173 +++++++++++++++++++++ Telegram/SourceFiles/intro/intro_email.h | 50 ++++++ Telegram/SourceFiles/intro/intro_phone.cpp | 4 + Telegram/SourceFiles/intro/intro_step.cpp | 2 +- Telegram/SourceFiles/intro/intro_widget.h | 9 ++ 9 files changed, 265 insertions(+), 13 deletions(-) create mode 100644 Telegram/SourceFiles/intro/intro_email.cpp create mode 100644 Telegram/SourceFiles/intro/intro_email.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 13b2d395df..d01f72ec9f 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1123,6 +1123,8 @@ PRIVATE inline_bots/inline_results_widget.h intro/intro_code.cpp intro/intro_code.h + intro/intro_email.cpp + intro/intro_email.h intro/intro_password_check.cpp intro/intro_password_check.h intro/intro_phone.cpp diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index f61c06fa57..2e10feba15 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -377,6 +377,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_intro_fragment_about" = "Get the code for {phone_number} in the Anonymous Numbers section on Fragment."; "lng_intro_fragment_button" = "Open Fragment"; +"lng_intro_email_setup_title" = "Choose a login email"; +"lng_intro_email_confirm_subtitle" = "Please check your email {email} (don't forget the spam folder) and enter the code we just sent you."; + "lng_phone_title" = "Your Phone Number"; "lng_phone_desc" = "Please confirm your country code\nand enter your phone number."; "lng_phone_to_qr" = "Quick log in using QR code"; diff --git a/Telegram/SourceFiles/intro/intro_code.cpp b/Telegram/SourceFiles/intro/intro_code.cpp index f870d52cb6..e41810e8fd 100644 --- a/Telegram/SourceFiles/intro/intro_code.cpp +++ b/Telegram/SourceFiles/intro/intro_code.cpp @@ -84,16 +84,19 @@ void CodeWidget::updateDescText() { const auto byTelegram = getData()->codeByTelegram; const auto isFragment = !getData()->codeByFragmentUrl.isEmpty(); _isFragment = isFragment; - setDescriptionText( - isFragment - ? tr::lng_intro_fragment_about( - lt_phone_number, - rpl::single(TextWithEntities{ - .text = Ui::FormatPhone(getData()->phone) - }), - Ui::Text::RichLangValue) - : (byTelegram ? tr::lng_code_from_telegram : tr::lng_code_desc)( - Ui::Text::RichLangValue)); + setDescriptionText(isEmailVerification() + ? tr::lng_intro_email_confirm_subtitle( + lt_email, + rpl::single(Ui::Text::WrapEmailPattern(getData()->emailPattern)), + Ui::Text::WithEntities) + : isFragment + ? tr::lng_intro_fragment_about( + lt_phone_number, + rpl::single( + TextWithEntities::Simple(Ui::FormatPhone(getData()->phone))), + Ui::Text::RichLangValue) + : (byTelegram ? tr::lng_code_from_telegram : tr::lng_code_desc)( + Ui::Text::RichLangValue)); if (getData()->codeByTelegram) { _noTelegramCode->show(); _callTimer.cancel(); @@ -350,11 +353,13 @@ void CodeWidget::submitCode(const QString &text) { _code->setEnabled(false); getData()->pwdState = Core::CloudPasswordState(); _sentRequest = api().request(MTPauth_SignIn( - MTP_flags(MTPauth_SignIn::Flag::f_phone_code), + MTP_flags(isEmailVerification() + ? MTPauth_SignIn::Flag::f_email_verification + : MTPauth_SignIn::Flag::f_phone_code), MTP_string(getData()->phone), MTP_bytes(getData()->phoneHash), MTP_string(_sentCode), - MTPEmailVerification() + MTP_emailVerificationCode(MTP_string(_sentCode)) )).done([=](const MTPauth_Authorization &result) { codeSubmitDone(result); }).fail([=](const MTP::Error &error) { @@ -362,6 +367,10 @@ void CodeWidget::submitCode(const QString &text) { }).handleFloodErrors().send(); } +bool CodeWidget::isEmailVerification() const { + return !getData()->emailPattern.isEmpty(); +} + rpl::producer CodeWidget::nextButtonText() const { return _isFragment.value( ) | rpl::map([=](bool isFragment) { diff --git a/Telegram/SourceFiles/intro/intro_code.h b/Telegram/SourceFiles/intro/intro_code.h index 53a94b50dc..a9d22ad31b 100644 --- a/Telegram/SourceFiles/intro/intro_code.h +++ b/Telegram/SourceFiles/intro/intro_code.h @@ -54,6 +54,8 @@ private: int errorTop() const override; + [[nodiscard]] bool isEmailVerification() const; + void updateCallText(); void refreshLang(); void updateControlsGeometry(); diff --git a/Telegram/SourceFiles/intro/intro_email.cpp b/Telegram/SourceFiles/intro/intro_email.cpp new file mode 100644 index 0000000000..dacc28cc3f --- /dev/null +++ b/Telegram/SourceFiles/intro/intro_email.cpp @@ -0,0 +1,173 @@ +/* +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 "intro/intro_email.h" + +#include "intro/intro_code.h" +#include "intro/intro_code_input.h" +#include "lang/lang_keys.h" +#include "lottie/lottie_icon.h" +#include "settings/cloud_password/settings_cloud_password_common.h" +#include "settings/settings_common.h" // CreateLottieIcon. +#include "ui/rect.h" +#include "ui/vertical_list.h" +#include "ui/widgets/fields/input_field.h" +#include "ui/widgets/labels.h" +#include "ui/wrap/vertical_layout.h" +#include "window/window_controller.h" +#include "window/window_session_controller.h" +#include "window/window_session_controller.h" +#include "styles/style_intro.h" +#include "styles/style_settings.h" + +namespace Intro { +namespace details { + +EmailWidget::EmailWidget( + QWidget *parent, + not_null account, + not_null data) +: Step(parent, account, data) +, _inner(this) { + const auto content = _inner.get(); + widthValue() | rpl::start_with_next([=](int w) { + content->resizeToWidth(st::introNextButton.width); + content->moveToLeft((w - content->width()) / 2, contentTop()); + }, content->lifetime()); + + content->add( + object_ptr( + content, + tr::lng_intro_email_setup_title(), + st::introTitle), + style::margins(), + style::al_left); + Ui::AddSkip(content, st::lineWidth * 2); + content->add( + object_ptr( + content, + tr::lng_settings_cloud_login_email_about(), + st::introDescription), + style::margins(), + style::al_left); + + { + const auto lottie = u"cloud_password/email"_q; + const auto size = st::settingsCloudPasswordIconSize / 3 * 2; + auto icon = Settings::CreateLottieIcon( + content, + { .name = lottie, .sizeOverride = Size(size) }, + style::margins()); + content->add(std::move(icon.widget)); + _showFinished.events( + ) | rpl::start_with_next([animate = std::move(icon.animate)] { + animate(anim::repeat::once); + }, lifetime()); + } + + const auto wrap = Settings::CloudPassword::AddWrappedField( + content, + tr::lng_settings_cloud_login_email_placeholder(), + QString()); + const auto newInput = wrap->entity(); + Ui::AddSkip(content); + const auto error = Settings::CloudPassword::AddError(content, nullptr); + newInput->changes() | rpl::start_with_next([=] { + error->hide(); + }, newInput->lifetime()); + newInput->setText(getData()->email); + if (newInput->hasText()) { + newInput->selectAll(); + } + _setFocus.events() | rpl::start_with_next([=] { + newInput->setFocus(); + }, newInput->lifetime()); + + _submitCallback = [=] { + const auto send = [=](const QString &email) { + getData()->email = email; + + const auto done = [=](int length, const QString &pattern) { + _sentRequest = 0; + getData()->codeLength = length; + getData()->emailPattern = pattern; + goNext(); + }; + const auto fail = [=](const QString &type) { + _sentRequest = 0; + + newInput->setFocus(); + newInput->showError(); + newInput->selectAll(); + error->show(); + + if (MTP::IsFloodError(type)) { + error->setText(tr::lng_flood_error(tr::now)); + } else if (type == u"EMAIL_NOT_ALLOWED"_q) { + error->setText( + tr::lng_settings_error_email_not_alowed(tr::now)); + } else if (type == u"EMAIL_INVALID"_q) { + error->setText(tr::lng_cloud_password_bad_email(tr::now)); + } else if (type == u"EMAIL_HASH_EXPIRED"_q) { + // Show box? + error->setText(Lang::Hard::EmailConfirmationExpired()); + } else { + error->setText(Lang::Hard::ServerError()); + } + }; + + _sentRequest = api().request(MTPaccount_SendVerifyEmailCode( + MTP_emailVerifyPurposeLoginSetup( + MTP_string(getData()->phone), + MTP_bytes(getData()->phoneHash)), + MTP_string(email) + )).done([=](const MTPaccount_SentEmailCode &result) { + done( + result.data().vlength().v, + qs(result.data().vemail_pattern())); + }).fail([=](const MTP::Error &error) { + fail(error.type()); + }).send(); + }; + const auto newText = newInput->getLastText(); + if (newText.isEmpty()) { + newInput->setFocus(); + newInput->showError(); + } else { + send(newText); + } + }; +} + +void EmailWidget::submit() { + if (_submitCallback) { + _submitCallback(); + } +} + +void EmailWidget::setInnerFocus() { + _setFocus.fire({}); +} + +void EmailWidget::activate() { + Step::activate(); + showChildren(); + setInnerFocus(); + _showFinished.fire({}); +} + +void EmailWidget::finished() { + Step::finished(); + cancelled(); +} + +void EmailWidget::cancelled() { + api().request(base::take(_sentRequest)).cancel(); +} + +} // namespace details +} // namespace Intro diff --git a/Telegram/SourceFiles/intro/intro_email.h b/Telegram/SourceFiles/intro/intro_email.h new file mode 100644 index 0000000000..aa21da3ab6 --- /dev/null +++ b/Telegram/SourceFiles/intro/intro_email.h @@ -0,0 +1,50 @@ +/* +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 "intro/intro_step.h" + +namespace Ui { +class VerticalLayout; +} // namespace Ui + +namespace MTP { +class Sender; +} // namespace MTP + +namespace Intro { +namespace details { + +class EmailWidget final : public Step { +public: + EmailWidget( + QWidget *parent, + not_null account, + not_null data); + + void setInnerFocus() override; + void activate() override; + void finished() override; + void cancelled() override; + void submit() override; + + bool hasBack() const override { + return true; + } + +private: + object_ptr _inner; + Fn _submitCallback = nullptr; + rpl::event_stream<> _showFinished; + rpl::event_stream<> _setFocus; + mtpRequestId _sentRequest = 0; + +}; + +} // namespace details +} // namespace Intro diff --git a/Telegram/SourceFiles/intro/intro_phone.cpp b/Telegram/SourceFiles/intro/intro_phone.cpp index 261c533112..21299c41e9 100644 --- a/Telegram/SourceFiles/intro/intro_phone.cpp +++ b/Telegram/SourceFiles/intro/intro_phone.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "intro/intro_code.h" +#include "intro/intro_email.h" #include "intro/intro_qr.h" #include "styles/style_intro.h" #include "ui/widgets/buttons.h" @@ -237,6 +238,9 @@ void PhoneWidget::phoneSubmitDone(const MTPauth_SentCode &result) { fillSentCodeData(data); getData()->phone = DigitsOnly(_sentPhone); getData()->phoneHash = qba(data.vphone_code_hash()); + if (getData()->emailStatus == EmailStatus::SetupRequired) { + return goNext(); + } const auto next = data.vnext_type(); if (next && next->type() == mtpc_auth_codeTypeCall) { getData()->callStatus = CallStatus::Waiting; diff --git a/Telegram/SourceFiles/intro/intro_step.cpp b/Telegram/SourceFiles/intro/intro_step.cpp index 969aecbb01..977f0bd758 100644 --- a/Telegram/SourceFiles/intro/intro_step.cpp +++ b/Telegram/SourceFiles/intro/intro_step.cpp @@ -375,7 +375,7 @@ void Step::fillSentCodeData(const MTPDauth_sentCode &data) { }, [&](const MTPDauth_sentCodeTypeSmsPhrase &) { bad("SmsPhrase"); }, [&](const MTPDauth_sentCodeTypeSetUpEmailRequired &) { - bad("SetUpEmailRequired"); + getData()->emailStatus = EmailStatus::SetupRequired; }); } diff --git a/Telegram/SourceFiles/intro/intro_widget.h b/Telegram/SourceFiles/intro/intro_widget.h index 8b804c623c..6227beef9e 100644 --- a/Telegram/SourceFiles/intro/intro_widget.h +++ b/Telegram/SourceFiles/intro/intro_widget.h @@ -43,6 +43,11 @@ enum class CallStatus { Disabled, }; +enum class EmailStatus { + None, + SetupRequired, +}; + struct Data { // Required for the UserpicButton. const not_null controller; @@ -58,6 +63,10 @@ struct Data { bool codeByTelegram = false; QString codeByFragmentUrl; + EmailStatus emailStatus = EmailStatus::None; + QString email; + QString emailPattern; + Core::CloudPasswordState pwdState; Window::TermsLock termsLock;