init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Close stale issues and PRs / stale (push) Successful in 13s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s

This commit is contained in:
allhaileris
2026-02-16 15:50:16 +03:00
commit afb81b8278
13816 changed files with 3689732 additions and 0 deletions

View File

@@ -0,0 +1,252 @@
/*
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/cloud_password/settings_cloud_password_common.h"
#include "lang/lang_keys.h"
#include "lottie/lottie_icon.h"
#include "settings/settings_common.h"
#include "ui/vertical_list.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/widgets/fields/password_input.h"
#include "ui/widgets/labels.h"
#include "ui/wrap/vertical_layout.h"
#include "styles/style_boxes.h"
#include "styles/style_settings.h"
namespace Settings::CloudPassword {
void OneEdgeBoxContentDivider::skipEdge(Qt::Edge edge, bool skip) {
const auto was = _skipEdges;
if (skip) {
_skipEdges |= edge;
} else {
_skipEdges &= ~edge;
}
if (was != _skipEdges) {
update();
}
}
void OneEdgeBoxContentDivider::paintEvent(QPaintEvent *e) {
auto p = QPainter(this);
p.fillRect(e->rect(), Ui::BoxContentDivider::color());
if (!(_skipEdges & Qt::TopEdge)) {
Ui::BoxContentDivider::paintTop(p);
}
if (!(_skipEdges & Qt::BottomEdge)) {
Ui::BoxContentDivider::paintBottom(p);
}
}
BottomButton CreateBottomDisableButton(
not_null<Ui::RpWidget*> parent,
rpl::producer<QRect> &&sectionGeometryValue,
rpl::producer<QString> &&buttonText,
Fn<void()> &&callback) {
const auto content = Ui::CreateChild<Ui::VerticalLayout>(parent.get());
Ui::AddSkip(content);
content->add(object_ptr<Button>(
content,
std::move(buttonText),
st::settingsAttentionButton
))->addClickHandler(std::move(callback));
const auto divider = Ui::CreateChild<OneEdgeBoxContentDivider>(
parent.get());
divider->skipEdge(Qt::TopEdge, true);
rpl::combine(
std::move(sectionGeometryValue),
parent->geometryValue(),
content->geometryValue()
) | rpl::on_next([=](
const QRect &r,
const QRect &parentRect,
const QRect &bottomRect) {
const auto top = r.y() + r.height();
divider->setGeometry(
0,
top,
r.width(),
parentRect.height() - top - bottomRect.height());
}, divider->lifetime());
divider->show();
return {
.content = base::make_weak(content),
.isBottomFillerShown = divider->geometryValue(
) | rpl::map([](const QRect &r) {
return r.height() > 0;
}),
};
}
void SetupAutoCloseTimer(
rpl::lifetime &lifetime,
Fn<void()> callback,
Fn<crl::time()> lastNonIdleTime) {
constexpr auto kTimerCheck = crl::time(1000 * 60);
constexpr auto kAutoCloseTimeout = crl::time(1000 * 60 * 10);
const auto timer = lifetime.make_state<base::Timer>([=] {
const auto idle = crl::now() - lastNonIdleTime();
if (idle >= kAutoCloseTimeout) {
callback();
}
});
timer->callEach(kTimerCheck);
}
void SetupHeader(
not_null<Ui::VerticalLayout*> content,
const QString &lottie,
rpl::producer<> &&showFinished,
rpl::producer<QString> &&subtitle,
v::text::data &&about) {
if (!lottie.isEmpty()) {
const auto &size = st::settingsCloudPasswordIconSize;
auto icon = CreateLottieIcon(
content,
{ .name = lottie, .sizeOverride = { size, size } },
st::settingLocalPasscodeIconPadding);
content->add(std::move(icon.widget));
std::move(
showFinished
) | rpl::on_next([animate = std::move(icon.animate)] {
animate(anim::repeat::once);
}, content->lifetime());
}
Ui::AddSkip(content);
content->add(
object_ptr<Ui::FlatLabel>(
content,
std::move(subtitle),
st::changePhoneTitle),
st::changePhoneTitlePadding,
style::al_top);
{
const auto &st = st::settingLocalPasscodeDescription;
const auto description = content->add(
object_ptr<Ui::FlatLabel>(
content,
v::text::take_marked(std::move(about)),
st,
st::defaultPopupMenu),
st::changePhoneDescriptionPadding,
style::al_top);
description->setTryMakeSimilarLines(true);
description->setAttribute(Qt::WA_TransparentForMouseEvents);
}
}
not_null<Ui::PasswordInput*> AddPasswordField(
not_null<Ui::VerticalLayout*> content,
rpl::producer<QString> &&placeholder,
const QString &text) {
const auto &st = st::settingLocalPasscodeInputField;
auto container = object_ptr<Ui::RpWidget>(content);
container->resize(container->width(), st.heightMin);
const auto field = Ui::CreateChild<Ui::PasswordInput>(
container.data(),
st,
std::move(placeholder),
text);
container->geometryValue(
) | rpl::on_next([=](const QRect &r) {
field->moveToLeft((r.width() - field->width()) / 2, 0);
}, container->lifetime());
content->add(std::move(container));
return field;
}
not_null<Ui::InputField*> AddWrappedField(
not_null<Ui::VerticalLayout*> content,
rpl::producer<QString> &&placeholder,
const QString &text) {
return content->add(
object_ptr<Ui::InputField>(
content,
st::settingLocalPasscodeInputField,
std::move(placeholder),
text),
style::al_top);
}
not_null<Ui::LinkButton*> AddLinkButton(
not_null<Ui::InputField*> input,
rpl::producer<QString> &&text) {
const auto button = Ui::CreateChild<Ui::LinkButton>(
input->parentWidget(),
QString());
std::move(
text
) | rpl::on_next([=](const QString &text) {
button->setText(text);
}, button->lifetime());
input->geometryValue(
) | rpl::on_next([=](QRect r) {
button->moveToLeft(r.x(), r.y() + r.height() + st::passcodeTextLine);
}, button->lifetime());
return button;
}
not_null<Ui::FlatLabel*> AddError(
not_null<Ui::VerticalLayout*> content,
Ui::PasswordInput *input) {
const auto error = content->add(
object_ptr<Ui::FlatLabel>(
content,
QString(),
st::settingLocalPasscodeError),
st::changePhoneDescriptionPadding,
style::al_top);
error->hide();
if (input) {
QObject::connect(input, &Ui::MaskedInputField::changed, [=] {
error->hide();
});
}
return error;
};
not_null<Ui::RoundButton*> AddDoneButton(
not_null<Ui::VerticalLayout*> content,
rpl::producer<QString> &&text) {
const auto button = content->add(
object_ptr<Ui::RoundButton>(
content,
std::move(text),
st::changePhoneButton),
st::settingLocalPasscodeButtonPadding,
style::al_top);
button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
return button;
}
void AddSkipInsteadOfField(not_null<Ui::VerticalLayout*> content) {
Ui::AddSkip(content, st::settingLocalPasscodeInputField.heightMin);
}
void AddSkipInsteadOfError(not_null<Ui::VerticalLayout*> content) {
auto dummy = base::make_unique_q<Ui::FlatLabel>(
content,
tr::lng_language_name(tr::now),
st::settingLocalPasscodeError);
const auto &padding = st::changePhoneDescriptionPadding;
Ui::AddSkip(content, dummy->height() + padding.top() + padding.bottom());
dummy = nullptr;
}
} // namespace Settings::CloudPassword

View File

@@ -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 "ui/text/text_variant.h"
#include "ui/widgets/box_content_divider.h"
namespace Ui {
class FlatLabel;
class InputField;
class LinkButton;
class PasswordInput;
class RoundButton;
class VerticalLayout;
} // namespace Ui
namespace Api {
class CloudPassword;
} // namespace Api
namespace Settings::CloudPassword {
struct StepData {
QString currentPassword;
QString password;
QString hint;
QString email;
int unconfirmedEmailLengthCode;
bool setOnlyRecoveryEmail = false;
bool suggestionValidate = false;
struct ProcessRecover {
bool setNewPassword = false;
QString checkedCode;
QString emailPattern;
};
ProcessRecover processRecover;
};
void SetupAutoCloseTimer(
rpl::lifetime &lifetime,
Fn<void()> callback,
Fn<crl::time()> lastNonIdleTime);
void SetupHeader(
not_null<Ui::VerticalLayout*> content,
const QString &lottie,
rpl::producer<> &&showFinished,
rpl::producer<QString> &&subtitle,
v::text::data &&about);
[[nodiscard]] not_null<Ui::PasswordInput*> AddPasswordField(
not_null<Ui::VerticalLayout*> content,
rpl::producer<QString> &&placeholder,
const QString &text);
[[nodiscard]] not_null<Ui::InputField*> AddWrappedField(
not_null<Ui::VerticalLayout*> content,
rpl::producer<QString> &&placeholder,
const QString &text);
[[nodiscard]] not_null<Ui::FlatLabel*> AddError(
not_null<Ui::VerticalLayout*> content,
Ui::PasswordInput *input);
[[nodiscard]] not_null<Ui::RoundButton*> AddDoneButton(
not_null<Ui::VerticalLayout*> content,
rpl::producer<QString> &&text);
[[nodiscard]] not_null<Ui::LinkButton*> AddLinkButton(
not_null<Ui::InputField*> input,
rpl::producer<QString> &&text);
void AddSkipInsteadOfField(not_null<Ui::VerticalLayout*> content);
void AddSkipInsteadOfError(not_null<Ui::VerticalLayout*> content);
struct BottomButton {
base::weak_qptr<Ui::RpWidget> content;
rpl::producer<bool> isBottomFillerShown;
};
BottomButton CreateBottomDisableButton(
not_null<Ui::RpWidget*> parent,
rpl::producer<QRect> &&sectionGeometryValue,
rpl::producer<QString> &&buttonText,
Fn<void()> &&callback);
class OneEdgeBoxContentDivider : public Ui::BoxContentDivider {
public:
using Ui::BoxContentDivider::BoxContentDivider;
void skipEdge(Qt::Edge edge, bool skip);
protected:
void paintEvent(QPaintEvent *e) override;
private:
Qt::Edges _skipEdges;
};
} // namespace Settings::CloudPassword

View File

@@ -0,0 +1,205 @@
/*
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/cloud_password/settings_cloud_password_email.h"
#include "api/api_cloud_password.h"
#include "core/core_cloud_password.h"
#include "lang/lang_keys.h"
#include "settings/cloud_password/settings_cloud_password_common.h"
#include "settings/cloud_password/settings_cloud_password_email_confirm.h"
#include "settings/cloud_password/settings_cloud_password_manage.h"
#include "settings/cloud_password/settings_cloud_password_step.h"
#include "ui/vertical_list.h"
#include "ui/boxes/confirm_box.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/wrap/vertical_layout.h"
#include "window/window_session_controller.h"
#include "styles/style_layers.h"
#include "styles/style_settings.h"
/*
Available actions for follow states.
CreateEmail:
Continue to EmailConfirm.
+ Continue to Manage if Email is confirmed already.
Warn and Skip to Manage.
Back to CreateHint.
ChangeEmail from ChangePassword:
Continue to EmailConfirm.
+ Continue to Manage if Email is confirmed already.
Warn and Skip to Manage.
Back to ChangeHint.
ChangeEmail from Manage:
Continue to EmailConfirm.
+ Continue to Manage if Email is confirmed already.
Back to Manage.
*/
namespace Settings {
namespace CloudPassword {
class Email : public TypedAbstractStep<Email> {
public:
using TypedAbstractStep::TypedAbstractStep;
[[nodiscard]] rpl::producer<QString> title() override;
void setupContent();
private:
rpl::lifetime _requestLifetime;
};
rpl::producer<QString> Email::title() {
return tr::lng_settings_cloud_password_email_title();
}
void Email::setupContent() {
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
auto currentStepData = stepData();
const auto currentStepDataEmail = base::take(currentStepData.email);
const auto setOnly = base::take(currentStepData.setOnlyRecoveryEmail);
setStepData(currentStepData);
const auto state = cloudPassword().stateCurrent();
const auto hasRecovery = state && state->hasRecovery;
SetupHeader(
content,
u"cloud_password/email"_q,
showFinishes(),
hasRecovery
? tr::lng_settings_cloud_password_manage_email_change()
: tr::lng_settings_cloud_password_email_subtitle(),
tr::lng_settings_cloud_password_email_about());
Ui::AddSkip(content, st::settingLocalPasscodeDescriptionBottomSkip);
const auto newInput = AddWrappedField(
content,
tr::lng_cloud_password_email(),
currentStepDataEmail);
const auto error = AddError(content, nullptr);
newInput->changes(
) | rpl::on_next([=] {
error->hide();
}, newInput->lifetime());
AddSkipInsteadOfField(content);
const auto send = [=](Fn<void()> close) {
Expects(!_requestLifetime);
const auto data = stepData();
_requestLifetime = (setOnly
? cloudPassword().setEmail(data.currentPassword, data.email)
: cloudPassword().set(
data.currentPassword,
data.password,
data.hint,
!data.email.isEmpty(),
data.email)
) | rpl::on_next_error_done([=](Api::CloudPassword::SetOk d) {
_requestLifetime.destroy();
auto data = stepData();
data.unconfirmedEmailLengthCode = d.unconfirmedEmailLengthCode;
setStepData(std::move(data));
showOther(CloudPasswordEmailConfirmId());
}, [=](const QString &type) {
_requestLifetime.destroy();
if (MTP::IsFloodError(type)) {
error->show();
error->setText(tr::lng_flood_error(tr::now));
} else if (AbstractStep::isPasswordInvalidError(type)) {
} else if (type == u"EMAIL_INVALID"_q) {
error->show();
error->setText(tr::lng_cloud_password_bad_email(tr::now));
newInput->setFocus();
newInput->showError();
newInput->selectAll();
}
}, [=] {
_requestLifetime.destroy();
auto empty = StepData();
empty.currentPassword = stepData().password.isEmpty()
? stepData().currentPassword
: stepData().password;
setStepData(std::move(empty));
showOther(CloudPasswordManageId());
});
if (close) {
_requestLifetime.add(close);
}
};
const auto confirm = [=](const QString &email) {
if (_requestLifetime) {
return;
}
auto data = stepData();
data.email = email;
setStepData(std::move(data));
if (!email.isEmpty()) {
send(nullptr);
return;
}
controller()->show(Ui::MakeConfirmBox({
.text = { tr::lng_cloud_password_about_recover() },
.confirmed = crl::guard(this, send),
.confirmText = tr::lng_cloud_password_skip_email(),
.confirmStyle = &st::attentionBoxButton,
}));
};
const auto skip = AddLinkButton(
newInput,
tr::lng_cloud_password_skip_email());
skip->setClickedCallback([=] {
confirm(QString());
});
skip->setVisible(!setOnly);
const auto button = AddDoneButton(
content,
tr::lng_settings_cloud_password_save());
button->setClickedCallback([=] {
const auto newText = newInput->getLastText();
if (newText.isEmpty()) {
newInput->setFocus();
newInput->showError();
} else {
confirm(newText);
}
});
const auto submit = [=] { button->clicked({}, Qt::LeftButton); };
newInput->submits() | rpl::on_next(submit, newInput->lifetime());
setFocusCallback([=] { newInput->setFocus(); });
Ui::ResizeFitChild(this, content);
}
} // namespace CloudPassword
Type CloudPasswordEmailId() {
return CloudPassword::Email::Id();
}
} // namespace Settings

