Passport phone/email verification added.

This commit is contained in:
John Preston 2018-04-09 21:56:07 +04:00
parent 35dcbe0aa0
commit 4e2a109a46
16 changed files with 711 additions and 70 deletions

View File

@ -1555,7 +1555,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_passport_use_existing_email" = "Use the same email as on Telegram.";
"lng_passport_new_email" = "Or enter a new email";
"lng_passport_new_email_code" = "Note: You will receive a confirmation code on the email address you provide.";
"lng_passport_email_enter_code" = "Please enter the confirmation code we've just sent to {email}.";
"lng_passport_confirm_phone" = "We've sent an SMS with a confirmation code to your phone {phone}.";
"lng_passport_confirm_email" = "We've sent a confirmation code to your email {email}.";
"lng_passport_sure_cancel" = "If you continue your changes will be lost.";
// Wnd specific

View File

@ -999,7 +999,7 @@ secureCredentialsEncrypted#33f0ea47 data:bytes hash:bytes secret:bytes = SecureC
account.authorizationForm#b9d3d1f0 flags:# selfie_required:flags.1?true required_types:Vector<SecureValueType> values:Vector<SecureValue> users:Vector<User> privacy_policy_url:flags.0?string = account.AuthorizationForm;
account.sentEmailCode#28b1633b email_pattern:string = account.SentEmailCode;
account.sentEmailCode#811f854f email_pattern:string length:int = account.SentEmailCode;
---functions---

View File

