2022-05-07 02:45:52 +00:00
|
|
|
|
/*
|
|
|
|
|
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 "api/api_cloud_password.h"
|
2022-05-09 16:24:34 +00:00
|
|
|
|
#include "base/unixtime.h"
|
2022-05-07 02:45:52 +00:00
|
|
|
|
#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.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"
|
2022-05-09 16:24:34 +00:00
|
|
|
|
#include "ui/text/format_values.h"
|
2022-05-07 02:45:52 +00:00
|
|
|
|
#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_settings.h"
|
|
|
|
|
|
2022-05-09 19:03:56 +00:00
|
|
|
|
/*
|
|
|
|
|
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.
|
|
|
|
|
*/
|
|
|
|
|
|
2022-05-07 02:45:52 +00:00
|
|
|
|
namespace Settings {
|
|
|
|
|
namespace CloudPassword {
|
|
|
|
|
|
|
|
|
|
class EmailConfirm : public TypedAbstractStep<EmailConfirm> {
|
|
|
|
|
public:
|
|
|
|
|
using TypedAbstractStep::TypedAbstractStep;
|
|
|
|
|
|
|
|
|
|
[[nodiscard]] rpl::producer<QString> title() override;
|
|
|
|
|
void setupContent();
|
|
|
|
|
|
2022-05-09 23:47:16 +00:00
|
|
|
|
protected:
|
|
|
|
|
[[nodiscard]] rpl::producer<std::vector<Type>> removeTypes() override;
|
2022-05-07 02:45:52 +00:00
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
rpl::lifetime _requestLifetime;
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
rpl::producer<QString> EmailConfirm::title() {
|
|
|
|
|
return tr::lng_settings_cloud_password_email_title();
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-09 23:47:16 +00:00
|
|
|
|
rpl::producer<std::vector<Type>> EmailConfirm::removeTypes() {
|
2022-05-07 02:45:52 +00:00
|
|
|
|
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);
|
2022-05-08 23:19:51 +00:00
|
|
|
|
const auto recoverEmailPattern = base::take(
|
|
|
|
|
currentStepData.processRecover.emailPattern);
|
2022-05-07 02:45:52 +00:00
|
|
|
|
setStepData(currentStepData);
|
|
|
|
|
|
|
|
|
|
const auto state = cloudPassword().stateCurrent();
|
|
|
|
|
if (!state) {
|
|
|
|
|
setStepData(StepData());
|
|
|
|
|
showBack();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
cloudPassword().state(
|
|
|
|
|
) | rpl::start_with_next([=](const Core::CloudPasswordState &state) {
|
2022-05-08 23:19:51 +00:00
|
|
|
|
if (!_requestLifetime
|
|
|
|
|
&& state.unconfirmedPattern.isEmpty()
|
|
|
|
|
&& recoverEmailPattern.isEmpty()) {
|
2022-05-07 02:45:52 +00:00
|
|
|
|
setStepData(StepData());
|
|
|
|
|
showBack();
|
|
|
|
|
}
|
|
|
|
|
}, lifetime());
|
|
|
|
|
|
|
|
|
|
SetupHeader(
|
|
|
|
|
content,
|
|
|
|
|
u"cloud_password/email"_q,
|
|
|
|
|
showFinishes(),
|
2022-05-08 23:19:51 +00:00
|
|
|
|
state->unconfirmedPattern.isEmpty()
|
|
|
|
|
? tr::lng_settings_cloud_password_email_recovery_subtitle()
|
|
|
|
|
: tr::lng_cloud_password_confirm(),
|
2022-05-07 02:45:52 +00:00
|
|
|
|
rpl::single(
|
|
|
|
|
tr::lng_cloud_password_waiting_code(
|
|
|
|
|
tr::now,
|
|
|
|
|
lt_email,
|
2022-05-08 23:19:51 +00:00
|
|
|
|
state->unconfirmedPattern.isEmpty()
|
|
|
|
|
? recoverEmailPattern
|
|
|
|
|
: state->unconfirmedPattern)));
|
2022-05-07 02:45:52 +00:00
|
|
|
|
|
|
|
|
|
AddSkip(content, st::settingLocalPasscodeDescriptionBottomSkip);
|
|
|
|
|
|
2022-05-08 21:49:54 +00:00
|
|
|
|
auto objectInput = object_ptr<Ui::SentCodeField>(
|
|
|
|
|
content,
|
|
|
|
|
st::settingLocalPasscodeInputField,
|
|
|
|
|
tr::lng_change_phone_code_title());
|
|
|
|
|
const auto newInput = objectInput.data();
|
2022-05-07 02:45:52 +00:00
|
|
|
|
const auto wrap = content->add(
|
2022-05-08 21:49:54 +00:00
|
|
|
|
object_ptr<Ui::CenterWrap<Ui::InputField>>(
|
2022-05-07 02:45:52 +00:00
|
|
|
|
content,
|
2022-05-08 21:49:54 +00:00
|
|
|
|
std::move(objectInput)));
|
2022-05-07 02:45:52 +00:00
|
|
|
|
|
|
|
|
|
const auto error = AddError(content, nullptr);
|
2023-08-31 11:21:24 +00:00
|
|
|
|
newInput->changes(
|
|
|
|
|
) | rpl::start_with_next([=] {
|
2022-05-07 02:45:52 +00:00
|
|
|
|
error->hide();
|
2023-08-31 11:21:24 +00:00
|
|
|
|
}, newInput->lifetime());
|
2022-05-07 02:45:52 +00:00
|
|
|
|
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::start_with_next([=](const QRect &r) {
|
|
|
|
|
resendInfo->setGeometry(r);
|
|
|
|
|
}, resendInfo->lifetime());
|
|
|
|
|
error->shownValue(
|
|
|
|
|
) | rpl::start_with_next([=](bool shown) {
|
|
|
|
|
if (shown) {
|
|
|
|
|
resendInfo->hide();
|
|
|
|
|
}
|
|
|
|
|
}, resendInfo->lifetime());
|
|
|
|
|
|
2022-05-08 21:49:54 +00:00
|
|
|
|
const auto resend = AddLinkButton(wrap, tr::lng_cloud_password_resend());
|
2022-05-07 02:45:52 +00:00
|
|
|
|
resend->setClickedCallback([=] {
|
|
|
|
|
if (_requestLifetime) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
_requestLifetime = cloudPassword().resendEmailCode(
|
|
|
|
|
) | rpl::start_with_error_done([=](const QString &type) {
|
|
|
|
|
_requestLifetime.destroy();
|
|
|
|
|
|
|
|
|
|
error->show();
|
|
|
|
|
error->setText(Lang::Hard::ServerError());
|
|
|
|
|
}, [=] {
|
|
|
|
|
_requestLifetime.destroy();
|
|
|
|
|
|
|
|
|
|
error->hide();
|
|
|
|
|
resendInfo->show();
|
|
|
|
|
newInput->hideError();
|
|
|
|
|
});
|
|
|
|
|
});
|
2022-05-09 16:24:34 +00:00
|
|
|
|
|
|
|
|
|
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::start_with_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::start_with_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,
|
|
|
|
|
}));
|
|
|
|
|
});
|
|
|
|
|
}
|
2022-05-07 02:45:52 +00:00
|
|
|
|
|
|
|
|
|
const auto button = AddDoneButton(
|
|
|
|
|
content,
|
2022-05-08 23:19:51 +00:00
|
|
|
|
recoverEmailPattern.isEmpty()
|
|
|
|
|
? tr::lng_settings_cloud_password_email_confirm()
|
|
|
|
|
: tr::lng_passcode_check_button());
|
2022-05-07 02:45:52 +00:00
|
|
|
|
button->setClickedCallback([=] {
|
|
|
|
|
const auto newText = newInput->getDigitsOnly();
|
|
|
|
|
if (newText.isEmpty()) {
|
|
|
|
|
newInput->setFocus();
|
|
|
|
|
newInput->showError();
|
2022-05-08 23:19:51 +00:00
|
|
|
|
} else if (!_requestLifetime && recoverEmailPattern.isEmpty()) {
|
2022-05-07 02:45:52 +00:00
|
|
|
|
_requestLifetime = cloudPassword().confirmEmail(
|
|
|
|
|
newText
|
|
|
|
|
) | rpl::start_with_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());
|
|
|
|
|
}
|
|
|
|
|
});
|
2022-05-08 23:19:51 +00:00
|
|
|
|
} else if (!_requestLifetime) {
|
|
|
|
|
_requestLifetime = cloudPassword().checkRecoveryEmailAddressCode(
|
|
|
|
|
newText
|
|
|
|
|
) | rpl::start_with_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());
|
|
|
|
|
});
|
2022-05-07 02:45:52 +00:00
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const auto submit = [=] { button->clicked({}, Qt::LeftButton); };
|
|
|
|
|
newInput->setAutoSubmit(currentStepDataCodeLength, submit);
|
2023-08-31 11:21:24 +00:00
|
|
|
|
newInput->submits() | rpl::start_with_next(submit, newInput->lifetime());
|
2022-05-07 02:45:52 +00:00
|
|
|
|
|
|
|
|
|
setFocusCallback([=] { newInput->setFocus(); });
|
|
|
|
|
|
|
|
|
|
Ui::ResizeFitChild(this, content);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace CloudPassword
|
|
|
|
|
|
|
|
|
|
Type CloudPasswordEmailConfirmId() {
|
|
|
|
|
return CloudPassword::EmailConfirm::Id();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace Settings
|