View File

@@ -0,0 +1,17 @@
/*
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_type.h"
namespace Settings {
Type CloudPasswordEmailId();
} // namespace Settings

View File

@@ -0,0 +1,369 @@
/*
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/cloud_password/settings_cloud_password_email_confirm.h"
#include "apiwrap.h"
#include "api/api_cloud_password.h"
#include "base/unixtime.h"
#include "core/core_cloud_password.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "settings/cloud_password/settings_cloud_password_common.h"
#include "settings/cloud_password/settings_cloud_password_email.h"
#include "settings/cloud_password/settings_cloud_password_hint.h"
#include "settings/cloud_password/settings_cloud_password_input.h"
#include "settings/cloud_password/settings_cloud_password_manage.h"
#include "settings/cloud_password/settings_cloud_password_start.h"
#include "settings/cloud_password/settings_cloud_password_step.h"
#include "ui/vertical_list.h"
#include "ui/boxes/confirm_box.h"
#include "ui/text/format_values.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/menu/menu_add_action_callback.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/sent_code_field.h"
#include "ui/wrap/padding_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "window/window_session_controller.h"
#include "styles/style_boxes.h"
#include "styles/style_layers.h"
#include "styles/style_menu_icons.h"
#include "styles/style_settings.h"
/*
Available actions for follow states.
CreateEmailConfirm from CreateEmail:
Continue to Manage.
Abort to Settings.
Back to Settings.
ChangeEmailConfirm from ChangeEmail:
Continue to Manage.
Abort to Settings.
Back to Settings.
Recover from CreatePassword:
Continue to RecreateResetPassword.
Back to Settings.
*/
namespace Settings {
namespace CloudPassword {
class EmailConfirm : public TypedAbstractStep<EmailConfirm> {
public:
using TypedAbstractStep::TypedAbstractStep;
[[nodiscard]] rpl::producer<QString> title() override;
void fillTopBarMenu(
const Ui::Menu::MenuCallback &addAction) override;
void setupContent();
protected:
[[nodiscard]] rpl::producer<std::vector<Type>> removeTypes() override;
private:
rpl::lifetime _requestLifetime;
};
rpl::producer<QString> EmailConfirm::title() {
return tr::lng_settings_cloud_password_email_title();
}
void EmailConfirm::fillTopBarMenu(
const Ui::Menu::MenuCallback &addAction) {
const auto api = &controller()->session().api();
if (const auto state = api->cloudPassword().stateCurrent()) {
if (state->unconfirmedPattern.isEmpty()) {
return;
}
}
addAction(
tr::lng_settings_password_abort(tr::now),
[=] { api->cloudPassword().clearUnconfirmedPassword(); },
&st::menuIconCancel);
}
rpl::producer<std::vector<Type>> EmailConfirm::removeTypes() {
return rpl::single(std::vector<Type>{
CloudPasswordStartId(),
CloudPasswordInputId(),
CloudPasswordHintId(),
CloudPasswordEmailId(),
CloudPasswordEmailConfirmId(),
CloudPasswordManageId(),
});
}
void EmailConfirm::setupContent() {
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
auto currentStepData = stepData();
const auto currentStepDataCodeLength = base::take(
currentStepData.unconfirmedEmailLengthCode);
// If we go back from Email Confirm to Privacy Settings
// we should forget the current password.
const auto currentPassword = base::take(currentStepData.currentPassword);
const auto typedPassword = base::take(currentStepData.password);
const auto recoverEmailPattern = base::take(
currentStepData.processRecover.emailPattern);
setStepData(currentStepData);
const auto state = cloudPassword().stateCurrent();
if (!state) {
setStepData(StepData());
showBack();
return;
}
cloudPassword().state(
) | rpl::on_next([=](const Core::CloudPasswordState &state) {
if (!_requestLifetime
&& state.unconfirmedPattern.isEmpty()
&& recoverEmailPattern.isEmpty()) {
setStepData(StepData());
showBack();
}
}, lifetime());
SetupHeader(
content,
u"cloud_password/email"_q,
showFinishes(),
state->unconfirmedPattern.isEmpty()
? tr::lng_settings_cloud_password_email_recovery_subtitle()
: tr::lng_cloud_password_confirm(),
tr::lng_cloud_password_waiting_code(
lt_email,
rpl::single(
Ui::Text::WrapEmailPattern(
state->unconfirmedPattern.isEmpty()
? recoverEmailPattern
: state->unconfirmedPattern)),
TextWithEntities::Simple));
Ui::AddSkip(content, st::settingLocalPasscodeDescriptionBottomSkip);
auto objectInput = object_ptr<Ui::SentCodeField>(
content,
st::settingLocalPasscodeInputField,
tr::lng_change_phone_code_title());
const auto newInput = content->add(
std::move(objectInput),
style::al_top);
const auto error = AddError(content, nullptr);
newInput->changes(
) | rpl::on_next([=] {
error->hide();
}, newInput->lifetime());
AddSkipInsteadOfField(content);
const auto resendInfo = Ui::CreateChild<Ui::FlatLabel>(
error->parentWidget(),
tr::lng_cloud_password_resent(tr::now),
st::changePhoneLabel);
resendInfo->hide();
error->geometryValue(
) | rpl::on_next([=](const QRect &r) {
resendInfo->setGeometry(r);
}, resendInfo->lifetime());
error->shownValue(
) | rpl::on_next([=](bool shown) {
if (shown) {
resendInfo->hide();
}
}, resendInfo->lifetime());
const auto resend = AddLinkButton(
newInput,
tr::lng_cloud_password_resend());
resend->setClickedCallback([=] {
if (_requestLifetime) {
return;
}
_requestLifetime = cloudPassword().resendEmailCode(
) | rpl::on_error_done([=](const QString &type) {
_requestLifetime.destroy();
error->show();
error->setText(Lang::Hard::ServerError());
}, [=] {
_requestLifetime.destroy();
error->hide();
resendInfo->show();
newInput->hideError();
});
});
if (!recoverEmailPattern.isEmpty()) {
resend->setText(tr::lng_signin_try_password(tr::now));
resend->setClickedCallback([=] {
const auto reset = [=](Fn<void()> close) {
if (_requestLifetime) {
return;
}
_requestLifetime = cloudPassword().resetPassword(
) | rpl::on_next_error_done([=](
Api::CloudPassword::ResetRetryDate retryDate) {
_requestLifetime.destroy();
const auto left = std::max(
retryDate - base::unixtime::now(),
60);
controller()->show(Ui::MakeInformBox(
tr::lng_cloud_password_reset_later(
tr::now,
lt_duration,
Ui::FormatResetCloudPasswordIn(left))));
}, [=](const QString &type) {
_requestLifetime.destroy();
}, [=] {
_requestLifetime.destroy();
cloudPassword().reload();
using PasswordState = Core::CloudPasswordState;
_requestLifetime = cloudPassword().state(
) | rpl::filter([=](const PasswordState &s) {
return s.pendingResetDate != 0;
}) | rpl::take(
1
) | rpl::on_next([=](const PasswordState &s) {
const auto left = (s.pendingResetDate
- base::unixtime::now());
if (left > 0) {
_requestLifetime.destroy();
controller()->show(Ui::MakeInformBox(
tr::lng_settings_cloud_password_reset_in(
tr::now,
lt_duration,
Ui::FormatResetCloudPasswordIn(left))));
setStepData(StepData());
showBack();
}
});
});
_requestLifetime.add(close);
};
controller()->show(Ui::MakeConfirmBox({
.text = tr::lng_cloud_password_reset_with_email(),
.confirmed = reset,
.confirmText = tr::lng_cloud_password_reset_ok(),
.confirmStyle = &st::attentionBoxButton,
}));
});
}
const auto button = AddDoneButton(
content,
recoverEmailPattern.isEmpty()
? tr::lng_settings_cloud_password_email_confirm()
: tr::lng_passcode_check_button());
button->setClickedCallback([=] {
const auto newText = newInput->getDigitsOnly();
if (newText.isEmpty()) {
newInput->setFocus();
newInput->showError();
} else if (!_requestLifetime && recoverEmailPattern.isEmpty()) {
_requestLifetime = cloudPassword().confirmEmail(
newText
) | rpl::on_error_done([=](const QString &type) {
_requestLifetime.destroy();
newInput->setFocus();
newInput->showError();
error->show();
if (MTP::IsFloodError(type)) {
error->setText(tr::lng_flood_error(tr::now));
} else if (type == u"CODE_INVALID"_q) {
error->setText(tr::lng_signin_wrong_code(tr::now));
} else if (type == u"EMAIL_HASH_EXPIRED"_q) {
// Show box?
error->setText(Lang::Hard::EmailConfirmationExpired());
} else {
error->setText(Lang::Hard::ServerError());
}
}, [=] {
_requestLifetime.destroy();
auto empty = StepData();
const auto anyPassword = currentPassword.isEmpty()
? typedPassword
: currentPassword;
empty.currentPassword = anyPassword;
setStepData(std::move(empty));
// If we don't have the current password
// Then we should go to Privacy Settings.
if (anyPassword.isEmpty()) {
showBack();
} else {
showOther(CloudPasswordManageId());
}
});
} else if (!_requestLifetime) {
_requestLifetime = cloudPassword().checkRecoveryEmailAddressCode(
newText
) | rpl::on_error_done([=](const QString &type) {
_requestLifetime.destroy();
newInput->setFocus();
newInput->showError();
error->show();
if (MTP::IsFloodError(type)) {
error->setText(tr::lng_flood_error(tr::now));
return;
}
if (type == u"PASSWORD_RECOVERY_NA"_q) {
setStepData(StepData());
showBack();
} else if (type == u"PASSWORD_RECOVERY_EXPIRED"_q) {
setStepData(StepData());
showBack();
} else if (type == u"CODE_INVALID"_q) {
error->setText(tr::lng_signin_wrong_code(tr::now));
} else {
error->setText(Logs::DebugEnabled()
// internal server error
? type
: Lang::Hard::ServerError());
}
}, [=] {
_requestLifetime.destroy();
auto empty = StepData();
empty.processRecover.checkedCode = newText;
empty.processRecover.setNewPassword = true;
setStepData(std::move(empty));
showOther(CloudPasswordInputId());
});
}
});
const auto submit = [=] { button->clicked({}, Qt::LeftButton); };
newInput->setAutoSubmit(currentStepDataCodeLength, submit);
newInput->submits() | rpl::on_next(submit, newInput->lifetime());
setFocusCallback([=] { newInput->setFocus(); });
Ui::ResizeFitChild(this, content);
}
} // namespace CloudPassword
Type CloudPasswordEmailConfirmId() {
return CloudPassword::EmailConfirm::Id();
}
} // namespace Settings

