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
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:
@@ -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> &§ionGeometryValue,
|
||||
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
|
||||
@@ -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> &§ionGeometryValue,
|
||||
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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user