Add ability to reset cloud password in 7 days.

This commit is contained in:
John Preston 2021-07-30 14:32:49 +03:00
parent c100055fac
commit 256546071b
11 changed files with 299 additions and 30 deletions

View File

@ -82,6 +82,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_box_ok" = "OK";
"lng_box_done" = "Done";
"lng_box_yes" = "Yes";
"lng_box_no" = "No";
"lng_cancel" = "Cancel";
"lng_continue" = "Continue";
@ -596,6 +598,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_cloud_password_remove" = "Remove cloud password";
"lng_cloud_password_set" = "Enable two-step verification";
"lng_cloud_password_edit" = "Change cloud password";
"lng_cloud_password_reset_in" = "Reset password in";
"lng_cloud_password_reset_ready" = "Reset password";
"lng_cloud_password_reset_cancel" = "Cancel password reset";
"lng_cloud_password_enter_old" = "Enter current password";
"lng_cloud_password_enter_first" = "Enter a password";
"lng_cloud_password_enter_new" = "Enter new password";
@ -618,6 +623,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_cloud_password_passport_losing" = "Warning! All data saved in your Telegram Passport will be lost!";
"lng_cloud_password_resend" = "Resend code";
"lng_cloud_password_resent" = "Code was resent.";
"lng_cloud_password_reset_title" = "Reset password";
"lng_cloud_password_reset_no_email" = "Since you didn't provide a recovery email when setting up your password, your remaining options are either to remember your password or wait 7 days until your password is reset.";
"lng_cloud_password_reset_with_email" = "If you don't have access to your recovery email, your remaining options are either to remember your password or wait 7 days until your password resets.";
"lng_cloud_password_reset_ok" = "Reset";
"lng_cloud_password_reset_cancel_title" = "Cancel reset";
"lng_cloud_password_reset_cancel_sure" = "Cancel the password reset process? If you request a new reset later, it will take another 7 days.";
"lng_cloud_password_reset_later" = "You recently requested a password reset that was cancelled. Please wait {duration} before making a new request.";
"lng_connection_auto_connecting" = "Default (connecting...)";
"lng_connection_auto" = "Default ({transport} used)";

View File