View File

@@ -0,0 +1,17 @@
/*
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_type.h"
namespace Settings {
Type CloudPasswordEmailConfirmId();
} // namespace Settings

View File

@@ -0,0 +1,160 @@
/*
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/cloud_password/settings_cloud_password_hint.h"
#include "api/api_cloud_password.h"
#include "lang/lang_keys.h"
#include "settings/cloud_password/settings_cloud_password_common.h"
#include "settings/cloud_password/settings_cloud_password_email.h"
#include "settings/cloud_password/settings_cloud_password_manage.h"
#include "settings/cloud_password/settings_cloud_password_step.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/wrap/padding_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "styles/style_boxes.h"
#include "styles/style_settings.h"
/*
Available actions for follow states.
CreateHint:
Continue to Email.
Skip to Email.
Back to CreatePassword.
ChangeHint:
Continue to Email.
Skip to Email.
Back to ChangePassword.
RecreateResetHint:
Continue to Manage.
Skip to Manage.
Back to RecreateResetPassword.
*/
namespace Settings {
namespace CloudPassword {
class Hint : public TypedAbstractStep<Hint> {
public:
using TypedAbstractStep::TypedAbstractStep;
[[nodiscard]] rpl::producer<QString> title() override;
void setupContent();
private:
rpl::lifetime _requestLifetime;
};
rpl::producer<QString> Hint::title() {
return tr::lng_settings_cloud_password_hint_title();
}
void Hint::setupContent() {
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
auto currentStepData = stepData();
const auto currentStepDataHint = base::take(currentStepData.hint);
setStepData(currentStepData);
SetupHeader(
content,
u"cloud_password/hint"_q,
showFinishes(),
tr::lng_settings_cloud_password_hint_subtitle(),
tr::lng_settings_cloud_password_hint_about());
Ui::AddSkip(content, st::settingLocalPasscodeDescriptionBottomSkip);
const auto newInput = AddWrappedField(
content,
tr::lng_cloud_password_hint(),
currentStepDataHint);
const auto error = AddError(content, nullptr);
newInput->changes(
) | rpl::on_next([=] {
error->hide();
}, newInput->lifetime());
AddSkipInsteadOfField(content);
const auto save = [=](const QString &hint) {
if (currentStepData.processRecover.setNewPassword) {
if (_requestLifetime) {
return;
}
_requestLifetime = cloudPassword().recoverPassword(
currentStepData.processRecover.checkedCode,
currentStepData.password,
hint
) | rpl::on_error_done([=](const QString &type) {
_requestLifetime.destroy();
error->show();
if (MTP::IsFloodError(type)) {
error->setText(tr::lng_flood_error(tr::now));
} else {
error->setText(Lang::Hard::ServerError());
}
}, [=] {
_requestLifetime.destroy();
auto empty = StepData();
empty.currentPassword = stepData().password;
setStepData(std::move(empty));
showOther(CloudPasswordManageId());
});
} else {
auto data = stepData();
data.hint = hint;
setStepData(std::move(data));
showOther(CloudPasswordEmailId());
}
};
AddLinkButton(
newInput,
tr::lng_settings_cloud_password_skip_hint()
)->setClickedCallback([=] {
save(QString());
});
const auto button = AddDoneButton(content, tr::lng_continue());
button->setClickedCallback([=] {
const auto newText = newInput->getLastText();
if (newText.isEmpty()) {
newInput->setFocus();
newInput->showError();
} else if (newText == stepData().password) {
error->show();
error->setText(tr::lng_cloud_password_bad(tr::now));
newInput->setFocus();
newInput->showError();
} else {
save(newText);
}
});
const auto submit = [=] { button->clicked({}, Qt::LeftButton); };
newInput->submits() | rpl::on_next(submit, newInput->lifetime());
setFocusCallback([=] { newInput->setFocus(); });
Ui::ResizeFitChild(this, content);
}
} // namespace CloudPassword
Type CloudPasswordHintId() {
return CloudPassword::Hint::Id();
}
} // namespace Settings