@ -213,7 +213,7 @@ ChangePhoneBox::EnterCode::EnterCode(QWidget*, const QString &phone, const QStri
, _hash(hash)
, _codeLength(codeLength)
, _callTimeout(callTimeout)
, _call(this, [this] { sendCall(); }, [this] { updateCall(); }) {
, _call([this] { sendCall(); }, [this] { updateCall(); }) {
}
void ChangePhoneBox::EnterCode::prepare() {

View File

@ -76,15 +76,14 @@ void SentCodeField::fix() {
}
}
SentCodeCall::SentCodeCall(QObject *parent, base::lambda_once<void()> callCallback, base::lambda<void()> updateCallback)
: _timer(parent)
, _call(std::move(callCallback))
SentCodeCall::SentCodeCall(base::lambda_once<void()> callCallback, base::lambda<void()> updateCallback)
: _call(std::move(callCallback))
, _update(std::move(updateCallback)) {
_timer->connect(_timer, &QTimer::timeout, [this] {
_timer.setCallback([=] {
if (_status.state == State::Waiting) {
if (--_status.timeout <= 0) {
_status.state = State::Calling;
_timer->stop();
_timer.cancel();
if (_call) {
_call();
}
@ -99,7 +98,7 @@ SentCodeCall::SentCodeCall(QObject *parent, base::lambda_once<void()> callCallba
void SentCodeCall::setStatus(const Status &status) {
_status = status;
if (_status.state == State::Waiting) {
_timer->start(1000);
_timer.callEach(1000);
}
}
@ -130,7 +129,7 @@ void ConfirmPhoneBox::start(const QString &phone, const QString &hash) {
ConfirmPhoneBox::ConfirmPhoneBox(QWidget*, const QString &phone, const QString &hash)
: _phone(phone)
, _hash(hash)
, _call(this, [this] { sendCall(); }, [this] { update(); }) {
, _call([this] { sendCall(); }, [this] { update(); }) {
}
void ConfirmPhoneBox::sendCall() {

View File

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "boxes/abstract_box.h"
#include "base/timer.h"
#include "ui/widgets/input_fields.h"
namespace Ui {
@ -43,7 +44,9 @@ private:
class SentCodeCall {
public:
SentCodeCall(QObject *parent, base::lambda_once<void()> callCallback, base::lambda<void()> updateCallback);
SentCodeCall(
base::lambda_once<void()> callCallback,
base::lambda<void()> updateCallback);
enum class State {
Waiting,
@ -75,7 +78,7 @@ public:
private:
Status _status;
object_ptr<QTimer> _timer;
base::Timer _timer;
base::lambda_once<void()> _call;
base::lambda<void()> _update;

View File

@ -30,6 +30,9 @@ passportPasswordHintLabel: passportPasswordLabel;
passportErrorLabel: FlatLabel(passportPasswordLabel) {
textFg: boxTextFgError;
}
passportVerifyErrorLabel: FlatLabel(passportErrorLabel) {
align: align(topleft);
}
passportPanelWidth: 392px;
passportPanelHeight: 600px;

View File

@ -451,16 +451,84 @@ auto FormController::scanUpdated() const
return _scanUpdated.events();
}
auto FormController::valueSaved() const
->rpl::producer<not_null<const Value*>> {
return _valueSaved.events();
auto FormController::valueSaveFinished() const
-> rpl::producer<not_null<const Value*>> {
return _valueSaveFinished.events();
}
auto FormController::verificationNeeded() const
->rpl::producer<not_null<const Value*>> {
-> rpl::producer<not_null<const Value*>> {
return _verificationNeeded.events();
}
auto FormController::verificationUpdate() const
-> rpl::producer<not_null<const Value*>> {
return _verificationUpdate.events();
}
void FormController::verify(
not_null<const Value*> value,
const QString &code) {
if (value->verification.requestId) {
return;
}
const auto nonconst = findValue(value);
const auto prepared = code.trimmed();
Assert(nonconst->verification.codeLength != 0);
verificationError(nonconst, QString());
if (nonconst->verification.codeLength > 0
&& nonconst->verification.codeLength != prepared.size()) {
verificationError(nonconst, lang(lng_signin_wrong_code));
return;
} else if (prepared.isEmpty()) {
verificationError(nonconst, lang(lng_signin_wrong_code));
return;
}
nonconst->verification.requestId = [&] {
switch (nonconst->type) {
case Value::Type::Phone:
return request(MTPaccount_VerifyPhone(
MTP_string(getPhoneFromValue(nonconst)),
MTP_string(nonconst->verification.phoneCodeHash),
MTP_string(prepared)
)).done([=](const MTPBool &result) {
savePlainTextValue(nonconst);
clearValueVerification(nonconst);
}).fail([=](const RPCError &error) {
nonconst->verification.requestId = 0;
if (error.type() == qstr("PHONE_CODE_INVALID")) {
verificationError(nonconst, lang(lng_signin_wrong_code));
} else {
verificationError(nonconst, error.type());
}
}).send();
case Value::Type::Email:
return request(MTPaccount_VerifyEmail(
MTP_string(getEmailFromValue(nonconst)),
MTP_string(prepared)
)).done([=](const MTPBool &result) {
savePlainTextValue(nonconst);
clearValueVerification(nonconst);
}).fail([=](const RPCError &error) {
nonconst->verification.requestId = 0;
if (error.type() == qstr("CODE_INVALID")) {
verificationError(nonconst, lang(lng_signin_wrong_code));
} else {
verificationError(nonconst, error.type());
}
}).send();
}
Unexpected("Type in FormController::verify().");
}();
}
void FormController::verificationError(
not_null<Value*> value,
const QString &text) {
value->verification.error = text;
_verificationUpdate.fire_copy(value);
}
const Form &FormController::form() const {
return _form;
}
@ -475,15 +543,16 @@ not_null<Value*> FormController::findValue(not_null<const Value*> value) {
}
void FormController::startValueEdit(not_null<const Value*> value) {
if (value->saveRequestId) {
const auto nonconst = findValue(value);
++nonconst->editScreens;
if (savingValue(nonconst)) {
return;
}
const auto nonconst = findValue(value);
loadFiles(nonconst->files);
nonconst->filesInEdit = ranges::view::all(
value->files
nonconst->files
) | ranges::view::transform([=](const File &file) {
return EditFile(value, file, nullptr);
return EditFile(nonconst, file, nullptr);
}) | ranges::to_vector;
nonconst->data.parsedInEdit = nonconst->data.parsed;
}
@ -570,28 +639,112 @@ void FormController::fileLoadFail(FileKey key) {
}
}
bool FormController::savingValue(not_null<const Value*> value) const {
return (value->saveRequestId != 0)
|| (value->verification.requestId != 0)
|| (value->verification.codeLength != 0);
}
void FormController::cancelValueEdit(not_null<const Value*> value) {
if (value->saveRequestId) {
Expects(value->editScreens > 0);
const auto nonconst = findValue(value);
--nonconst->editScreens;
clearValueEdit(nonconst);
}
void FormController::valueEditFailed(not_null<Value*> value) {
Expects(!savingValue(value));
if (value->editScreens == 0) {
clearValueEdit(value);
}
}
void FormController::clearValueEdit(not_null<Value*> value) {
if (savingValue(value)) {
return;
}
value->filesInEdit.clear();
value->data.encryptedSecretInEdit.clear();
value->data.hashInEdit.clear();
value->data.parsedInEdit = ValueMap();
}
void FormController::cancelValueVerification(not_null<const Value*> value) {
const auto nonconst = findValue(value);
nonconst->filesInEdit.clear();
nonconst->data.encryptedSecretInEdit.clear();
nonconst->data.hashInEdit.clear();
nonconst->data.parsedInEdit = ValueMap();
clearValueVerification(nonconst);
if (!savingValue(nonconst)) {
valueEditFailed(nonconst);
}
}
void FormController::clearValueVerification(not_null<Value*> value) {
const auto was = (value->verification.codeLength != 0);
if (const auto requestId = base::take(value->verification.requestId)) {
request(requestId).cancel();
}
value->verification = Verification();
if (was) {
_verificationUpdate.fire_copy(value);
}
}
bool FormController::isEncryptedValue(Value::Type type) const {
return (type != Value::Type::Phone && type != Value::Type::Email);
}
bool FormController::editFileChanged(const EditFile &file) const {
if (file.uploadData) {
return !file.deleted;
}
return file.deleted;
}
bool FormController::editValueChanged(
not_null<const Value*> value,
const ValueMap &data) const {
auto filesCount = 0;
for (const auto &file : value->filesInEdit) {
if (editFileChanged(file)) {
return true;
}
}
if (value->selfieInEdit && editFileChanged(*value->selfieInEdit)) {
return true;
}
auto existing = value->data.parsed.fields;
for (const auto &[key, value] : data.fields) {
const auto i = existing.find(key);
if (i != existing.end()) {
if (i->second != value) {
return true;
}
existing.erase(i);
} else if (!value.isEmpty()) {
return true;
}
}
return !existing.empty();
}
void FormController::saveValueEdit(
not_null<const Value*> value,
ValueMap &&data) {
if (value->saveRequestId) {
if (savingValue(value)) {
return;
}
const auto nonconst = findValue(value);
if (!editValueChanged(nonconst, data)) {
base::take(nonconst->filesInEdit);
base::take(nonconst->selfieInEdit);
base::take(nonconst->data.encryptedSecretInEdit);
base::take(nonconst->data.hashInEdit);
base::take(nonconst->data.parsedInEdit);
_valueSaveFinished.fire_copy(nonconst);
return;
}
nonconst->data.parsedInEdit = std::move(data);
if (isEncryptedValue(nonconst->type)) {
@ -699,7 +852,7 @@ void FormController::saveEncryptedValue(not_null<Value*> value) {
void FormController::savePlainTextValue(not_null<Value*> value) {
Expects(!isEncryptedValue(value->type));
const auto text = value->data.parsedInEdit.fields["value"];
const auto text = getPlainTextFromValue(value);
const auto type = [&] {
switch (value->type) {
case Value::Type::Phone: return MTP_secureValueTypePhone();
@ -734,6 +887,8 @@ void FormController::sendSaveRequest(
)).done([=](const MTPSecureValue &result) {
Expects(result.type() == mtpc_secureValue);
value->saveRequestId = 0;
const auto &data = result.c_secureValue();
value->files = parseFiles(
data.vfiles.v,
@ -743,26 +898,146 @@ void FormController::sendSaveRequest(
value->data.parsed = std::move(value->data.parsedInEdit);
value->data.hash = std::move(value->data.hashInEdit);
value->saveRequestId = 0;
_valueSaved.fire_copy(value);
_valueSaveFinished.fire_copy(value);
}).fail([=](const RPCError &error) {
value->saveRequestId = 0;
if (error.type() == qstr("PHONE_VERIFICATION_NEEDED")) {
if (value->type == Value::Type::Phone) {
_verificationNeeded.fire_copy(value);
startPhoneVerification(value);
return;
}
} else if (error.type() == qstr("EMAIL_VERIFICATION_NEEDED")) {
if (value->type == Value::Type::Email) {
_verificationNeeded.fire_copy(value);
startEmailVerification(value);
return;
}
}
_view->show(Box<InformBox>("Error saving value:\n" + error.type()));
valueSaveFailed(value, error);
}).send();
}
QString FormController::getPhoneFromValue(
not_null<const Value*> value) const {
Expects(value->type == Value::Type::Phone);
return getPlainTextFromValue(value);
}
QString FormController::getEmailFromValue(
not_null<const Value*> value) const {
Expects(value->type == Value::Type::Email);
return getPlainTextFromValue(value);
}
QString FormController::getPlainTextFromValue(
not_null<const Value*> value) const {
Expects(value->type == Value::Type::Phone
|| value->type == Value::Type::Email);
const auto i = value->data.parsedInEdit.fields.find("value");
Assert(i != end(value->data.parsedInEdit.fields));
return i->second;
}
void FormController::startPhoneVerification(not_null<Value*> value) {
value->verification.requestId = request(MTPaccount_SendVerifyPhoneCode(
MTP_flags(MTPaccount_SendVerifyPhoneCode::Flag(0)),
MTP_string(getPhoneFromValue(value)),
MTPBool()
)).done([=](const MTPauth_SentCode &result) {
Expects(result.type() == mtpc_auth_sentCode);
value->verification.requestId = 0;
const auto &data = result.c_auth_sentCode();
value->verification.phoneCodeHash = qs(data.vphone_code_hash);
switch (data.vtype.type()) {
case mtpc_auth_sentCodeTypeApp:
LOG(("API Error: sentCodeTypeApp not expected "
"in FormController::startPhoneVerification."));
return;
case mtpc_auth_sentCodeTypeFlashCall:
LOG(("API Error: sentCodeTypeFlashCall not expected "
"in FormController::startPhoneVerification."));
return;
case mtpc_auth_sentCodeTypeCall: {
const auto &type = data.vtype.c_auth_sentCodeTypeCall();
value->verification.codeLength = (type.vlength.v > 0)
? type.vlength.v
: -1;
value->verification.call = std::make_unique<SentCodeCall>(
[=] { requestPhoneCall(value); },
[=] { _verificationUpdate.fire_copy(value); });
value->verification.call->setStatus(
{ SentCodeCall::State::Called, 0 });
if (data.has_next_type()) {
LOG(("API Error: next_type is not supported for calls."));
}
} break;
case mtpc_auth_sentCodeTypeSms: {
const auto &type = data.vtype.c_auth_sentCodeTypeSms();
value->verification.codeLength = (type.vlength.v > 0)
? type.vlength.v
: -1;
const auto &next = data.vnext_type;
if (data.has_next_type()
&& next.type() == mtpc_auth_codeTypeCall) {
value->verification.call = std::make_unique<SentCodeCall>(
[=] { requestPhoneCall(value); },
[=] { _verificationUpdate.fire_copy(value); });
value->verification.call->setStatus({
SentCodeCall::State::Waiting,
data.has_timeout() ? data.vtimeout.v : 60 });
}
} break;
}
_verificationNeeded.fire_copy(value);
}).fail([=](const RPCError &error) {
value->verification.requestId = 0;
valueSaveFailed(value, error);
}).send();
}
void FormController::startEmailVerification(not_null<Value*> value) {
value->verification.requestId = request(MTPaccount_SendVerifyEmailCode(
MTP_string(getEmailFromValue(value))
)).done([=](const MTPaccount_SentEmailCode &result) {
Expects(result.type() == mtpc_account_sentEmailCode);
value->verification.requestId = 0;
const auto &data = result.c_account_sentEmailCode();
value->verification.codeLength = (data.vlength.v > 0)
? data.vlength.v
: -1;
_verificationNeeded.fire_copy(value);
}).fail([=](const RPCError &error) {
valueSaveFailed(value, error);
}).send();
}
void FormController::requestPhoneCall(not_null<Value*> value) {
Expects(value->verification.call != nullptr);
value->verification.call->setStatus(
{ SentCodeCall::State::Calling, 0 });
request(MTPauth_ResendCode(
MTP_string(getPhoneFromValue(value)),
MTP_string(value->verification.phoneCodeHash)
)).done([=](const MTPauth_SentCode &code) {
value->verification.call->callDone();
}).send();
}
void FormController::valueSaveFailed(
not_null<Value*> value,
const RPCError &error) {
_view->show(Box<InformBox>("Error saving value:\n" + error.type()));
valueEditFailed(value);
_valueSaveFinished.fire_copy(value);
}
void FormController::generateSecret(bytes::const_span password) {
if (_saveSecretRequestId) {
return;

View File

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "mtproto/sender.h"
#include "boxes/confirm_phone_box.h"
#include "base/weak_ptr.h"
class BoxContent;
@ -111,6 +112,16 @@ struct ValueData {
bytes::vector encryptedSecretInEdit;
};
struct Verification {
mtpRequestId requestId = 0;
QString phoneCodeHash;
int codeLength = 0;
std::unique_ptr<SentCodeCall> call;
QString error;
};
struct Value {
enum class Type {
PersonalDetails,
@ -135,6 +146,9 @@ struct Value {
std::vector<EditFile> filesInEdit;
base::optional<File> selfie;
base::optional<EditFile> selfieInEdit;
Verification verification;
int editScreens = 0;
mtpRequestId saveRequestId = 0;
};
@ -205,13 +219,20 @@ public:
QString defaultPhoneNumber() const;
rpl::producer<not_null<const EditFile*>> scanUpdated() const;
rpl::producer<not_null<const Value*>> valueSaved() const;
rpl::producer<not_null<const Value*>> valueSaveFinished() const;
rpl::producer<not_null<const Value*>> verificationNeeded() const;
rpl::producer<not_null<const Value*>> verificationUpdate() const;
void verify(not_null<const Value*> value, const QString &code);
const Form &form() const;
void startValueEdit(not_null<const Value*> value);
void cancelValueEdit(not_null<const Value*> value);
void cancelValueVerification(not_null<const Value*> value);
bool editValueChanged(
not_null<const Value*> value,
const ValueMap &data) const;
void saveValueEdit(not_null<const Value*> value, ValueMap &&data);
bool savingValue(not_null<const Value*> value) const;
void cancel();
@ -274,6 +295,21 @@ private:
void scanUploadFail(const FullMsgId &fullId);
void scanDeleteRestore(not_null<const Value*> value, int fileIndex, bool deleted);
QString getPhoneFromValue(not_null<const Value*> value) const;
QString getEmailFromValue(not_null<const Value*> value) const;
QString getPlainTextFromValue(not_null<const Value*> value) const;
void startPhoneVerification(not_null<Value*> value);
void startEmailVerification(not_null<Value*> value);
void valueSaveFailed(not_null<Value*> value, const RPCError &error);
void requestPhoneCall(not_null<Value*> value);
void verificationError(
not_null<Value*> value,
const QString &text);
void valueEditFailed(not_null<Value*> value);
void clearValueEdit(not_null<Value*> value);
void clearValueVerification(not_null<Value*> value);
bool editFileChanged(const EditFile &file) const;
bool isEncryptedValue(Value::Type type) const;
void saveEncryptedValue(not_null<Value*> value);
void savePlainTextValue(not_null<Value*> value);
@ -295,8 +331,9 @@ private:
std::map<FileKey, std::unique_ptr<mtpFileLoader>> _fileLoaders;
rpl::event_stream<not_null<const EditFile*>> _scanUpdated;
rpl::event_stream<not_null<const Value*>> _valueSaved;
rpl::event_stream<not_null<const Value*>> _valueSaveFinished;
rpl::event_stream<not_null<const Value*>> _verificationNeeded;
rpl::event_stream<not_null<const Value*>> _verificationUpdate;
bytes::vector _secret;
uint64 _secretId = 0;

View File

@ -83,7 +83,9 @@ void Panel::updateTitlePosition() {
}
rpl::producer<> Panel::backRequests() const {
return _back->entity()->clicks();
return rpl::merge(
_back->entity()->clicks(),
_synteticBackRequests.events());
}
void Panel::setBackAllowed(bool allowed) {
@ -100,10 +102,11 @@ void Panel::showAndActivate() {
setFocus();
}
bool Panel::eventHook(QEvent *e) {
if (e->type() == QEvent::WindowDeactivate) {
void Panel::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Escape && _back->toggled()) {
_synteticBackRequests.fire({});
}
return RpWidget::eventHook(e);
return RpWidget::keyPressEvent(e);
}
void Panel::initLayout() {
@ -276,6 +279,10 @@ void Panel::showInner(base::unique_qptr<Ui::RpWidget> inner) {
}, _inner->lifetime());
_inner->show();
if (_layer) {
_layer->raise();
}
showAndActivate();
}

View File

@ -55,7 +55,7 @@ protected:
void mouseMoveEvent(QMouseEvent *e) override;
void leaveEventHook(QEvent *e) override;
void leaveToChildEvent(QEvent *e, QWidget *child) override;
bool eventHook(QEvent *e) override;
void keyPressEvent(QKeyEvent *e) override;
private:
void initControls();
@ -83,6 +83,7 @@ private:
object_ptr<Ui::RpWidget> _body;
base::unique_qptr<Ui::RpWidget> _inner;
object_ptr<Window::LayerStackWidget> _layer = { nullptr };
rpl::event_stream<> _synteticBackRequests;
bool _useTransparency = true;
style::margins _padding;

View File

@ -225,6 +225,19 @@ PanelController::PanelController(not_null<FormController*> form)
_panel->showForm();
}
}, lifetime());
_form->verificationNeeded(
) | rpl::start_with_next([=](not_null<const Value*> value) {
processVerificationNeeded(value);
}, lifetime());
_form->verificationUpdate(
) | rpl::filter([=](not_null<const Value*> field) {
return (field->verification.codeLength == 0);
}) | rpl::start_with_next([=](not_null<const Value*> field) {
_verificationBoxes.erase(field);
}, lifetime());
_scopes = ComputeScopes(_form);
}
@ -417,8 +430,8 @@ void PanelController::editScope(int index) {
auto content = [&]() -> object_ptr<Ui::RpWidget> {
switch (_editScope->type) {
case Scope::Type::Identity:
case Scope::Type::Address:
return (_editScopeFilesIndex >= 0)
case Scope::Type::Address: {
auto result = (_editScopeFilesIndex >= 0)
? object_ptr<PanelEditDocument>(
_panel.get(),
this,
@ -431,10 +444,17 @@ void PanelController::editScope(int index) {
this,
std::move(GetDocumentScheme(_editScope->type)),
_editScope->fields->data.parsedInEdit);
const auto weak = make_weak(result.data());
_panelHasUnsavedChanges = [=] {
return weak ? weak->hasUnsavedChanges() : false;
};
return std::move(result);
} break;
case Scope::Type::Phone:
case Scope::Type::Email: {
const auto &parsed = _editScope->fields->data.parsedInEdit;
const auto valueIt = parsed.fields.find("value");
_panelHasUnsavedChanges = nullptr;
return object_ptr<PanelEditContact>(
_panel.get(),
this,
@ -448,48 +468,106 @@ void PanelController::editScope(int index) {
Unexpected("Type in PanelController::editScope().");
}();
_panel->setBackAllowed(true);
_panel->backRequests(
) | rpl::start_with_next([=] {
_panel->showForm();
}, content->lifetime());
content->lifetime().add([=] {
cancelValueEdit();
});
_form->valueSaved(
) | rpl::start_with_next([=](not_null<const Value*> value) {
processValueSaved(value);
_panel->setBackAllowed(true);
_panel->backRequests(
) | rpl::start_with_next([=] {
cancelEditScope();
}, content->lifetime());
_form->verificationNeeded(
_form->valueSaveFinished(
) | rpl::start_with_next([=](not_null<const Value*> value) {
processVerificationNeeded(value);
processValueSaveFinished(value);
}, content->lifetime());
_panel->showEditValue(std::move(content));
}
void PanelController::processValueSaved(not_null<const Value*> value) {
void PanelController::processValueSaveFinished(
not_null<const Value*> value) {
Expects(_editScope != nullptr);
const auto boxIt = _verificationBoxes.find(value);
if (boxIt != end(_verificationBoxes)) {
const auto saved = std::move(boxIt->second);
_verificationBoxes.erase(boxIt);
}
const auto value1 = _editScope->fields;
const auto value2 = (_editScopeFilesIndex >= 0)
? _editScope->files[_editScopeFilesIndex].get()
: nullptr;
if (value == value1 || value == value2) {
if (!value1->saveRequestId && (!value2 || !value2->saveRequestId)) {
if (!_form->savingValue(value1)
&& (!value2 || !_form->savingValue(value2))) {
_panel->showForm();
show(Box<InformBox>("Saved"));
}
}
}
void PanelController::processVerificationNeeded(
not_null<const Value*> value) {
show(Box<InformBox>("Verification needed :("));
const auto i = _verificationBoxes.find(value);
if (i != _verificationBoxes.end()) {
LOG(("API Error: Requesting for verification repeatedly."));
return;
}
const auto textIt = value->data.parsedInEdit.fields.find("value");
Assert(textIt != end(value->data.parsedInEdit.fields));
const auto text = textIt->second;
const auto type = value->type;
const auto update = _form->verificationUpdate(
) | rpl::filter([=](not_null<const Value*> field) {
return (field == value);
});
const auto box = [&] {
if (type == Value::Type::Phone) {
return show(VerifyPhoneBox(
text,
value->verification.codeLength,
[=](const QString &code) { _form->verify(value, code); },
value->verification.call ? rpl::single(
value->verification.call->getText()
) | rpl::then(rpl::duplicate(
update
) | rpl::filter([=](not_null<const Value*> field) {
return field->verification.call != nullptr;
}) | rpl::map([=](not_null<const Value*> field) {
return field->verification.call->getText();
})) : (rpl::single(QString()) | rpl::type_erased()),
rpl::duplicate(
update
) | rpl::map([=](not_null<const Value*> field) {
return field->verification.error;
}) | rpl::distinct_until_changed()));
} else if (type == Value::Type::Email) {
return show(VerifyEmailBox(
text,
value->verification.codeLength,
[=](const QString &code) { _form->verify(value, code); },
rpl::duplicate(
update
) | rpl::map([=](not_null<const Value*> field) {
return field->verification.error;
}) | rpl::distinct_until_changed()));
} else {
Unexpected("Type in processVerificationNeeded.");
}
}();
box->boxClosing(
) | rpl::start_with_next([=] {
_form->cancelValueVerification(value);
}, lifetime());
_verificationBoxes.emplace(value, box);
}
std::vector<ScanInfo> PanelController::valueFiles(
@ -525,6 +603,36 @@ void PanelController::saveScope(ValueMap &&data, ValueMap &&filesData) {
}
}
bool PanelController::editScopeChanged(
const ValueMap &data,
const ValueMap &filesData) const {
Expects(_editScope != nullptr);
if (_form->editValueChanged(_editScope->fields, data)) {
return true;
} else if (_editScopeFilesIndex >= 0) {
return _form->editValueChanged(
_editScope->files[_editScopeFilesIndex],
filesData);
}
return false;
}
void PanelController::cancelEditScope() {
Expects(_editScope != nullptr);
if (_panelHasUnsavedChanges && _panelHasUnsavedChanges()) {
if (!_confirmForgetChangesBox) {
_confirmForgetChangesBox = BoxPointer(show(Box<ConfirmBox>(
lang(lng_passport_sure_cancel),
lang(lng_continue),
[=] { _panel->showForm(); })).data());
}
} else {
_panel->showForm();
}
}
void PanelController::cancelAuth() {
_form->cancel();
}

View File

@ -71,6 +71,10 @@ public:
void editScope(int index) override;
void saveScope(ValueMap &&data, ValueMap &&filesData);
bool editScopeChanged(
const ValueMap &data,
const ValueMap &filesData) const;
void cancelEditScope();
void showBox(object_ptr<BoxContent> box) override;
@ -83,7 +87,7 @@ private:
void cancelValueEdit();
std::vector<ScanInfo> valueFiles(const Value &value) const;
void processValueSaved(not_null<const Value*> value);
void processValueSaveFinished(not_null<const Value*> value);
void processVerificationNeeded(not_null<const Value*> value);
ScanInfo collectScanInfo(const EditFile &file) const;
@ -93,8 +97,11 @@ private:
std::vector<Scope> _scopes;
std::unique_ptr<Panel> _panel;
base::lambda<bool()> _panelHasUnsavedChanges;
BoxPointer _confirmForgetChangesBox;
Scope *_editScope = nullptr;
int _editScopeFilesIndex = -1;
std::map<not_null<const Value*>, BoxPointer> _verificationBoxes;
rpl::lifetime _lifetime;

View File

@ -16,11 +16,143 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/buttons.h"
#include "ui/widgets/shadow.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/wrap/fade_wrap.h"
#include "boxes/abstract_box.h"
#include "boxes/confirm_phone_box.h"
#include "lang/lang_keys.h"
#include "styles/style_passport.h"
#include "styles/style_boxes.h"
namespace Passport {
namespace {
class VerifyBox : public BoxContent {
public:
VerifyBox(
QWidget *parent,
const QString &title,
const QString &text,
int codeLength,
base::lambda<void(QString code)> submit,
rpl::producer<QString> call,
rpl::producer<QString> error);
void setInnerFocus() override;
protected:
void prepare() override;
private:
void setupControls(
const QString &text,
int codeLength,
base::lambda<void(QString code)> submit,
rpl::producer<QString> call,
rpl::producer<QString> error);
QString _title;
base::lambda<void()> _submit;
QPointer<SentCodeField> _code;
int _height = 0;
};
VerifyBox::VerifyBox(
QWidget *parent,
const QString &title,
const QString &text,
int codeLength,
base::lambda<void(QString code)> submit,
rpl::producer<QString> call,
rpl::producer<QString> error)
: _title(title) {
setupControls(text, codeLength, submit, std::move(call), std::move(error));
}
void VerifyBox::setupControls(
const QString &text,
int codeLength,
base::lambda<void(QString code)> submit,
rpl::producer<QString> call,
rpl::producer<QString> error) {
const auto description = Ui::CreateChild<Ui::FlatLabel>(
this,
text,
Ui::FlatLabel::InitType::Simple,
st::boxLabel);
_code = Ui::CreateChild<SentCodeField>(
this,
st::defaultInputField,
langFactory(lng_change_phone_code_title));
const auto problem = Ui::CreateChild<Ui::FadeWrap<Ui::FlatLabel>>(
this,
object_ptr<Ui::FlatLabel>(
this,
QString(),
Ui::FlatLabel::InitType::Simple,
st::passportVerifyErrorLabel));
const auto waiter = Ui::CreateChild<Ui::FlatLabel>(
this,
std::move(call),
st::passportFormLabel);
std::move(
error
) | rpl::start_with_next([=](const QString &error) {
if (error.isEmpty()) {
problem->hide(anim::type::normal);
} else {
problem->entity()->setText(error);
problem->show(anim::type::normal);
_code->showError();
}
}, lifetime());
auto y = 0;
const auto innerWidth = st::boxWidth
- st::boxPadding.left()
- st::boxPadding.right();
description->resizeToWidth(innerWidth);
description->moveToLeft(st::boxPadding.left(), y);
y += description->height() + st::boxPadding.bottom();
_code->resizeToWidth(innerWidth);
_code->moveToLeft(st::boxPadding.left(), y);
y += _code->height() + st::boxPadding.bottom();
problem->resizeToWidth(innerWidth);
problem->moveToLeft(st::boxPadding.left(), y);
y += problem->height() + st::boxPadding.top();
waiter->resizeToWidth(innerWidth);
waiter->moveToLeft(st::boxPadding.left(), y);
y += waiter->height() + st::boxPadding.bottom();
_submit = [=] {
submit(_code->getLastText());
};
if (codeLength > 0) {
_code->setAutoSubmit(codeLength, _submit);
} else {
connect(_code, &SentCodeField::submitted, _submit);
}
connect(_code, &SentCodeField::changed, [=] {
problem->hide(anim::type::normal);
});
_height = y;
}
void VerifyBox::setInnerFocus() {
_code->setFocusFast();
}
void VerifyBox::prepare() {
setTitle([=] { return _title; });
addButton(langFactory(lng_change_phone_new_submit), _submit);
addButton(langFactory(lng_cancel), [=] { closeBox(); });
setDimensions(st::boxWidth, _height);
}
} // namespace
PanelEditContact::PanelEditContact(
QWidget*,
@ -87,14 +219,17 @@ void PanelEditContact::setupControls(
_field = _content->add(
object_ptr<Ui::InputField>(
_content,
st::passportDetailsField),
st::passportDetailsField,
nullptr,
data),
st::passportContactNewFieldPadding);
} else {
_field = _content->add(
object_ptr<Ui::InputField>(
_content,
st::passportContactField,
_scheme.newPlaceholder),
_scheme.newPlaceholder,
data),
st::passportContactFieldPadding);
}
_content->add(
@ -107,11 +242,13 @@ void PanelEditContact::setupControls(
st::passportFormLabel),
st::passportFormLabelPadding));
_done->addClickHandler([=] {
const auto submit = [=] {
crl::on_main(this, [=] {
save();
});
});
};
connect(_field, &Ui::InputField::submitted, submit);
_done->addClickHandler(submit);
}
void PanelEditContact::focusInEvent(QFocusEvent *e) {
@ -148,4 +285,33 @@ void PanelEditContact::save(const QString &value) {
_controller->saveScope(std::move(data), {});
}
object_ptr<BoxContent> VerifyPhoneBox(
const QString &phone,
int codeLength,
base::lambda<void(QString code)> submit,
rpl::producer<QString> call,
rpl::producer<QString> error) {
return Box<VerifyBox>(
lang(lng_passport_phone_title),
lng_passport_confirm_phone(lt_phone, App::formatPhone(phone)),
codeLength,
submit,
std::move(call),
std::move(error));
}
object_ptr<BoxContent> VerifyEmailBox(
const QString &email,
int codeLength,
base::lambda<void(QString code)> submit,
rpl::producer<QString> error) {
return Box<VerifyBox>(
lang(lng_passport_email_title),
lng_passport_confirm_email(lt_email, email),
codeLength,
submit,
rpl::single(QString()),
std::move(error));
}
} // namespace Passport

View File

@ -69,4 +69,16 @@ private:
};
object_ptr<BoxContent> VerifyPhoneBox(
const QString &phone,
int codeLength,
base::lambda<void(QString code)> submit,
rpl::producer<QString> call,
rpl::producer<QString> error);
object_ptr<BoxContent> VerifyEmailBox(
const QString &email,
int codeLength,
base::lambda<void(QString code)> submit,
rpl::producer<QString> error);
} // namespace Passport

View File

@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/wrap/vertical_layout.h"
#include "ui/wrap/fade_wrap.h"
#include "boxes/abstract_box.h"
#include "boxes/confirm_box.h"
#include "lang/lang_keys.h"
#include "styles/style_widgets.h"
#include "styles/style_boxes.h"
@ -25,6 +26,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Passport {
struct PanelEditDocument::Result {
ValueMap data;
ValueMap filesData;
};
PanelEditDocument::PanelEditDocument(
QWidget*,
not_null<PanelController*> controller,
@ -144,6 +150,11 @@ void PanelEditDocument::resizeEvent(QResizeEvent *e) {
updateControlsGeometry();
}
bool PanelEditDocument::hasUnsavedChanges() const {
const auto result = collect();
return _controller->editScopeChanged(result.data, result.filesData);
}
void PanelEditDocument::updateControlsGeometry() {
const auto submitTop = height() - _done->height();
_scroll->setGeometry(0, 0, width(), submitTop);
@ -157,17 +168,23 @@ void PanelEditDocument::updateControlsGeometry() {
_scroll->updateBars();
}
void PanelEditDocument::save() {
auto data = ValueMap();
auto scanData = ValueMap();
PanelEditDocument::Result PanelEditDocument::collect() const {
auto result = Result();
for (const auto [i, field] : _details) {
const auto &row = _scheme.rows[i];
auto &fields = (row.type == Scheme::ValueType::Fields)
? data
: scanData;
fields.fields[row.key] = _details[i]->getValue();
? result.data
: result.filesData;
fields.fields[row.key] = field->getValue();
}
_controller->saveScope(std::move(data), std::move(scanData));
return result;
}
void PanelEditDocument::save() {
auto result = collect();
_controller->saveScope(
std::move(result.data),
std::move(result.filesData));
}
} // namespace Passport

View File

@ -56,11 +56,14 @@ public:
Scheme scheme,
const ValueMap &data);
bool hasUnsavedChanges() const;
protected:
void focusInEvent(QFocusEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
private:
struct Result;
void setupControls(
const ValueMap &data,
const ValueMap *scanData,
@ -71,6 +74,7 @@ private:
std::vector<ScanInfo> &&files);
void updateControlsGeometry();
Result collect() const;
void save();
not_null<PanelController*> _controller;