@ -4764,6 +4764,23 @@ void ApiWrap::reloadPasswordState() {
}).send();
}
void ApiWrap::applyPendingReset(const MTPaccount_ResetPasswordResult &data) {
if (!_passwordState) {
reloadPasswordState();
return;
}
data.match([&](const MTPDaccount_resetPasswordOk &data) {
reloadPasswordState();
}, [&](const MTPDaccount_resetPasswordRequestedWait &data) {
const auto until = data.vuntil_date().v;
if (_passwordState->pendingResetDate != until) {
_passwordState->pendingResetDate = until;
_passwordStateChanges.fire_copy(*_passwordState);
}
}, [&](const MTPDaccount_resetPasswordFailedWait &data) {
});
}
void ApiWrap::clearUnconfirmedPassword() {
_passwordRequestId = request(MTPaccount_CancelPasswordEmail(
)).done([=](const MTPBool &result) {

View File

@ -434,6 +434,7 @@ public:
void clearPeerPhoto(not_null<PhotoData*> photo);
void reloadPasswordState();
void applyPendingReset(const MTPaccount_ResetPasswordResult &data);
void clearUnconfirmedPassword();
rpl::producer<Core::CloudPasswordState> passwordState() const;
std::optional<Core::CloudPasswordState> passwordStateCurrent() const;

View File

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "boxes/confirm_box.h"
#include "boxes/confirm_phone_box.h"
#include "base/unixtime.h"
#include "mainwindow.h"
#include "apiwrap.h"
#include "main/main_session.h"
@ -95,6 +96,56 @@ void TransferPasswordError(
}
}
void StartPendingReset(
not_null<Main::Session*> session,
not_null<Ui::BoxContent*> context,
Fn<void()> close) {
const auto weak = Ui::MakeWeak(context.get());
session->api().request(MTPaccount_ResetPassword(
)).done([=](const MTPaccount_ResetPasswordResult &result) {
session->api().applyPendingReset(result);
result.match([&](const MTPDaccount_resetPasswordOk &data) {
}, [&](const MTPDaccount_resetPasswordRequestedWait &data) {
}, [&](const MTPDaccount_resetPasswordFailedWait &data) {
constexpr auto kMinute = 60;
constexpr auto kHour = 3600;
constexpr auto kDay = 86400;
const auto left = std::max(
data.vretry_date().v - base::unixtime::now(),
kMinute);
const auto days = (left / kDay);
const auto hours = (left / kHour);
const auto minutes = (left / kMinute);
const auto duration = days
? tr::lng_group_call_duration_days(tr::now, lt_count, days)
: hours
? tr::lng_group_call_duration_hours(tr::now, lt_count, hours)
: tr::lng_group_call_duration_minutes(
tr::now,
lt_count,
minutes);
if (const auto strong = weak.data()) {
strong->getDelegate()->show(Box<InformBox>(
tr::lng_cloud_password_reset_later(
tr::now,
lt_duration,
duration)));
}
});
if (const auto strong = weak.data()) {
strong->closeBox();
}
close();
}).fail([=](const MTP::Error &error) {
if (const auto strong = weak.data()) {
strong->getDelegate()->show(
Box<InformBox>("Error: " + error.type()));
strong->closeBox();
}
close();
}).send();
}
} // namespace
PasscodeBox::CloudFields PasscodeBox::CloudFields::From(
@ -106,6 +157,7 @@ PasscodeBox::CloudFields PasscodeBox::CloudFields::From(
result.hasRecovery = current.hasRecovery;
result.notEmptyPassport = current.notEmptyPassport;
result.hint = current.hint;
result.pendingResetDate = current.pendingResetDate;
return result;
}
@ -145,7 +197,8 @@ PasscodeBox::PasscodeBox(
, _reenterPasscode(this, st::defaultInputField, tr::lng_cloud_password_confirm_new())
, _passwordHint(this, st::defaultInputField, fields.curRequest ? tr::lng_cloud_password_change_hint() : tr::lng_cloud_password_hint())
, _recoverEmail(this, st::defaultInputField, tr::lng_cloud_password_email())
, _recover(this, tr::lng_signin_recover(tr::now)) {
, _recover(this, tr::lng_signin_recover(tr::now))
, _showRecoverLink(_cloudFields.hasRecovery || !_cloudFields.pendingResetDate) {
Expects(!_turningOff || _cloudFields.curRequest);
if (!_cloudFields.hint.isEmpty()) {
@ -203,14 +256,14 @@ void PasscodeBox::prepare() {
: _cloudPwd
? tr::lng_cloud_password_remove()
: tr::lng_passcode_remove());
setDimensions(st::boxWidth, st::passcodePadding.top() + _oldPasscode->height() + st::passcodeTextLine + ((_cloudFields.hasRecovery && !_hintText.isEmpty()) ? st::passcodeTextLine : 0) + st::passcodeAboutSkip + _aboutHeight + st::passcodePadding.bottom());
setDimensions(st::boxWidth, st::passcodePadding.top() + _oldPasscode->height() + st::passcodeTextLine + ((_showRecoverLink && !_hintText.isEmpty()) ? st::passcodeTextLine : 0) + st::passcodeAboutSkip + _aboutHeight + st::passcodePadding.bottom());
} else {
if (currentlyHave()) {
_oldPasscode->show();
setTitle(_cloudPwd
? tr::lng_cloud_password_change()
: tr::lng_passcode_change());
setDimensions(st::boxWidth, st::passcodePadding.top() + _oldPasscode->height() + st::passcodeTextLine + ((_cloudFields.hasRecovery && !_hintText.isEmpty()) ? st::passcodeTextLine : 0) + _newPasscode->height() + st::passcodeLittleSkip + _reenterPasscode->height() + st::passcodeSkip + (_cloudPwd ? _passwordHint->height() + st::passcodeLittleSkip : 0) + st::passcodeAboutSkip + _aboutHeight + st::passcodePadding.bottom());
setDimensions(st::boxWidth, st::passcodePadding.top() + _oldPasscode->height() + st::passcodeTextLine + ((_showRecoverLink && !_hintText.isEmpty()) ? st::passcodeTextLine : 0) + _newPasscode->height() + st::passcodeLittleSkip + _reenterPasscode->height() + st::passcodeSkip + (_cloudPwd ? _passwordHint->height() + st::passcodeLittleSkip : 0) + st::passcodeAboutSkip + _aboutHeight + st::passcodePadding.bottom());
} else {
_oldPasscode->hide();
setTitle(_cloudPwd
@ -237,7 +290,9 @@ void PasscodeBox::prepare() {
const auto has = currentlyHave();
_oldPasscode->setVisible(onlyCheck || has);
_recover->setVisible((onlyCheck || has) && _cloudPwd && _cloudFields.hasRecovery);
_recover->setVisible((onlyCheck || has)
&& _cloudPwd
&& _showRecoverLink);
_newPasscode->setVisible(!onlyCheck);
_reenterPasscode->setVisible(!onlyCheck);
_passwordHint->setVisible(!onlyCheck && _cloudPwd);
@ -285,7 +340,7 @@ void PasscodeBox::paintEvent(QPaintEvent *e) {
Painter p(this);
int32 w = st::boxWidth - st::boxPadding.left() * 1.5;
int32 abouty = (_passwordHint->isHidden() ? ((_reenterPasscode->isHidden() ? (_oldPasscode->y() + (_cloudFields.hasRecovery && !_hintText.isEmpty() ? st::passcodeTextLine : 0)) : _reenterPasscode->y()) + st::passcodeSkip) : _passwordHint->y()) + _oldPasscode->height() + st::passcodeLittleSkip + st::passcodeAboutSkip;
int32 abouty = (_passwordHint->isHidden() ? ((_reenterPasscode->isHidden() ? (_oldPasscode->y() + (_showRecoverLink && !_hintText.isEmpty() ? st::passcodeTextLine : 0)) : _reenterPasscode->y()) + st::passcodeSkip) : _passwordHint->y()) + _oldPasscode->height() + st::passcodeLittleSkip + st::passcodeAboutSkip;
p.setPen(st::boxTextFg);
_about.drawLeft(p, st::boxPadding.left(), abouty, w, width());
@ -317,7 +372,7 @@ void PasscodeBox::resizeEvent(QResizeEvent *e) {
_oldPasscode->resize(w, _oldPasscode->height());
_oldPasscode->moveToLeft(st::boxPadding.left(), st::passcodePadding.top());
_newPasscode->resize(w, _newPasscode->height());
_newPasscode->moveToLeft(st::boxPadding.left(), _oldPasscode->y() + ((_turningOff || has) ? (_oldPasscode->height() + st::passcodeTextLine + ((_cloudFields.hasRecovery && !_hintText.isEmpty()) ? st::passcodeTextLine : 0)) : 0));
_newPasscode->moveToLeft(st::boxPadding.left(), _oldPasscode->y() + ((_turningOff || has) ? (_oldPasscode->height() + st::passcodeTextLine + ((_showRecoverLink && !_hintText.isEmpty()) ? st::passcodeTextLine : 0)) : 0));
_reenterPasscode->resize(w, _reenterPasscode->height());
_reenterPasscode->moveToLeft(st::boxPadding.left(), _newPasscode->y() + _newPasscode->height() + st::passcodeLittleSkip);
_passwordHint->resize(w, _passwordHint->height());
@ -379,7 +434,7 @@ void PasscodeBox::setPasswordFail(const QString &type) {
_oldPasscode->setFocus();
_oldPasscode->showError();
_oldError = tr::lng_flood_error(tr::now);
if (_cloudFields.hasRecovery && _hintText.isEmpty()) {
if (_showRecoverLink && _hintText.isEmpty()) {
_recover->hide();
}
update();
@ -913,7 +968,7 @@ void PasscodeBox::badOldPasscode() {
_oldError = _cloudPwd
? tr::lng_cloud_password_wrong(tr::now)
: tr::lng_passcode_wrong(tr::now);
if (_cloudFields.hasRecovery && _hintText.isEmpty()) {
if (_showRecoverLink && _hintText.isEmpty()) {
_recover->hide();
}
update();
@ -922,7 +977,7 @@ void PasscodeBox::badOldPasscode() {
void PasscodeBox::oldChanged() {
if (!_oldError.isEmpty()) {
_oldError = QString();
if (_cloudFields.hasRecovery && _hintText.isEmpty()) {
if (_showRecoverLink && _hintText.isEmpty()) {
_recover->show();
}
update();
@ -944,7 +999,21 @@ void PasscodeBox::emailChanged() {
}
void PasscodeBox::recoverByEmail() {
if (_pattern.isEmpty()) {
if (!_cloudFields.hasRecovery) {
const auto session = _session;
const auto confirmBox = std::make_shared<QPointer<BoxContent>>();
const auto reset = crl::guard(this, [=] {
StartPendingReset(session, this, [=] {
if (const auto box = *confirmBox) {
box->closeBox();
}
});
});
*confirmBox = getDelegate()->show(Box<ConfirmBox>(
tr::lng_cloud_password_reset_no_email(tr::now),
tr::lng_cloud_password_reset_ok(tr::now),
reset));
} else if (_pattern.isEmpty()) {
_pattern = "-";
_api.request(MTPauth_RequestPasswordRecovery(
)).done([=](const MTPauth_PasswordRecovery &result) {
@ -964,10 +1033,13 @@ void PasscodeBox::recoverExpired() {
void PasscodeBox::recover() {
if (_pattern == "-") return;
const auto weak = Ui::MakeWeak(this);
const auto box = getDelegate()->show(Box<RecoverBox>(
_session,
_pattern,
_cloudFields.notEmptyPassport));
_cloudFields.notEmptyPassport,
_cloudFields.pendingResetDate != 0,
[weak] { if (weak) { weak->closeBox(); } }));
box->passwordCleared(
) | rpl::map_to(
@ -996,11 +1068,37 @@ RecoverBox::RecoverBox(
QWidget*,
not_null<Main::Session*> session,
const QString &pattern,
bool notEmptyPassport)
bool notEmptyPassport,
bool hasPendingReset,
Fn<void()> closeParent)
: _api(&session->mtp())
, _pattern(st::normalFont->elided(tr::lng_signin_recover_hint(tr::now, lt_recover_email, pattern), st::boxWidth - st::boxPadding.left() * 1.5))
, _notEmptyPassport(notEmptyPassport)
, _recoverCode(this, st::defaultInputField, tr::lng_signin_code()) {
, _recoverCode(this, st::defaultInputField, tr::lng_signin_code())
, _noEmailAccess(this, tr::lng_signin_try_password(tr::now))
, _closeParent(std::move(closeParent)) {
if (hasPendingReset) {
_noEmailAccess.destroy();
} else {
_noEmailAccess->setClickedCallback([=] {
const auto confirmBox = std::make_shared<QPointer<BoxContent>>();
const auto reset = crl::guard(this, [=] {
const auto closeParent = _closeParent;
StartPendingReset(session, this, [=] {
if (closeParent) {
closeParent();
}
if (const auto box = *confirmBox) {
box->closeBox();
}
});
});
*confirmBox = getDelegate()->show(Box<ConfirmBox>(
tr::lng_cloud_password_reset_with_email(tr::now),
tr::lng_cloud_password_reset_ok(tr::now),
reset));
});
}
}
rpl::producer<> RecoverBox::passwordCleared() const {
@ -1017,7 +1115,13 @@ void RecoverBox::prepare() {
addButton(tr::lng_passcode_submit(), [=] { submit(); });
addButton(tr::lng_cancel(), [=] { closeBox(); });
setDimensions(st::boxWidth, st::passcodePadding.top() + st::passcodePadding.bottom() + st::passcodeTextLine + _recoverCode->height() + st::passcodeTextLine);
setDimensions(
st::boxWidth,
(st::passcodePadding.top()
+ st::passcodePadding.bottom()
+ st::passcodeTextLine
+ _recoverCode->height()
+ st::passcodeTextLine));
connect(_recoverCode, &Ui::InputField::changed, [=] { codeChanged(); });
connect(_recoverCode, &Ui::InputField::submitted, [=] { submit(); });
@ -1044,6 +1148,9 @@ void RecoverBox::resizeEvent(QResizeEvent *e) {
_recoverCode->resize(st::boxWidth - st::boxPadding.left() - st::boxPadding.right(), _recoverCode->height());
_recoverCode->moveToLeft(st::boxPadding.left(), st::passcodePadding.top() + st::passcodePadding.bottom() + st::passcodeTextLine);
if (_noEmailAccess) {
_noEmailAccess->moveToLeft(st::boxPadding.left(), _recoverCode->y() + _recoverCode->height() + (st::passcodeTextLine - _noEmailAccess->height()) / 2);
}
}
void RecoverBox::setInnerFocus() {
@ -1085,11 +1192,18 @@ void RecoverBox::submit() {
}
}
void RecoverBox::codeChanged() {
_error = QString();
void RecoverBox::setError(const QString &error) {
_error = error;
if (_noEmailAccess) {
_noEmailAccess->setVisible(error.isEmpty());
}
update();
}
void RecoverBox::codeChanged() {
setError(QString());
}
void RecoverBox::codeSubmitDone(const MTPauth_Authorization &result) {
_submitRequest = 0;
@ -1102,8 +1216,7 @@ void RecoverBox::codeSubmitDone(const MTPauth_Authorization &result) {
void RecoverBox::codeSubmitFail(const MTP::Error &error) {
if (MTP::IsFloodError(error)) {
_submitRequest = 0;
_error = tr::lng_flood_error(tr::now);
update();
setError(tr::lng_flood_error(tr::now));
_recoverCode->showError();
return;
}
@ -1121,18 +1234,14 @@ void RecoverBox::codeSubmitFail(const MTP::Error &error) {
_recoveryExpired.fire({});
closeBox();
} else if (err == qstr("CODE_INVALID")) {
_error = tr::lng_signin_wrong_code(tr::now);
update();
setError(tr::lng_signin_wrong_code(tr::now));
_recoverCode->selectAll();
_recoverCode->setFocus();
_recoverCode->showError();
} else {
if (Logs::DebugEnabled()) { // internal server error
_error = err + ": " + error.description();
} else {
_error = Lang::Hard::ServerError();
}
update();
setError(Logs::DebugEnabled() // internal server error
? (err + ": " + error.description())
: Lang::Hard::ServerError());
_recoverCode->setFocus();
}
}

View File

@ -39,6 +39,7 @@ public:
QString hint;
Core::SecureSecretAlgo newSecureSecretAlgo;
bool turningOff = false;
TimeId pendingResetDate = 0;
// Check cloud password for some action.
Fn<void(const Core::CloudPasswordResult &)> customCheckCallback;
@ -157,6 +158,7 @@ private:
object_ptr<Ui::InputField> _passwordHint;
object_ptr<Ui::InputField> _recoverEmail;
object_ptr<Ui::LinkButton> _recover;
bool _showRecoverLink = false;
QString _oldError, _newError, _emailError;
@ -172,7 +174,9 @@ public:
QWidget*,
not_null<Main::Session*> session,
const QString &pattern,
bool notEmptyPassport);
bool notEmptyPassport,
bool hasPendingReset,
Fn<void()> closeParent = nullptr);
rpl::producer<> passwordCleared() const;
rpl::producer<> recoveryExpired() const;
@ -192,6 +196,7 @@ private:
void codeChanged();
void codeSubmitDone(const MTPauth_Authorization &result);
void codeSubmitFail(const MTP::Error &error);
void setError(const QString &error);
MTP::Sender _api;
mtpRequestId _submitRequest = 0;
@ -200,6 +205,8 @@ private:
bool _notEmptyPassport = false;
object_ptr<Ui::InputField> _recoverCode;
object_ptr<Ui::LinkButton> _noEmailAccess;
Fn<void()> _closeParent;
QString _error;

View File

@ -314,6 +314,7 @@ CloudPasswordState ParseCloudPasswordState(
ParseSecureSecretAlgo(data.vnew_secure_algo()));
result.unconfirmedPattern =
qs(data.vemail_unconfirmed_pattern().value_or_empty());
result.pendingResetDate = data.vpending_reset_date().value_or_empty();
return result;
}

View File

@ -130,6 +130,7 @@ struct CloudPasswordState {
CloudPasswordAlgo newPassword;
SecureSecretAlgo newSecureSecret;
QString unconfirmedPattern;
TimeId pendingResetDate = 0;
};
CloudPasswordState ParseCloudPasswordState(

View File

@ -1001,7 +1001,8 @@ void FormController::recoverPassword() {
const auto box = _view->show(Box<RecoverBox>(
&_controller->session(),
pattern,
_password.notEmptyPassport));
_password.notEmptyPassport,
_password.pendingResetDate != 0));
box->passwordCleared(
) | rpl::start_with_next([=] {
@ -2636,6 +2637,7 @@ bool FormController::applyPassword(const MTPDaccount_password &result) {
Core::ParseCloudPasswordAlgo(result.vnew_algo()));
settings.newSecureAlgo = Core::ValidateNewSecureSecretAlgo(
Core::ParseSecureSecretAlgo(result.vnew_secure_algo()));
settings.pendingResetDate = result.vpending_reset_date().value_or_empty();
openssl::AddRandomSeed(bytes::make_span(result.vsecure_random().v));
return applyPassword(std::move(settings));
}

View File

@ -280,6 +280,7 @@ struct PasswordSettings {
bool hasRecovery = false;
bool notEmptyPassport = false;
bool unknownAlgo = false;
TimeId pendingResetDate = 0;
bool operator==(const PasswordSettings &other) const {
return (request == other.request)
@ -296,7 +297,8 @@ struct PasswordSettings {
&& (unconfirmedPattern == other.unconfirmedPattern)
&& (confirmedEmail == other.confirmedEmail)
&& (hasRecovery == other.hasRecovery)
&& (unknownAlgo == other.unknownAlgo);
&& (unknownAlgo == other.unknownAlgo)
&& (pendingResetDate == other.pendingResetDate);
}
bool operator!=(const PasswordSettings &other) const {
return !(*this == other);

View File

@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_common.h"
#include "settings/settings_privacy_controllers.h"
#include "base/timer_rpl.h"
#include "base/unixtime.h"
#include "boxes/peer_list_box.h"
#include "boxes/edit_privacy_box.h"
#include "boxes/passcode_box.h"
@ -359,6 +360,10 @@ void SetupCloudPassword(
) | rpl::then(rpl::duplicate(
unconfirmed
));
auto resetAt = session->api().passwordState(
) | rpl::map([](const State &state) {
return state.pendingResetDate;
});
const auto label = container->add(
object_ptr<Ui::SlideWrap<Ui::FlatLabel>>(
container,
@ -464,6 +469,118 @@ void SetupCloudPassword(
_1 && !_2));
disable->entity()->addClickHandler(remove);
auto resetInSeconds = rpl::duplicate(
resetAt
) | rpl::filter([](TimeId time) {
return time != 0;
}) | rpl::map([](TimeId time) {
return rpl::single(
rpl::empty_value()
) | rpl::then(base::timer_each(
999
)) | rpl::map([=] {
const auto now = base::unixtime::now();
return (time - now);
}) | rpl::distinct_until_changed(
) | rpl::take_while([](TimeId left) {
return left > 0;
}) | rpl::then(rpl::single(TimeId(0)));
}) | rpl::flatten_latest(
) | rpl::start_spawning(container->lifetime());
auto resetText = rpl::duplicate(
resetInSeconds
) | rpl::map([](TimeId left) {
return (left > 0);
}) | rpl::distinct_until_changed(
) | rpl::map([](bool waiting) {
return waiting
? tr::lng_cloud_password_reset_in()
: tr::lng_cloud_password_reset_ready();
}) | rpl::flatten_latest();
constexpr auto kMinute = 60;
constexpr auto kHour = 3600;
constexpr auto kDay = 86400;
auto resetLabel = rpl::duplicate(
resetInSeconds
) | rpl::map([](TimeId left) {
return (left >= kDay)
? ((left / kDay) * kDay)
: (left >= kHour)
? ((left / kHour) * kHour)
: (left >= kMinute)
? ((left / kMinute) * kMinute)
: left;
}) | rpl::distinct_until_changed(
) | rpl::map([](TimeId left) {
const auto days = left / kDay;
const auto hours = left / kHour;
const auto minutes = left / kMinute;
return days
? tr::lng_group_call_duration_days(tr::now, lt_count, days)
: hours
? tr::lng_group_call_duration_hours(tr::now, lt_count, hours)
: minutes
? tr::lng_group_call_duration_minutes(tr::now, lt_count, minutes)
: left
? tr::lng_group_call_duration_seconds(tr::now, lt_count, left)
: QString();
});
const auto reset = container->add(
object_ptr<Ui::SlideWrap<Button>>(
container,
object_ptr<Button>(
container,
rpl::duplicate(resetText),
st::settingsButton))
)->setDuration(0);
CreateRightLabel(
reset->entity(),
std::move(resetLabel),
st::settingsButton,
std::move(resetText));
reset->toggleOn(rpl::duplicate(
resetAt
) | rpl::map([](TimeId time) {
return time != 0;
}));
const auto sent = std::make_shared<mtpRequestId>(0);
reset->entity()->addClickHandler([=] {
const auto api = &session->api();
const auto state = api->passwordStateCurrent();
const auto date = state ? state->pendingResetDate : TimeId(0);
if (!date || *sent) {
return;
} else if (base::unixtime::now() >= date) {
*sent = api->request(MTPaccount_ResetPassword(
)).done([=](const MTPaccount_ResetPasswordResult &result) {
*sent = 0;
api->applyPendingReset(result);
}).fail([=](const MTP::Error &error) {
*sent = 0;
}).send();
} else {
const auto cancel = [=] {
Ui::hideLayer();
*sent = api->request(MTPaccount_DeclinePasswordReset(
)).done([=] {
*sent = 0;
api->reloadPasswordState();
}).fail([=](const MTP::Error &error) {
*sent = 0;
}).send();
};
Ui::show(Box<ConfirmBox>(
tr::lng_cloud_password_reset_cancel_sure(tr::now),
tr::lng_box_yes(tr::now),
tr::lng_box_no(tr::now),
cancel));
}
});
const auto abort = container->add(
object_ptr<Ui::SlideWrap<Button>>(
container,

@ -1 +1 @@
Subproject commit 65712d18253905d72db28d10d319e3c53616c915
Subproject commit a23c05c44e4f01dc4428f4d75d4db98c59d313a6