View File

@@ -0,0 +1,17 @@
/*
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_type.h"
namespace Settings {
Type CloudPasswordHintId();
} // namespace Settings

View File

@@ -0,0 +1,692 @@
/*
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/cloud_password/settings_cloud_password_input.h"
#include "api/api_cloud_password.h"
#include "base/qt_signal_producer.h"
#include "base/timer.h"
#include "base/unixtime.h"
#include "core/core_cloud_password.h"
#include "data/components/promo_suggestions.h"
#include "lang/lang_keys.h"
#include "lottie/lottie_icon.h"
#include "main/main_session.h"
#include "settings/cloud_password/settings_cloud_password_common.h"
#include "settings/cloud_password/settings_cloud_password_email_confirm.h"
#include "settings/cloud_password/settings_cloud_password_hint.h"
#include "settings/cloud_password/settings_cloud_password_manage.h"
#include "settings/cloud_password/settings_cloud_password_step.h"
#include "settings/cloud_password/settings_cloud_password_validate_icon.h"
#include "ui/boxes/boost_box.h" // Ui::StartFireworks.
#include "ui/boxes/confirm_box.h"
#include "ui/rect.h"
#include "ui/text/format_values.h"
#include "ui/ui_utility.h"
#include "ui/vertical_list.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/fields/password_input.h"
#include "ui/widgets/labels.h"
#include "ui/wrap/vertical_layout.h"
#include "window/window_session_controller.h"
#include "styles/style_boxes.h"
#include "styles/style_layers.h"
#include "styles/style_settings.h"
/*
Available actions for follow states.
CreatePassword:
Continue to CreateHint.
Back to Start.
ChangePassword:
Continue to ChangeHint.
Back to Manage.
CheckPassword:
Continue to Manage.
Recover to EmailConfirm.
Reset and wait (+ Cancel reset).
Reset now and Back to Settings.
Back to Settings.
RecreateResetPassword:
Continue to RecreateResetHint.
Clear password and Back to Settings.
Back to Settings.
ValidatePassword:
- Submit to show good validate.
- Back to Main Settings.
*/
namespace Settings {
namespace CloudPassword {
namespace {
struct Icon {
not_null<Lottie::Icon*> icon;
Fn<void()> update;
};
Icon CreateInteractiveLottieIcon(
not_null<Ui::VerticalLayout*> container,
Lottie::IconDescriptor &&descriptor,
style::margins padding) {
auto object = object_ptr<Ui::RpWidget>(container);
const auto raw = object.data();
const auto width = descriptor.sizeOverride.width();
raw->resize((Rect(descriptor.sizeOverride) + padding).size());
auto owned = Lottie::MakeIcon(std::move(descriptor));
const auto icon = owned.get();
raw->lifetime().add([kept = std::move(owned)]{});
raw->paintRequest(
) | rpl::on_next([=] {
auto p = QPainter(raw);
const auto left = (raw->width() - width) / 2;
icon->paint(p, left, padding.top());
}, raw->lifetime());
container->add(std::move(object));
return { .icon = icon, .update = [=] { raw->update(); } };
}
[[nodiscard]] not_null<Ui::LinkButton*> AddLinkButton(
not_null<Ui::VerticalLayout*> content,
not_null<Ui::PasswordInput*> input) {
const auto button = Ui::CreateChild<Ui::LinkButton>(
content.get(),
QString());
rpl::merge(
content->geometryValue(),
input->geometryValue()
) | rpl::on_next([=] {
const auto topLeft = input->mapTo(content, input->pos());
button->moveToLeft(
input->pos().x(),
topLeft.y() + input->height() + st::passcodeTextLine);
}, button->lifetime());
return button;
}
} // namespace
class Input : public TypedAbstractStep<Input> {
public:
using TypedAbstractStep::TypedAbstractStep;
[[nodiscard]] rpl::producer<QString> title() override;
[[nodiscard]] base::weak_qptr<Ui::RpWidget> createPinnedToTop(
not_null<QWidget*> parent) override;
void setupContent();
void setupValidateGood();
protected:
[[nodiscard]] rpl::producer<std::vector<Type>> removeTypes() override;
private:
void setupRecoverButton(
not_null<Ui::VerticalLayout*> container,
not_null<Ui::LinkButton*> button,
not_null<Ui::FlatLabel*> info,
Fn<void()> recoverCallback);
QWidget *_parent = nullptr;
rpl::variable<std::vector<Type>> _removesFromStack;
rpl::lifetime _requestLifetime;
};
rpl::producer<std::vector<Type>> Input::removeTypes() {
return _removesFromStack.value();
}
rpl::producer<QString> Input::title() {
return tr::lng_settings_cloud_password_password_title();
}
base::weak_qptr<Ui::RpWidget> Input::createPinnedToTop(
not_null<QWidget*> parent) {
_parent = parent;
return nullptr;
}
void Input::setupValidateGood() {
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
if (_parent) {
Ui::StartFireworks(_parent);
}
if (auto owned = CreateValidateGoodIcon(&controller()->session())) {
content->add(
std::move(owned),
QMargins(0, st::lineWidth * 75, 0, 0));
}
SetupHeader(
content,
QString(),
rpl::never<>(),
tr::lng_settings_suggestion_password_step_finish_title(),
tr::lng_settings_suggestion_password_step_finish_about());
const auto button = AddDoneButton(content, tr::lng_share_done());
button->setClickedCallback([=] {
showBack();
});
Ui::ToggleChildrenVisibility(this, true);
Ui::ResizeFitChild(this, content);
content->resizeToWidth(width());
Ui::SendPendingMoveResizeEvents(content);
}
void Input::setupContent() {
if (QWidget::children().count() > 0) {
return;
}
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
auto currentStepData = stepData();
const auto currentStepDataPassword = base::take(currentStepData.password);
const auto currentStepProcessRecover = base::take(
currentStepData.processRecover);
const auto currentStepValidate = base::take(
currentStepData.suggestionValidate);
setStepData(currentStepData);
const auto currentState = cloudPassword().stateCurrent();
const auto hasPassword = !currentStepProcessRecover.setNewPassword
&& (currentState ? currentState->hasPassword : false);
const auto isCheck = currentStepData.currentPassword.isEmpty()
&& hasPassword
&& !currentStepProcessRecover.setNewPassword;
if (currentStepProcessRecover.setNewPassword) {
_removesFromStack = std::vector<Type>{
CloudPasswordEmailConfirmId()
};
}
const auto icon = CreateInteractiveLottieIcon(
content,
{
.name = currentStepValidate
? u"cloud_password/validate"_q
: u"cloud_password/password_input"_q,
.sizeOverride = Size(st::settingsCloudPasswordIconSize),
},
st::settingLocalPasscodeIconPadding);
SetupHeader(
content,
QString(),
rpl::never<>(),
currentStepValidate
? tr::lng_settings_suggestion_password_step_input_title()
: isCheck
? tr::lng_settings_cloud_password_check_subtitle()
: hasPassword
? tr::lng_settings_cloud_password_manage_password_change()
: tr::lng_settings_cloud_password_password_subtitle(),
currentStepValidate
? tr::lng_settings_suggestion_password_step_input_about()
: isCheck
? tr::lng_settings_cloud_password_manage_about1()
: tr::lng_cloud_password_about());
Ui::AddSkip(content, st::settingLocalPasscodeDescriptionBottomSkip);
const auto newInput = AddPasswordField(
content,
(isCheck
? tr::lng_cloud_password_enter_old()
: tr::lng_cloud_password_enter_new()),
currentStepDataPassword);
const auto reenterInput = isCheck
? (Ui::PasswordInput*)(nullptr)
: AddPasswordField(
content,
tr::lng_cloud_password_confirm_new(),
currentStepDataPassword).get();
const auto error = AddError(content, newInput);
if (reenterInput) {
QObject::connect(reenterInput, &Ui::MaskedInputField::changed, [=] {
error->hide();
});
}
if (isCheck) {
AddSkipInsteadOfField(content);
const auto hint = currentState ? currentState->hint : QString();
const auto hintInfo = Ui::CreateChild<Ui::FlatLabel>(
error->parentWidget(),
tr::lng_signin_hint(tr::now, lt_password_hint, hint),
st::defaultFlatLabel);
hintInfo->setVisible(!hint.isEmpty());
rpl::combine(
error->geometryValue(),
newInput->geometryValue()
) | rpl::on_next([=](QRect r, QRect input) {
hintInfo->setGeometry(
{ input.x(), r.y(), input.width(), r.height() });
}, hintInfo->lifetime());
error->shownValue(
) | rpl::on_next([=](bool shown) {
if (shown) {
hintInfo->hide();
} else {
hintInfo->setVisible(!hint.isEmpty());
}
}, hintInfo->lifetime());
auto recoverCallback = [=] {
if (_requestLifetime) {
return;
}
const auto state = cloudPassword().stateCurrent();
if (!state) {
return;
}
if (state->hasRecovery) {
_requestLifetime = cloudPassword().requestPasswordRecovery(
) | rpl::on_next_error([=](const QString &pattern) {
_requestLifetime.destroy();
auto data = stepData();
data.processRecover = currentStepProcessRecover;
data.processRecover.emailPattern = pattern;
setStepData(std::move(data));
showOther(CloudPasswordEmailConfirmId());
}, [=](const QString &type) {
_requestLifetime.destroy();
error->show();
if (MTP::IsFloodError(type)) {
error->setText(tr::lng_flood_error(tr::now));
} else {
error->setText(Lang::Hard::ServerError());
}
});
} else {
const auto callback = [=](Fn<void()> close) {
if (_requestLifetime) {
return;
}
close();
_requestLifetime = cloudPassword().resetPassword(
) | rpl::on_next_error_done([=](
Api::CloudPassword::ResetRetryDate retryDate) {
_requestLifetime.destroy();
const auto left = std::max(
retryDate - base::unixtime::now(),
60);
controller()->show(Ui::MakeInformBox(
tr::lng_cloud_password_reset_later(
tr::now,
lt_duration,
Ui::FormatResetCloudPasswordIn(left))));
}, [=](const QString &type) {
_requestLifetime.destroy();
}, [=] {
_requestLifetime.destroy();
});
};
controller()->show(Ui::MakeConfirmBox({
.text = tr::lng_cloud_password_reset_no_email(),
.confirmed = callback,
.confirmText = tr::lng_cloud_password_reset_ok(),
.cancelText = tr::lng_cancel(),
.confirmStyle = &st::attentionBoxButton,
}));
}
};
const auto recover = AddLinkButton(content, newInput);
const auto resetInfo = Ui::CreateChild<Ui::FlatLabel>(
content,
QString(),
st::boxDividerLabel);
recover->geometryValue(
) | rpl::on_next([=](const QRect &r) {
resetInfo->moveToLeft(r.x(), r.y() + st::passcodeTextLine);
}, resetInfo->lifetime());
setupRecoverButton(
content,
recover,
resetInfo,
std::move(recoverCallback));
} else if (currentStepProcessRecover.setNewPassword && reenterInput) {
const auto skip = AddLinkButton(content, reenterInput);
skip->setText(tr::lng_settings_auto_night_disable(tr::now));
skip->setClickedCallback([=] {
if (_requestLifetime) {
return;
}
_requestLifetime = cloudPassword().recoverPassword(
currentStepProcessRecover.checkedCode,
QString(),
QString()
) | rpl::on_error_done([=](const QString &type) {
_requestLifetime.destroy();
error->show();
if (MTP::IsFloodError(type)) {
error->setText(tr::lng_flood_error(tr::now));
} else {
error->setText(Lang::Hard::ServerError());
}
}, [=] {
_requestLifetime.destroy();
controller()->show(
Ui::MakeInformBox(tr::lng_cloud_password_removed()));
setStepData(StepData());
showBack();
});
});
Ui::AddSkip(content);
}
if (currentStepValidate) {
icon.icon->animate(icon.update, 0, icon.icon->framesCount() - 1);
} else if (!newInput->text().isEmpty()) {
icon.icon->jumpTo(icon.icon->framesCount() / 2, icon.update);
}
const auto checkPassword = [=](const QString &pass) {
if (_requestLifetime) {
return;
}
_requestLifetime = cloudPassword().check(
pass
) | rpl::on_error_done([=](const QString &type) {
_requestLifetime.destroy();
newInput->setFocus();
newInput->showError();
newInput->selectAll();
error->show();
if (MTP::IsFloodError(type)) {
error->setText(tr::lng_flood_error(tr::now));
} else if (type == u"PASSWORD_HASH_INVALID"_q
|| type == u"SRP_PASSWORD_CHANGED"_q) {
error->setText(tr::lng_cloud_password_wrong(tr::now));
} else {
error->setText(Lang::Hard::ServerError());
}
}, [=] {
_requestLifetime.destroy();
if (const auto state = cloudPassword().stateCurrent()) {
if (state->pendingResetDate > 0) {
auto lifetime = rpl::lifetime();
lifetime = cloudPassword().cancelResetPassword(
) | rpl::on_next([] {});
}
}
if (currentStepValidate) {
controller()->session().promoSuggestions().dismiss(
Data::PromoSuggestions::SugValidatePassword());
setupValidateGood();
delete content;
} else {
auto data = stepData();
data.currentPassword = pass;
setStepData(std::move(data));
showOther(CloudPasswordManageId());
}
});
};
const auto button = AddDoneButton(
content,
isCheck ? tr::lng_passcode_check_button() : tr::lng_continue());
button->setClickedCallback([=] {
const auto newText = newInput->text();
const auto reenterText = isCheck ? QString() : reenterInput->text();
if (newText.isEmpty()) {
newInput->setFocus();
newInput->showError();
} else if (reenterInput && reenterText.isEmpty()) {
reenterInput->setFocus();
reenterInput->showError();
} else if (reenterInput && (newText != reenterText)) {
reenterInput->setFocus();
reenterInput->showError();
reenterInput->selectAll();
error->show();
error->setText(tr::lng_cloud_password_differ(tr::now));
} else if (isCheck) {
checkPassword(newText);
} else {
auto data = stepData();
data.processRecover = currentStepProcessRecover;
data.password = newText;
setStepData(std::move(data));
showOther(CloudPasswordHintId());
}
});
if (!currentStepValidate) {
base::qt_signal_producer(
newInput.get(),
&QLineEdit::textChanged // Covers Undo.
) | rpl::map([=] {
return newInput->text().isEmpty();
}) | rpl::distinct_until_changed(
) | rpl::on_next([=](bool empty) {
const auto from = icon.icon->frameIndex();
const auto to = empty ? 0 : (icon.icon->framesCount() / 2 - 1);
icon.icon->animate(icon.update, from, to);
}, content->lifetime());
}
const auto submit = [=] {
if (!reenterInput || reenterInput->hasFocus()) {
button->clicked({}, Qt::LeftButton);
} else {
reenterInput->setFocus();
}
};
QObject::connect(newInput, &Ui::MaskedInputField::submitted, submit);
if (reenterInput) {
using namespace Ui;
QObject::connect(reenterInput, &MaskedInputField::submitted, submit);
}
setFocusCallback(crl::guard(content, [=] {
if (isCheck || newInput->text().isEmpty()) {
newInput->setFocus();
} else if (reenterInput->text().isEmpty()) {
reenterInput->setFocus();
} else {
newInput->setFocus();
}
}));
Ui::ResizeFitChild(this, content);
}
void Input::setupRecoverButton(
not_null<Ui::VerticalLayout*> container,
not_null<Ui::LinkButton*> button,
not_null<Ui::FlatLabel*> info,
Fn<void()> recoverCallback) {
struct Status {
enum class SuggestAction {
Recover,
Reset,
CancelReset,
};
SuggestAction suggest = SuggestAction::Recover;
TimeId left = 0;
};
struct State {
base::Timer timer;
rpl::variable<Status> status;
};
const auto state = container->lifetime().make_state<State>();
const auto updateStatus = [=] {
const auto passwordState = cloudPassword().stateCurrent();
const auto date = passwordState ? passwordState->pendingResetDate : 0;
const auto left = (date - base::unixtime::now());
state->status = Status{
.suggest = ((left > 0)
? Status::SuggestAction::CancelReset
: date
? Status::SuggestAction::Reset
: Status::SuggestAction::Recover),
.left = left,
};
};
state->timer.setCallback(updateStatus);
updateStatus();
state->status.value(
) | rpl::on_next([=](const Status &status) {
switch (status.suggest) {
case Status::SuggestAction::Recover: {
info->setText(QString());
button->setText(tr::lng_signin_recover(tr::now));
} break;
case Status::SuggestAction::Reset: {
info->setText(QString());
button->setText(tr::lng_cloud_password_reset_ready(tr::now));
} break;
case Status::SuggestAction::CancelReset: {
info->setText(
tr::lng_settings_cloud_password_reset_in(
tr::now,
lt_duration,
Ui::FormatResetCloudPasswordIn(status.left)));
button->setText(
tr::lng_cloud_password_reset_cancel_title(tr::now));
} break;
}
}, container->lifetime());
cloudPassword().state(
) | rpl::on_next([=](const Core::CloudPasswordState &passState) {
updateStatus();
state->timer.cancel();
if (passState.pendingResetDate) {
state->timer.callEach(999);
}
}, container->lifetime());
button->setClickedCallback([=] {
const auto passState = cloudPassword().stateCurrent();
if (_requestLifetime || !passState) {
return;
}
updateStatus();
const auto suggest = state->status.current().suggest;
if (suggest == Status::SuggestAction::Recover) {
recoverCallback();
} else if (suggest == Status::SuggestAction::CancelReset) {
const auto cancel = [=](Fn<void()> close) {
if (_requestLifetime) {
return;
}
close();
_requestLifetime = cloudPassword().cancelResetPassword(
) | rpl::on_error_done([=](const QString &error) {
_requestLifetime.destroy();
}, [=] {
_requestLifetime.destroy();
});
};
controller()->show(Ui::MakeConfirmBox({
.text = tr::lng_cloud_password_reset_cancel_sure(),
.confirmed = cancel,
.confirmText = tr::lng_box_yes(),
.cancelText = tr::lng_box_no(),
}));
} else if (suggest == Status::SuggestAction::Reset) {
_requestLifetime = cloudPassword().resetPassword(
) | rpl::on_next_error_done([=](
Api::CloudPassword::ResetRetryDate retryDate) {
_requestLifetime.destroy();
const auto left = std::max(
retryDate - base::unixtime::now(),
60);
controller()->show(Ui::MakeInformBox(
tr::lng_cloud_password_reset_later(
tr::now,
lt_duration,
Ui::FormatResetCloudPasswordIn(left))));
}, [=](const QString &type) {
_requestLifetime.destroy();
}, [=] {
_requestLifetime.destroy();
cloudPassword().reload();
using PasswordState = Core::CloudPasswordState;
_requestLifetime = cloudPassword().state(
) | rpl::filter([=](const PasswordState &s) {
return !s.hasPassword;
}) | rpl::take(
1
) | rpl::on_next([=](const PasswordState &s) {
_requestLifetime.destroy();
controller()->show(Ui::MakeInformBox(
tr::lng_cloud_password_removed()));
setStepData(StepData());
showBack();
});
});
}
});
}
class SuggestionInput : public Input {
public:
SuggestionInput(
QWidget *parent,
not_null<Window::SessionController*> controller)
: Input(parent, controller)
, _stepData(StepData{ .suggestionValidate = true }) {
setStepDataReference(_stepData);
}
[[nodiscard]] static Type Id() {
return SectionFactory<SuggestionInput>::Instance();
}
private:
std::any _stepData;
};
} // namespace CloudPassword
Type CloudPasswordInputId() {
return CloudPassword::Input::Id();
}
Type CloudPasswordSuggestionInputId() {
return CloudPassword::SuggestionInput::Id();
}
} // namespace Settings

