Added support of pending suggestion to set up login email.

This commit is contained in:
23rd
2025-11-04 01:27:26 +03:00
committed by John Preston
parent 4cdd793e0c
commit f832e31c7b
17 changed files with 987 additions and 19 deletions

View File

@@ -360,6 +360,18 @@ chatSwitchNameLabel: FlatLabel(defaultFlatLabel) {
chatSwitchNameSkip: 6px;
chatSwitchSelectLine: 3px;
lockSetupEmailLabelMaxWidth: 256px + 24px + 24px;
lockSetupEmailTitle: FlatLabel(defaultFlatLabel) {
minWidth: lockSetupEmailLabelMaxWidth;
style: TextStyle(defaultTextStyle) {
font: font(19px);
}
}
lockSetupEmailLabel: FlatLabel(defaultFlatLabel) {
minWidth: 256px;
}
lockSetupEmailLogOutPosition: point(10px, 10px);
// Windows specific
winQuitIcon: icon {{ "win_quit", windowFg }};

View File

@@ -28,6 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_session_controller.h"
#include "window/themes/window_theme_editor.h"
#include "ui/boxes/confirm_box.h"
#include "data/components/promo_suggestions.h"
#include "data/data_thread.h"
#include "apiwrap.h" // ApiWrap::acceptTerms.
#include "styles/style_layers.h"
@@ -346,7 +347,7 @@ auto Controller::sessionControllerChanges() const
}
bool Controller::locked() const {
if (Core::App().passcodeLocked()) {
if (Core::App().passcodeLocked() || Core::App().setupEmailLocked()) {
return true;
} else if (const auto controller = sessionController()) {
return controller->session().termsLocked().has_value();
@@ -374,6 +375,19 @@ void Controller::clearPasscodeLock() {
}
}
void Controller::setupSetupEmailLock() {
if (const auto &session = _sessionController) {
if (session->session().promoSuggestions().setupEmailState()
!= Data::SetupEmailState::None) {
_widget.setupSetupEmailLock();
}
}
}
void Controller::clearSetupEmailLock() {
_widget.clearSetupEmailLock();
}
void Controller::setupIntro(QPixmap oldContentCache) {
const auto point = Core::App().domain().maybeLastOrSomeAuthedAccount()
? Intro::EnterPoint::Qr

View File

@@ -68,6 +68,8 @@ public:
void setupPasscodeLock();
void clearPasscodeLock();
void setupSetupEmailLock();
void clearSetupEmailLock();
void showLogoutConfirmation();

View File

@@ -0,0 +1,607 @@
/*
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 "window/window_setup_email.h"
#include "api/api_cloud_password.h"
#include "apiwrap.h"
#include "base/call_delayed.h"
#include "base/event_filter.h"
#include "core/application.h"
#include "data/components/promo_suggestions.h"
#include "data/data_session.h"
#include "intro/intro_code_input.h"
#include "lang/lang_keys.h"
#include "lottie/lottie_icon.h"
#include "main/main_session.h"
#include "settings/settings_common.h"
#include "ui/painter.h"
#include "ui/ui_utility.h"
#include "ui/vertical_list.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/sent_code_field.h"
#include "ui/wrap/vertical_layout.h"
#include "window/window_controller.h"
#include "window/window_slide_animation.h"
#include "styles/style_boxes.h"
#include "styles/style_info.h"
#include "styles/style_intro.h"
#include "styles/style_layers.h"
#include "styles/style_settings.h"
#include "styles/style_window.h"
namespace Window {
namespace {
[[nodiscard]] Settings::LottieIcon CreateEmailIcon(
not_null<Ui::VerticalLayout*> container) {
return Settings::CreateLottieIcon(
container,
{
.name = u"cloud_password/email"_q,
.sizeOverride = st::normalBoxLottieSize,
},
{});
}
} // namespace
SetupEmailLockWidget::SetupEmailLockWidget(
QWidget *parent,
not_null<Controller*> window)
: LockWidget(parent, window)
, _layout(this)
, _confirmWidget(nullptr) {
const auto session = window->maybeSession();
const auto state = session
? session->promoSuggestions().setupEmailState()
: Data::SetupEmailState::Setup;
const auto noSkip = state == Data::SetupEmailState::SetupNoSkip;
if (state == Data::SetupEmailState::SettingUp
|| state == Data::SetupEmailState::SettingUpNoSkip) {
auto icon = CreateEmailIcon(_layout);
_iconAnimate = std::move(icon.animate);
_layout->add(std::move(icon.widget));
base::call_delayed(st::slideDuration, crl::guard(this, [=] {
if (_iconAnimate) {
_iconAnimate(anim::repeat::once);
}
}));
Ui::AddSkip(_layout);
Ui::AddSkip(_layout);
Ui::AddSkip(_layout);
_layout->add(
object_ptr<Ui::FlatLabel>(
_layout,
tr::lng_settings_cloud_login_email_title(),
st::lockSetupEmailTitle),
st::boxRowPadding,
style::al_top);
Ui::AddSkip(_layout);
Ui::AddSkip(_layout);
_layout->add(
object_ptr<Ui::FlatLabel>(
_layout,
tr::lng_settings_cloud_login_email_busy(),
st::lockSetupEmailLabel),
st::boxRowPadding,
style::al_top);
} else {
if (!noSkip) {
_backButton = object_ptr<Ui::IconButton>(
this,
st::introBackButton);
if (session) {
session->promoSuggestions().setSetupEmailState(
Data::SetupEmailState::SettingUp);
_backButton->setClickedCallback([=] {
session->promoSuggestions().dismissSetupEmail([=] {
});
});
}
} else {
_logoutButton = object_ptr<Ui::RoundButton>(
this,
tr::lng_settings_logout(),
st::defaultBoxButton);
_logoutButton->setTextTransform(
Ui::RoundButton::TextTransform::NoTransform);
if (session) {
session->promoSuggestions().setSetupEmailState(
Data::SetupEmailState::SettingUpNoSkip);
_logoutButton->setClickedCallback([=] {
window->showLogoutConfirmation();
});
}
}
#ifdef _DEBUG
if (session->isTestMode()) {
_debugButton = object_ptr<Ui::RoundButton>(
this,
rpl::single(u"[DEBUG] Clear bio"_q),
st::defaultBoxButton);
_debugButton->setTextTransform(
Ui::RoundButton::TextTransform::NoTransform);
_debugButton->setClickedCallback([=] {
session->api().saveSelfBio({});
});
}
#endif
auto icon = CreateEmailIcon(_layout);
_iconAnimate = std::move(icon.animate);
_layout->add(std::move(icon.widget));
base::call_delayed(st::slideDuration, crl::guard(this, [=] {
if (_iconAnimate) {
_iconAnimate(anim::repeat::once);
}
}));
Ui::AddSkip(_layout);
Ui::AddSkip(_layout);
Ui::AddSkip(_layout);
_layout->add(
object_ptr<Ui::FlatLabel>(
_layout,
tr::lng_settings_cloud_login_email_title(),
st::lockSetupEmailTitle),
st::boxRowPadding,
style::al_top);
Ui::AddSkip(_layout);
Ui::AddSkip(_layout);
_layout->add(
object_ptr<Ui::FlatLabel>(
_layout,
tr::lng_settings_cloud_login_email_about(),
st::lockSetupEmailLabel),
st::boxRowPadding,
style::al_top);
Ui::AddSkip(_layout);
Ui::AddSkip(_layout);
Ui::AddSkip(_layout);
Ui::AddSkip(_layout);
auto emailInput = _layout->add(
object_ptr<Ui::InputField>(
_layout,
st::settingLocalPasscodeInputField,
tr::lng_settings_cloud_login_email_placeholder()),
st::boxRowPadding,
style::al_top);
Ui::AddSkip(_layout);
Ui::AddSkip(_layout);
auto errorLabel = _layout->add(
object_ptr<Ui::FlatLabel>(
_layout,
QString(),
st::lockSetupEmailLabel),
{},
style::al_top);
errorLabel->setTextColorOverride(st::boxTextFgError->c);
errorLabel->hide();
Ui::AddSkip(_layout);
Ui::AddSkip(_layout);
Ui::AddSkip(_layout);
Ui::AddSkip(_layout);
auto submit = _layout->add(
object_ptr<Ui::RoundButton>(
_layout,
tr::lng_settings_cloud_login_email_confirm(),
st::changePhoneButton),
st::boxRowPadding,
style::al_top);
submit->setTextTransform(
Ui::RoundButton::TextTransform::NoTransform);
_emailInput = emailInput;
_errorLabel = errorLabel;
_submit = submit;
submit->setClickedCallback([=] { this->submit(); });
emailInput->changes() | rpl::start_with_next([=] {
_error = QString();
errorLabel->hide();
}, emailInput->lifetime());
emailInput->submits() | rpl::start_with_next([=] {
this->submit();
}, emailInput->lifetime());
}
}
void SetupEmailLockWidget::paintContent(QPainter &p) {
if (_backAnimation) {
_backAnimation->paintContents(p);
return;
}
LockWidget::paintContent(p);
}
void SetupEmailLockWidget::submit() {
if (!_emailInput) {
return;
}
const auto email = _emailInput->getLastText().trimmed();
if (email.isEmpty()) {
_emailInput->setFocus();
_emailInput->showError();
return;
}
if (!email.contains('@') || !email.contains('.')) {
_error = tr::lng_cloud_password_bad_email(tr::now);
_errorLabel->setText(_error);
_errorLabel->show();
_emailInput->setFocus();
_emailInput->showError();
_emailInput->selectAll();
return;
}
const auto session = window()->maybeSession();
if (!session) {
showConfirmWidget(email, 6, QString());
return;
}
_api.emplace(&session->mtp());
const auto done = crl::guard(this, [=](
int length,
const QString &pattern) {
_api.reset();
showConfirmWidget(email, length, pattern);
});
const auto fail = crl::guard(this, [=](const QString &type) {
_api.reset();
if (MTP::IsFloodError(type)) {
_error = tr::lng_flood_error(tr::now);
} else if (type == u"EMAIL_INVALID"_q) {
_error = tr::lng_cloud_password_bad_email(tr::now);
} else {
_error = tr::lng_cloud_password_bad_email(tr::now);
}
_errorLabel->setText(_error);
_errorLabel->show();
_emailInput->setFocus();
_emailInput->showError();
_emailInput->selectAll();
});
Api::RequestLoginEmailCode(*_api, email, done, fail);
}
void SetupEmailLockWidget::showConfirmWidget(
const QString &email,
int codeLength,
const QString &emailPattern) {
_layout->hide();
auto oldContentCache = grabContent();
_confirmWidget = Ui::CreateChild<SetupEmailConfirmWidget>(
this,
window(),
email,
codeLength,
emailPattern);
_confirmWidget->resize(size());
_confirmWidget->show();
_confirmWidget->showAnimated(std::move(oldContentCache));
_confirmWidget->setFocus();
_confirmWidget->backRequests(
) | rpl::start_with_next([=] {
showEmailInput();
}, _confirmWidget->lifetime());
_confirmWidget->confirmations(
) | rpl::start_with_next([=, controller = window()] {
if (const auto session = controller->maybeSession()) {
session->promoSuggestions().setSetupEmailState(
Data::SetupEmailState::None);
}
}, _confirmWidget->lifetime());
}
void SetupEmailLockWidget::showEmailInput() {
if (!_confirmWidget) {
return;
}
auto oldContentCache = Ui::GrabWidget(_confirmWidget);
_confirmWidget->hide();
_confirmWidget->deleteLater();
_confirmWidget = nullptr;
if (_logoutButton) {
_logoutButton->show();
}
_layout->show();
auto newContentCache = grabContent();
if (_logoutButton) {
_logoutButton->hide();
}
_layout->hide();
_backAnimation = std::make_unique<SlideAnimation>();
_backAnimation->setDirection(SlideDirection::FromLeft);
_backAnimation->setRepaintCallback([=] { update(); });
_backAnimation->setFinishedCallback([=] {
_layout->show();
if (_logoutButton) {
_logoutButton->show();
}
_backAnimation.reset();
if (_iconAnimate) {
_iconAnimate(anim::repeat::once);
}
});
_backAnimation->setPixmaps(
std::move(oldContentCache),
std::move(newContentCache));
_backAnimation->start();
if (_emailInput) {
_emailInput->setFocus();
}
}
QPixmap SetupEmailLockWidget::grabContent() {
return Ui::GrabWidget(this);
}
void SetupEmailLockWidget::resizeEvent(QResizeEvent *e) {
const auto layoutWidth = std::min(
width(),
st::lockSetupEmailLabelMaxWidth);
_layout->resizeToWidth(layoutWidth);
_layout->moveToLeft(
(width() - layoutWidth) / 2,
st::infoLayerTopBarHeight);
if (_backButton) {
_backButton->moveToLeft(0, 0);
}
if (_logoutButton) {
_logoutButton->move(st::lockSetupEmailLogOutPosition);
}
if (_debugButton) {
_debugButton->moveToLeft(
st::lockSetupEmailLogOutPosition.x(),
height() - _debugButton->height()
- st::lockSetupEmailLogOutPosition.y());
}
if (_confirmWidget) {
_confirmWidget->resize(size());
}
}
void SetupEmailLockWidget::setInnerFocus() {
LockWidget::setInnerFocus();
if (_confirmWidget && _confirmWidget->isVisible()) {
_confirmWidget->setFocus();
} else if (_emailInput) {
_emailInput->setFocus();
}
}
SetupEmailConfirmWidget::SetupEmailConfirmWidget(
QWidget *parent,
not_null<Controller*> window,
const QString &email,
int codeLength,
const QString &emailPattern)
: Ui::RpWidget(parent)
, _window(window)
, _email(email)
, _emailPattern(emailPattern)
, _backButton(this, st::introBackButton)
, _layout(this) {
auto icon = CreateEmailIcon(_layout);
_iconAnimate = std::move(icon.animate);
_layout->add(std::move(icon.widget));
Ui::AddSkip(_layout);
Ui::AddSkip(_layout);
Ui::AddSkip(_layout);
_layout->add(
object_ptr<Ui::FlatLabel>(
_layout,
tr::lng_settings_cloud_login_email_code_title(),
st::lockSetupEmailTitle),
st::boxRowPadding,
style::al_top);
Ui::AddSkip(_layout);
Ui::AddSkip(_layout);
_layout->add(
object_ptr<Ui::FlatLabel>(
_layout,
tr::lng_settings_cloud_login_email_code_about(
tr::now,
lt_email,
_emailPattern.isEmpty() ? _email : _emailPattern),
st::lockSetupEmailLabel),
st::boxRowPadding,
style::al_top);
Ui::AddSkip(_layout);
Ui::AddSkip(_layout);
Ui::AddSkip(_layout);
Ui::AddSkip(_layout);
auto codeInput = _layout->add(
object_ptr<Ui::CodeInput>(_layout),
style::al_top);
codeInput->setDigitsCountMax(codeLength > 0 ? codeLength : 6);
Ui::AddSkip(_layout);
Ui::AddSkip(_layout);
auto errorLabel = _layout->add(
object_ptr<Ui::FlatLabel>(
_layout,
QString(),
st::lockSetupEmailLabel),
st::boxRowPadding,
style::al_top);
errorLabel->setTextColorOverride(st::boxTextFgError->c);
errorLabel->hide();
_codeInput = codeInput;
_errorLabel = errorLabel;
_backButton->clicks(
) | rpl::to_empty | rpl::start_to_stream(_backRequests, lifetime());
base::install_event_filter(codeInput, [=](not_null<QEvent*> e) {
if (e->type() == QEvent::KeyPress) {
const auto k = static_cast<QKeyEvent*>(e.get());
if (k->key() == Qt::Key_Escape) {
_backRequests.fire({});
return base::EventFilterResult::Cancel;
}
}
return base::EventFilterResult::Continue;
});
codeInput->codeCollected(
) | rpl::start_with_next([=](const QString &code) {
verifyCode(code);
}, codeInput->lifetime());
}
rpl::producer<> SetupEmailConfirmWidget::backRequests() const {
return _backRequests.events();
}
rpl::producer<> SetupEmailConfirmWidget::confirmations() const {
return _confirmations.events();
}
void SetupEmailConfirmWidget::paintEvent(QPaintEvent *e) {
auto p = Painter(this);
if (_showAnimation) {
_showAnimation->paintContents(p);
return;
}
p.fillRect(rect(), st::windowBg);
}
void SetupEmailConfirmWidget::showAnimated(QPixmap oldContentCache) {
_showAnimation = nullptr;
showChildren();
auto newContentCache = Ui::GrabWidget(this);
hideChildren();
_showAnimation = std::make_unique<SlideAnimation>();
_showAnimation->setDirection(SlideDirection::FromRight);
_showAnimation->setRepaintCallback([=] { update(); });
_showAnimation->setFinishedCallback([=] {
showChildren();
_showAnimation.reset();
if (_iconAnimate) {
_iconAnimate(anim::repeat::once);
}
if (_codeInput) {
_codeInput->setFocus();
}
});
_showAnimation->setPixmaps(
std::move(oldContentCache),
std::move(newContentCache));
_showAnimation->start();
}
void SetupEmailConfirmWidget::resizeEvent(QResizeEvent *e) {
const auto layoutWidth = std::min(
width(),
st::lockSetupEmailLabelMaxWidth);
_layout->resizeToWidth(layoutWidth);
_layout->move((width() - layoutWidth) / 2, st::infoLayerTopBarHeight);
_backButton->move(0, 0);
}
void SetupEmailConfirmWidget::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Escape) {
_backRequests.fire({});
return;
}
Ui::RpWidget::keyPressEvent(e);
}
void SetupEmailConfirmWidget::verifyCode(const QString &code) {
const auto session = _window->maybeSession();
if (!session) {
_confirmations.fire({});
return;
}
_api.emplace(&session->mtp());
const auto done = crl::guard(this, [=] {
#ifdef _DEBUG
if (session->isTestMode()) {
session->api().saveSelfBio({});
}
#endif
_window->uiShow()->showToast(
tr::lng_settings_cloud_login_email_set_success(tr::now));
_api.reset();
_confirmations.fire({});
});
const auto fail = crl::guard(this, [=](const QString &type) {
_api.reset();
if (type.isEmpty()) {
_confirmations.fire({});
return;
}
if (MTP::IsFloodError(type)) {
_error = tr::lng_flood_error(tr::now);
} else if (type == u"EMAIL_NOT_ALLOWED"_q) {
_error = tr::lng_settings_error_email_not_alowed(tr::now);
} else if (type == u"CODE_INVALID"_q) {
_error = tr::lng_signin_wrong_code(tr::now);
} else if (type == u"CODE_EXPIRED"_q
|| type == u"EMAIL_HASH_EXPIRED"_q) {
_error = Lang::Hard::EmailConfirmationExpired();
} else {
_error = Lang::Hard::ServerError();
}
_errorLabel->setText(_error);
_errorLabel->show();
_codeInput->setFocus();
_codeInput->showError();
});
Api::VerifyLoginEmail(*_api, code, done, fail);
}
} // namespace Window

View File

@@ -0,0 +1,104 @@
/*
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 "mtproto/sender.h"
#include "window/window_lock_widgets.h"
namespace Ui {
class InputField;
class CodeInput;
class RoundButton;
class IconButton;
class FlatLabel;
class VerticalLayout;
} // namespace Ui
namespace Window {
class SlideAnimation;
class SetupEmailConfirmWidget : public Ui::RpWidget {
public:
SetupEmailConfirmWidget(
QWidget *parent,
not_null<Controller*> window,
const QString &email,
int codeLength,
const QString &emailPattern);
rpl::producer<> backRequests() const;
rpl::producer<> confirmations() const;
void showAnimated(QPixmap oldContentCache);
protected:
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
void keyPressEvent(QKeyEvent *e) override;
private:
void verifyCode(const QString &code);
not_null<Controller*> _window;
QString _email;
QString _emailPattern;
QString _error;
rpl::lifetime _requestLifetime;
std::optional<::MTP::Sender> _api;
object_ptr<Ui::IconButton> _backButton;
object_ptr<Ui::VerticalLayout> _layout;
Ui::CodeInput *_codeInput = nullptr;
Ui::FlatLabel *_errorLabel = nullptr;
rpl::event_stream<> _backRequests;
rpl::event_stream<> _confirmations;
std::unique_ptr<SlideAnimation> _showAnimation;
Fn<void(anim::repeat)> _iconAnimate;
};
class SetupEmailLockWidget : public LockWidget {
public:
SetupEmailLockWidget(QWidget *parent, not_null<Controller*> window);
void setInnerFocus() override;
protected:
void resizeEvent(QResizeEvent *e) override;
private:
void paintContent(QPainter &p) override;
void submit();
void showConfirmWidget(
const QString &email,
int codeLength,
const QString &emailPattern);
void showEmailInput();
QPixmap grabContent();
object_ptr<Ui::VerticalLayout> _layout;
SetupEmailConfirmWidget *_confirmWidget;
rpl::lifetime _requestLifetime;
std::optional<::MTP::Sender> _api;
object_ptr<Ui::IconButton> _backButton = { nullptr };
object_ptr<Ui::RoundButton> _logoutButton = { nullptr };
object_ptr<Ui::RoundButton> _debugButton = { nullptr };
Ui::InputField *_emailInput = nullptr;
Ui::RoundButton *_submit = nullptr;
Ui::FlatLabel *_errorLabel = nullptr;
QString _error;
std::unique_ptr<SlideAnimation> _backAnimation;
Fn<void(anim::repeat)> _iconAnimate;
};
} // namespace Window