View File

@@ -0,0 +1,18 @@
/*
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_type.h"
namespace Settings {
Type CloudPasswordInputId();
Type CloudPasswordSuggestionInputId();
} // namespace Settings

View File

@@ -0,0 +1,175 @@
/*
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/cloud_password/settings_cloud_password_login_email.h"
#include "api/api_cloud_password.h"
#include "core/core_cloud_password.h"
#include "info/channel_statistics/boosts/giveaway/boost_badge.h" // InfiniteRadialAnimationWidget.
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "settings/cloud_password/settings_cloud_password_common.h"
#include "settings/cloud_password/settings_cloud_password_login_email_confirm.h"
#include "settings/cloud_password/settings_cloud_password_manage.h"
#include "settings/cloud_password/settings_cloud_password_step.h"
#include "ui/boxes/confirm_box.h"
#include "ui/vertical_list.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/wrap/vertical_layout.h"
#include "window/window_session_controller.h"
#include "styles/style_giveaway.h"
#include "styles/style_layers.h"
#include "styles/style_settings.h"
namespace Settings {
namespace CloudPassword {
class LoginEmail : public TypedAbstractStep<LoginEmail> {
public:
using TypedAbstractStep::TypedAbstractStep;
[[nodiscard]] rpl::producer<QString> title() override;
void setupContent();
private:
rpl::lifetime _requestLifetime;
std::optional<MTP::Sender> _api;
rpl::variable<bool> _confirmButtonBusy = false;
};
rpl::producer<QString> LoginEmail::title() {
return tr::lng_settings_cloud_login_email_section_title();
}
void LoginEmail::setupContent() {
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
auto currentStepData = stepData();
const auto newEmail = base::take(currentStepData.email);
setStepData(currentStepData);
SetupHeader(
content,
u"cloud_password/email"_q,
showFinishes(),
tr::lng_settings_cloud_login_email_title(),
tr::lng_settings_cloud_login_email_about());
Ui::AddSkip(content, st::settingLocalPasscodeDescriptionBottomSkip);
const auto newInput = AddWrappedField(
content,
tr::lng_settings_cloud_login_email_placeholder(),
QString());
const auto error = AddError(content, nullptr);
newInput->changes() | rpl::on_next([=] {
error->hide();
}, newInput->lifetime());
newInput->setText(newEmail);
if (newInput->hasText()) {
newInput->selectAll();
}
AddSkipInsteadOfField(content);
const auto send = [=] {
Expects(_api == std::nullopt);
_confirmButtonBusy = true;
_api.emplace(&controller()->session().mtp());
const auto data = stepData();
const auto done = [=](int length, const QString &pattern) {
_api.reset();
_confirmButtonBusy = false;
auto data = stepData();
data.unconfirmedEmailLengthCode = length;
setStepData(std::move(data));
showOther(CloudLoginEmailConfirmId());
};
const auto fail = [=](const QString &type) {
_api.reset();
_confirmButtonBusy = false;
if (MTP::IsFloodError(type)) {
error->show();
error->setText(tr::lng_flood_error(tr::now));
} else if (AbstractStep::isPasswordInvalidError(type)) {
} else if (type == u"EMAIL_INVALID"_q) {
error->show();
error->setText(tr::lng_cloud_password_bad_email(tr::now));
newInput->setFocus();
newInput->showError();
newInput->selectAll();
} else if (type == u"EMAIL_NOT_SETUP"_q) {
error->show();
error->setText(Lang::Hard::ServerError());
newInput->setFocus();
newInput->showError();
newInput->selectAll();
}
};
Api::RequestLoginEmailCode(*_api, data.email, done, fail);
};
const auto confirm = [=](const QString &email) {
if (_confirmButtonBusy.current()) {
return;
}
auto data = stepData();
data.email = email;
setStepData(std::move(data));
if (!email.isEmpty()) {
send();
return;
}
};
const auto button = AddDoneButton(
content,
rpl::conditional(
_confirmButtonBusy.value(),
rpl::single(QString()),
tr::lng_settings_cloud_login_email_confirm()));
button->setClickedCallback([=] {
const auto newText = newInput->getLastText();
if (newText.isEmpty()) {
newInput->setFocus();
newInput->showError();
} else {
confirm(newText);
}
});
{
using namespace Info::Statistics;
const auto loadingAnimation = InfiniteRadialAnimationWidget(
button,
st::giveawayGiftCodeStartButton.height / 2);
AddChildToWidgetCenter(button, loadingAnimation);
loadingAnimation->showOn(_confirmButtonBusy.value());
}
const auto submit = [=] { button->clicked({}, Qt::LeftButton); };
newInput->submits() | rpl::on_next(submit, newInput->lifetime());
setFocusCallback([=] { newInput->setFocus(); });
Ui::ResizeFitChild(this, content);
}
} // namespace CloudPassword
Type CloudLoginEmailId() {
return CloudPassword::LoginEmail::Id();
}
} // namespace Settings

View File

@@ -0,0 +1,17 @@
/*
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_type.h"
namespace Settings {
Type CloudLoginEmailId();
} // namespace Settings

View File

@@ -0,0 +1,179 @@
/*
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/cloud_password/settings_cloud_password_login_email_confirm.h"
#include "api/api_cloud_password.h"
#include "apiwrap.h"
#include "base/unixtime.h"
#include "core/core_cloud_password.h"
#include "intro/intro_code_input.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "mainwidget.h"
#include "settings/cloud_password/settings_cloud_password_common.h"
#include "settings/cloud_password/settings_cloud_password_login_email.h"
#include "settings/cloud_password/settings_cloud_password_step.h"
#include "ui/boxes/boost_box.h" // Ui::StartFireworks.
#include "ui/boxes/confirm_box.h"
#include "ui/text/text_utilities.h"
#include "ui/vertical_list.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/menu/menu_add_action_callback.h"
#include "ui/widgets/sent_code_field.h"
#include "ui/wrap/padding_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "window/window_session_controller.h"
#include "styles/style_boxes.h"
#include "styles/style_layers.h"
#include "styles/style_menu_icons.h"
#include "styles/style_settings.h"
/*
Available actions for follow states.
LoginEmailConfirm from LoginEmail:
Continue to Settings.
Back to LoginEmail.
*/
namespace Settings {
namespace CloudPassword {
class LoginEmailConfirm : public TypedAbstractStep<LoginEmailConfirm> {
public:
using TypedAbstractStep::TypedAbstractStep;
[[nodiscard]] rpl::producer<QString> title() override;
void setupContent();
protected:
[[nodiscard]] rpl::producer<std::vector<Type>> removeTypes() override;
private:
QString _collectedCode;
std::optional<MTP::Sender> _api;
rpl::event_stream<> _processFinishes;
};
rpl::producer<std::vector<Type>> LoginEmailConfirm::removeTypes() {
return _processFinishes.events() | rpl::map([] {
return std::vector<Type>{ CloudLoginEmailId() };
});
}
rpl::producer<QString> LoginEmailConfirm::title() {
return tr::lng_settings_cloud_login_email_section_title();
}
void LoginEmailConfirm::setupContent() {
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
auto currentStepData = stepData();
const auto currentStepDataCodeLength = base::take(
currentStepData.unconfirmedEmailLengthCode);
const auto newEmail = currentStepData.email;
setStepData(currentStepData);
if (!currentStepDataCodeLength) {
setStepData(StepData());
showBack();
return;
}
cloudPassword().state(
) | rpl::on_next([=](const Core::CloudPasswordState &state) {
if (state.loginEmailPattern.isEmpty()) {
setStepData(StepData());
showBack();
}
}, lifetime());
SetupHeader(
content,
u"cloud_password/email"_q,
showFinishes(),
tr::lng_settings_cloud_login_email_code_title(),
tr::lng_settings_cloud_login_email_code_about(
lt_email,
rpl::single(Ui::Text::WrapEmailPattern(newEmail)),
TextWithEntities::Simple));
Ui::AddSkip(content, st::settingLocalPasscodeDescriptionBottomSkip);
const auto newInput = content->add(
object_ptr<Ui::CodeInput>(content),
style::al_top);
newInput->setDigitsCountMax(currentStepDataCodeLength);
Ui::AddSkip(content);
const auto error = AddError(content, nullptr);
AddSkipInsteadOfField(content);
const auto submit = [=] {
_api.emplace(&controller()->session().mtp());
const auto newText = _collectedCode;
if (newText.isEmpty()) {
newInput->setFocus();
newInput->showError();
} else {
const auto weak = base::make_weak(controller()->content());
const auto done = [=] {
_api.reset();
_processFinishes.fire({});
cloudPassword().reload();
setStepData(StepData());
showBack();
if (const auto strong = weak.get()) {
Ui::StartFireworks(strong);
}
};
const auto fail = [=](const QString &type) {
_api.reset();
newInput->setFocus();
newInput->showError();
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"CODE_INVALID"_q) {
error->setText(tr::lng_signin_wrong_code(tr::now));
} else if (type == u"EMAIL_HASH_EXPIRED"_q) {
// Show box?
error->setText(Lang::Hard::EmailConfirmationExpired());
} else {
error->setText(Lang::Hard::ServerError());
}
};
Api::VerifyLoginEmail(*_api, newText, done, fail);
}
};
newInput->codeCollected(
) | rpl::on_next([=](const QString &code) {
_collectedCode = code;
error->hide();
submit();
}, lifetime());
setFocusCallback([=] { newInput->setFocus(); });
Ui::ResizeFitChild(this, content);
}
} // namespace CloudPassword
Type CloudLoginEmailConfirmId() {
return CloudPassword::LoginEmailConfirm::Id();
}
} // namespace Settings

View File

@@ -0,0 +1,17 @@
/*
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_type.h"
namespace Settings {
Type CloudLoginEmailConfirmId();
} // namespace Settings

View File

@@ -0,0 +1,235 @@
/*
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/cloud_password/settings_cloud_password_manage.h"
#include "api/api_cloud_password.h"
#include "core/application.h"
#include "core/core_cloud_password.h"
#include "lang/lang_keys.h"
#include "settings/cloud_password/settings_cloud_password_common.h"
#include "settings/cloud_password/settings_cloud_password_email_confirm.h"
#include "settings/cloud_password/settings_cloud_password_email.h"
#include "settings/cloud_password/settings_cloud_password_hint.h"
#include "settings/cloud_password/settings_cloud_password_input.h"
#include "settings/cloud_password/settings_cloud_password_start.h"
#include "settings/cloud_password/settings_cloud_password_step.h"
#include "ui/vertical_list.h"
#include "ui/boxes/confirm_box.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "ui/wrap/vertical_layout.h"
#include "window/window_session_controller.h"
#include "styles/style_layers.h"
#include "styles/style_menu_icons.h"
#include "styles/style_settings.h"
/*
Available actions for follow states.
From CreateEmail
From CreateEmailConfirm
From ChangeEmail
From ChangeEmailConfirm
From CheckPassword
From RecreateResetHint:
Continue to ChangePassword.
Continue to ChangeEmail.
DisablePassword and Back to Settings.
Back to Settings.
*/
namespace Settings {
namespace CloudPassword {
class Manage : public TypedAbstractStep<Manage> {
public:
using TypedAbstractStep::TypedAbstractStep;
[[nodiscard]] rpl::producer<QString> title() override;
void setupContent();
[[nodiscard]] base::weak_qptr<Ui::RpWidget> createPinnedToBottom(
not_null<Ui::RpWidget*> parent) override;
protected:
[[nodiscard]] rpl::producer<std::vector<Type>> removeTypes() override;
private:
rpl::variable<bool> _isBottomFillerShown;
QString _currentPassword;
rpl::lifetime _requestLifetime;
};
rpl::producer<QString> Manage::title() {
return tr::lng_settings_cloud_password_start_title();
}
rpl::producer<std::vector<Type>> Manage::removeTypes() {
return rpl::single(std::vector<Type>{
CloudPasswordStartId(),
CloudPasswordInputId(),
CloudPasswordHintId(),
CloudPasswordEmailId(),
CloudPasswordEmailConfirmId(),
CloudPasswordManageId(),
});
}
void Manage::setupContent() {
setFocusPolicy(Qt::StrongFocus);
setFocus();
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
auto currentStepData = stepData();
_currentPassword = base::take(currentStepData.currentPassword);
// If we go back from Password Manage to Privacy Settings
// we should forget the current password.
setStepData(std::move(currentStepData));
const auto quit = [=] {
setStepData(StepData());
showBack();
};
SetupAutoCloseTimer(
content->lifetime(),
quit,
[] { return Core::App().lastNonIdleTime(); });
const auto state = cloudPassword().stateCurrent();
if (!state) {
quit();
return;
}
cloudPassword().state(
) | rpl::on_next([=](const Core::CloudPasswordState &state) {
if (!_requestLifetime && !state.hasPassword) {
quit();
}
}, lifetime());
const auto showOtherAndRememberPassword = [=](Type type) {
// Remember the current password to have ability
// to return from Change Password to Password Manage.
auto data = stepData();
data.currentPassword = _currentPassword;
setStepData(std::move(data));
showOther(type);
};
AddDividerTextWithLottie(content, {
.lottie = u"cloud_password/intro"_q,
.showFinished = showFinishes(),
.about = tr::lng_settings_cloud_password_manage_about1(
TextWithEntities::Simple),
});
Ui::AddSkip(content);
AddButtonWithIcon(
content,
tr::lng_settings_cloud_password_manage_password_change(),
st::settingsButton,
{ &st::menuIconPermissions }
)->setClickedCallback([=] {
showOtherAndRememberPassword(CloudPasswordInputId());
});
AddButtonWithIcon(
content,
state->hasRecovery
? tr::lng_settings_cloud_password_manage_email_change()
: tr::lng_settings_cloud_password_manage_email_new(),
st::settingsButton,
{ &st::menuIconRecoveryEmail }
)->setClickedCallback([=] {
auto data = stepData();
data.setOnlyRecoveryEmail = true;
setStepData(std::move(data));
showOtherAndRememberPassword(CloudPasswordEmailId());
});
Ui::AddSkip(content);
using Divider = CloudPassword::OneEdgeBoxContentDivider;
const auto divider = Ui::CreateChild<Divider>(this);
divider->lower();
const auto about = content->add(
object_ptr<Ui::PaddingWrap<>>(
content,
object_ptr<Ui::FlatLabel>(
content,
tr::lng_settings_cloud_password_manage_about2(),
st::boxDividerLabel),
st::defaultBoxDividerLabelPadding));
rpl::combine(
about->geometryValue(),
content->widthValue()
) | rpl::on_next([=](QRect r, int w) {
r.setWidth(w);
divider->setGeometry(r);
}, divider->lifetime());
_isBottomFillerShown.value(
) | rpl::on_next([=](bool shown) {
divider->skipEdge(Qt::BottomEdge, shown);
}, divider->lifetime());
Ui::ResizeFitChild(this, content);
}
base::weak_qptr<Ui::RpWidget> Manage::createPinnedToBottom(
not_null<Ui::RpWidget*> parent) {
const auto disable = [=](Fn<void()> close) {
if (_requestLifetime) {
return;
}
_requestLifetime = cloudPassword().set(
_currentPassword,
QString(),
QString(),
false,
QString()
) | rpl::on_error_done([=](const QString &type) {
AbstractStep::isPasswordInvalidError(type);
}, [=] {
setStepData(StepData());
close();
showBack();
});
};
auto callback = [=] {
controller()->show(
Ui::MakeConfirmBox({
.text = tr::lng_settings_cloud_password_manage_disable_sure(),
.confirmed = disable,
.confirmText = tr::lng_settings_auto_night_disable(),
.confirmStyle = &st::attentionBoxButton,
}));
};
auto bottomButton = CloudPassword::CreateBottomDisableButton(
parent,
geometryValue(),
tr::lng_settings_password_disable(),
std::move(callback));
_isBottomFillerShown = base::take(bottomButton.isBottomFillerShown);
return bottomButton.content;
}
} // namespace CloudPassword
Type CloudPasswordManageId() {
return CloudPassword::Manage::Id();
}
} // namespace Settings

View File

@@ -0,0 +1,17 @@
/*
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_type.h"
namespace Settings {
Type CloudPasswordManageId();
} // namespace Settings

View File

@@ -0,0 +1,67 @@
/*
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/cloud_password/settings_cloud_password_start.h"
#include "lang/lang_keys.h"
#include "settings/cloud_password/settings_cloud_password_common.h"
#include "settings/cloud_password/settings_cloud_password_input.h"
#include "settings/cloud_password/settings_cloud_password_step.h"
#include "ui/vertical_list.h"
#include "ui/widgets/buttons.h"
#include "ui/wrap/vertical_layout.h"
#include "styles/style_settings.h"
namespace Settings {
namespace CloudPassword {
class Start : public TypedAbstractStep<Start> {
public:
using TypedAbstractStep::TypedAbstractStep;
[[nodiscard]] rpl::producer<QString> title() override;
void setupContent();
};
rpl::producer<QString> Start::title() {
return tr::lng_settings_cloud_password_start_title();
}
void Start::setupContent() {
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
SetupHeader(
content,
u"cloud_password/intro"_q,
showFinishes(),
tr::lng_settings_cloud_password_start_title(),
tr::lng_settings_cloud_password_start_about());
AddSkip(content, st::settingLocalPasscodeDescriptionBottomSkip);
AddSkipInsteadOfField(content);
AddSkipInsteadOfField(content);
AddSkipInsteadOfError(content);
AddDoneButton(
content,
tr::lng_settings_cloud_password_password_subtitle()
)->setClickedCallback([=] {
showOther(CloudPasswordInputId());
});
Ui::ResizeFitChild(this, content);
}
} // namespace CloudPassword
Type CloudPasswordStartId() {
return CloudPassword::Start::Id();
}
} // namespace Settings

View File

@@ -0,0 +1,17 @@
/*
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_type.h"
namespace Settings {
Type CloudPasswordStartId();
} // namespace Settings

View File

@@ -0,0 +1,126 @@
/*
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/cloud_password/settings_cloud_password_step.h"
#include "apiwrap.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "settings/cloud_password/settings_cloud_password_common.h"
#include "settings/cloud_password/settings_cloud_password_email.h"
#include "settings/cloud_password/settings_cloud_password_email_confirm.h"
#include "settings/cloud_password/settings_cloud_password_hint.h"
#include "settings/cloud_password/settings_cloud_password_input.h"
#include "settings/cloud_password/settings_cloud_password_manage.h"
#include "settings/cloud_password/settings_cloud_password_start.h"
#include "ui/boxes/confirm_box.h"
#include "window/window_session_controller.h"
namespace Settings::CloudPassword {
AbstractStep::AbstractStep(
QWidget *parent,
not_null<Window::SessionController*> controller)
: AbstractSection(parent)
, _controller(controller) {
}
not_null<Window::SessionController*> AbstractStep::controller() const {
return _controller;
}
Api::CloudPassword &AbstractStep::cloudPassword() {
return _controller->session().api().cloudPassword();
}
rpl::producer<AbstractStep::Types> AbstractStep::removeTypes() {
return rpl::never<Types>();
}
void AbstractStep::showBack() {
_showBack.fire({});
}
void AbstractStep::showOther(Type type) {
_showOther.fire_copy(type);
}
void AbstractStep::setFocusCallback(Fn<void()> callback) {
_setInnerFocusCallback = callback;
}
rpl::producer<> AbstractStep::showFinishes() const {
return _showFinished.events();
}
void AbstractStep::showFinished() {
_showFinished.fire({});
}
void AbstractStep::setInnerFocus() {
if (_setInnerFocusCallback) {
_setInnerFocusCallback();
}
}
bool AbstractStep::isPasswordInvalidError(const QString &type) {
if (type == u"PASSWORD_HASH_INVALID"_q
|| type == u"SRP_PASSWORD_CHANGED"_q) {
// Most likely the cloud password has been changed on another device.
// Quit.
_quits.fire(AbstractStep::Types{
CloudPasswordStartId(),
CloudPasswordInputId(),
CloudPasswordHintId(),
CloudPasswordEmailId(),
CloudPasswordEmailConfirmId(),
CloudPasswordManageId(),
});
controller()->show(
Ui::MakeInformBox(tr::lng_cloud_password_expired()),
Ui::LayerOption::CloseOther);
setStepData(StepData());
showBack();
return true;
}
return false;
}
rpl::producer<Type> AbstractStep::sectionShowOther() {
return _showOther.events();
}
rpl::producer<> AbstractStep::sectionShowBack() {
return _showBack.events();
}
rpl::producer<std::vector<Type>> AbstractStep::removeFromStack() {
return rpl::merge(removeTypes(), _quits.events());
}
void AbstractStep::setStepDataReference(std::any &data) {
_stepData = &data;
}
StepData AbstractStep::stepData() const {
if (!_stepData || !_stepData->has_value()) {
StepData();
}
const auto my = std::any_cast<StepData>(_stepData);
return my ? (*my) : StepData();
}
void AbstractStep::setStepData(StepData data) {
if (_stepData) {
*_stepData = data;
}
}
AbstractStep::~AbstractStep() = default;
} // namespace Settings::CloudPassword

View File

@@ -0,0 +1,89 @@
/*
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_session.h"
namespace Api {
class CloudPassword;
} // namespace Api
namespace Settings::CloudPassword {
struct StepData;
class AbstractStep : public AbstractSection {
public:
using Types = std::vector<Type>;
AbstractStep(
QWidget *parent,
not_null<Window::SessionController*> controller);
~AbstractStep();
void showFinished() override final;
void setInnerFocus() override final;
[[nodiscard]] rpl::producer<Type> sectionShowOther() override final;
[[nodiscard]] rpl::producer<> sectionShowBack() override final;
[[nodiscard]] rpl::producer<Types> removeFromStack() override final;
void setStepDataReference(std::any &data) override;
protected:
[[nodiscard]] not_null<Window::SessionController*> controller() const;
[[nodiscard]] Api::CloudPassword &cloudPassword();
[[nodiscard]] virtual rpl::producer<Types> removeTypes();
bool isPasswordInvalidError(const QString &type);
void showBack();
void showOther(Type type);
void setFocusCallback(Fn<void()> callback);
[[nodiscard]] rpl::producer<> showFinishes() const;
StepData stepData() const;
void setStepData(StepData data);
private:
const not_null<Window::SessionController*> _controller;
Fn<void()> _setInnerFocusCallback;
rpl::event_stream<> _showFinished;
rpl::event_stream<Type> _showOther;
rpl::event_stream<> _showBack;
rpl::event_stream<Types> _quits;
std::any *_stepData;
};
template <typename SectionType>
class TypedAbstractStep : public AbstractStep {
public:
using AbstractStep::AbstractStep;
void setStepDataReference(std::any &data) override final {
AbstractStep::setStepDataReference(data);
static_cast<SectionType*>(this)->setupContent();
}
[[nodiscard]] static Type Id() {
return SectionFactory<SectionType>::Instance();
}
[[nodiscard]] Type id() const final override {
return Id();
}
};
} // namespace Settings::CloudPassword

View File

@@ -0,0 +1,75 @@
/*
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/cloud_password/settings_cloud_password_validate_icon.h"
#include "apiwrap.h"
#include "base/object_ptr.h"
#include "chat_helpers/stickers_emoji_pack.h"
#include "data/data_session.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/stickers/data_stickers.h"
#include "main/main_session.h"
#include "ui/rect.h"
#include "ui/rp_widget.h"
#include "styles/style_settings.h"
namespace Settings {
namespace {
[[nodiscard]] DocumentData *EmojiValidateGood(
not_null<Main::Session*> session) {
auto emoji = TextWithEntities{
.text = (QString(QChar(0xD83D)) + QChar(0xDC4D)),
};
if (const auto e = Ui::Emoji::Find(emoji.text)) {
const auto sticker = session->emojiStickersPack().stickerForEmoji(e);
return sticker.document;
}
return nullptr;
}
} // namespace
object_ptr<Ui::RpWidget> CreateValidateGoodIcon(
not_null<Main::Session*> session) {
const auto document = EmojiValidateGood(session);
if (!document) {
return nullptr;
}
auto owned = object_ptr<Ui::RpWidget>((QWidget*)nullptr);
const auto widget = owned.data();
struct State {
std::unique_ptr<Ui::Text::CustomEmoji> emoji;
};
const auto state = widget->lifetime().make_state<State>();
const auto size = st::settingsCloudPasswordIconSize;
state->emoji = std::make_unique<Ui::Text::LimitedLoopsEmoji>(
session->data().customEmojiManager().create(
document,
[=] { widget->update(); },
Data::CustomEmojiManager::SizeTag::Normal,
size),
1,
true);
widget->paintRequest() | rpl::on_next([=] {
auto p = QPainter(widget);
state->emoji->paint(p, Ui::Text::CustomEmojiPaintContext{
.textColor = st::windowFg->c,
.now = crl::now(),
});
}, widget->lifetime());
const auto padding = st::settingLocalPasscodeIconPadding;
widget->resize((Rect(Size(size)) + padding).size());
widget->setNaturalWidth(padding.left() + size + padding.right());
return owned;
}
} // namespace Settings

View File

@@ -0,0 +1,27 @@
/*
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
template <typename Object>
class object_ptr;
namespace Main {
class Session;
} // namespace Main
namespace Ui {
class RpWidget;
} // namespace Ui
namespace Settings {
[[nodiscard]] object_ptr<Ui::RpWidget> CreateValidateGoodIcon(
not_null<Main::Session*> session);
} // namespace Settings