mirror of
https://github.com/telegramdesktop/tdesktop
synced 2024-12-27 17:13:40 +00:00
Passport phone/email verification added.
This commit is contained in:
parent
35dcbe0aa0
commit
4e2a109a46
@ -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_use_existing_email" = "Use the same email as on Telegram.";
|
||||||
"lng_passport_new_email" = "Or enter a new email";
|
"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_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
|
// Wnd specific
|
||||||
|
|
||||||
|
@ -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.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---
|
---functions---
|
||||||
|
|
||||||
|
@ -213,7 +213,7 @@ ChangePhoneBox::EnterCode::EnterCode(QWidget*, const QString &phone, const QStri
|
|||||||
, _hash(hash)
|
, _hash(hash)
|
||||||
, _codeLength(codeLength)
|
, _codeLength(codeLength)
|
||||||
, _callTimeout(callTimeout)
|
, _callTimeout(callTimeout)
|
||||||
, _call(this, [this] { sendCall(); }, [this] { updateCall(); }) {
|
, _call([this] { sendCall(); }, [this] { updateCall(); }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChangePhoneBox::EnterCode::prepare() {
|
void ChangePhoneBox::EnterCode::prepare() {
|
||||||
|
@ -76,15 +76,14 @@ void SentCodeField::fix() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SentCodeCall::SentCodeCall(QObject *parent, base::lambda_once<void()> callCallback, base::lambda<void()> updateCallback)
|
SentCodeCall::SentCodeCall(base::lambda_once<void()> callCallback, base::lambda<void()> updateCallback)
|
||||||
: _timer(parent)
|
: _call(std::move(callCallback))
|
||||||
, _call(std::move(callCallback))
|
|
||||||
, _update(std::move(updateCallback)) {
|
, _update(std::move(updateCallback)) {
|
||||||
_timer->connect(_timer, &QTimer::timeout, [this] {
|
_timer.setCallback([=] {
|
||||||
if (_status.state == State::Waiting) {
|
if (_status.state == State::Waiting) {
|
||||||
if (--_status.timeout <= 0) {
|
if (--_status.timeout <= 0) {
|
||||||
_status.state = State::Calling;
|
_status.state = State::Calling;
|
||||||
_timer->stop();
|
_timer.cancel();
|
||||||
if (_call) {
|
if (_call) {
|
||||||
_call();
|
_call();
|
||||||
}
|
}
|
||||||
@ -99,7 +98,7 @@ SentCodeCall::SentCodeCall(QObject *parent, base::lambda_once<void()> callCallba
|
|||||||
void SentCodeCall::setStatus(const Status &status) {
|
void SentCodeCall::setStatus(const Status &status) {
|
||||||
_status = status;
|
_status = status;
|
||||||
if (_status.state == State::Waiting) {
|
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)
|
ConfirmPhoneBox::ConfirmPhoneBox(QWidget*, const QString &phone, const QString &hash)
|
||||||
: _phone(phone)
|
: _phone(phone)
|
||||||
, _hash(hash)
|
, _hash(hash)
|
||||||
, _call(this, [this] { sendCall(); }, [this] { update(); }) {
|
, _call([this] { sendCall(); }, [this] { update(); }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfirmPhoneBox::sendCall() {
|
void ConfirmPhoneBox::sendCall() {
|
||||||
|
@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "boxes/abstract_box.h"
|
#include "boxes/abstract_box.h"
|
||||||
|
#include "base/timer.h"
|
||||||
#include "ui/widgets/input_fields.h"
|
#include "ui/widgets/input_fields.h"
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
@ -43,7 +44,9 @@ private:
|
|||||||
|
|
||||||
class SentCodeCall {
|
class SentCodeCall {
|
||||||
public:
|
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 {
|
enum class State {
|
||||||
Waiting,
|
Waiting,
|
||||||
@ -75,7 +78,7 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
Status _status;
|
Status _status;
|
||||||
object_ptr<QTimer> _timer;
|
base::Timer _timer;
|
||||||
base::lambda_once<void()> _call;
|
base::lambda_once<void()> _call;
|
||||||
base::lambda<void()> _update;
|
base::lambda<void()> _update;
|
||||||
|
|
||||||
|
@ -30,6 +30,9 @@ passportPasswordHintLabel: passportPasswordLabel;
|
|||||||
passportErrorLabel: FlatLabel(passportPasswordLabel) {
|
passportErrorLabel: FlatLabel(passportPasswordLabel) {
|
||||||
textFg: boxTextFgError;
|
textFg: boxTextFgError;
|
||||||
}
|
}
|
||||||
|
passportVerifyErrorLabel: FlatLabel(passportErrorLabel) {
|
||||||
|
align: align(topleft);
|
||||||
|
}
|
||||||
|
|
||||||
passportPanelWidth: 392px;
|
passportPanelWidth: 392px;
|
||||||
passportPanelHeight: 600px;
|
passportPanelHeight: 600px;
|
||||||
|
@ -451,16 +451,84 @@ auto FormController::scanUpdated() const
|
|||||||
return _scanUpdated.events();
|
return _scanUpdated.events();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto FormController::valueSaved() const
|
auto FormController::valueSaveFinished() const
|
||||||
->rpl::producer<not_null<const Value*>> {
|
-> rpl::producer<not_null<const Value*>> {
|
||||||
return _valueSaved.events();
|
return _valueSaveFinished.events();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto FormController::verificationNeeded() const
|
auto FormController::verificationNeeded() const
|
||||||
->rpl::producer<not_null<const Value*>> {
|
-> rpl::producer<not_null<const Value*>> {
|
||||||
return _verificationNeeded.events();
|
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 {
|
const Form &FormController::form() const {
|
||||||
return _form;
|
return _form;
|
||||||
}
|
}
|
||||||
@ -475,15 +543,16 @@ not_null<Value*> FormController::findValue(not_null<const Value*> value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void FormController::startValueEdit(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;
|
return;
|
||||||
}
|
}
|
||||||
const auto nonconst = findValue(value);
|
|
||||||
loadFiles(nonconst->files);
|
loadFiles(nonconst->files);
|
||||||
nonconst->filesInEdit = ranges::view::all(
|
nonconst->filesInEdit = ranges::view::all(
|
||||||
value->files
|
nonconst->files
|
||||||
) | ranges::view::transform([=](const File &file) {
|
) | ranges::view::transform([=](const File &file) {
|
||||||
return EditFile(value, file, nullptr);
|
return EditFile(nonconst, file, nullptr);
|
||||||
}) | ranges::to_vector;
|
}) | ranges::to_vector;
|
||||||
nonconst->data.parsedInEdit = nonconst->data.parsed;
|
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) {
|
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;
|
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);
|
const auto nonconst = findValue(value);
|
||||||
nonconst->filesInEdit.clear();
|
clearValueVerification(nonconst);
|
||||||
nonconst->data.encryptedSecretInEdit.clear();
|
if (!savingValue(nonconst)) {
|
||||||
nonconst->data.hashInEdit.clear();
|
valueEditFailed(nonconst);
|
||||||
nonconst->data.parsedInEdit = ValueMap();
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
bool FormController::isEncryptedValue(Value::Type type) const {
|
||||||
return (type != Value::Type::Phone && type != Value::Type::Email);
|
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(
|
void FormController::saveValueEdit(
|
||||||
not_null<const Value*> value,
|
not_null<const Value*> value,
|
||||||
ValueMap &&data) {
|
ValueMap &&data) {
|
||||||
if (value->saveRequestId) {
|
if (savingValue(value)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto nonconst = findValue(value);
|
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);
|
nonconst->data.parsedInEdit = std::move(data);
|
||||||
|
|
||||||
if (isEncryptedValue(nonconst->type)) {
|
if (isEncryptedValue(nonconst->type)) {
|
||||||
@ -699,7 +852,7 @@ void FormController::saveEncryptedValue(not_null<Value*> value) {
|
|||||||
void FormController::savePlainTextValue(not_null<Value*> value) {
|
void FormController::savePlainTextValue(not_null<Value*> value) {
|
||||||
Expects(!isEncryptedValue(value->type));
|
Expects(!isEncryptedValue(value->type));
|
||||||
|
|
||||||
const auto text = value->data.parsedInEdit.fields["value"];
|
const auto text = getPlainTextFromValue(value);
|
||||||
const auto type = [&] {
|
const auto type = [&] {
|
||||||
switch (value->type) {
|
switch (value->type) {
|
||||||
case Value::Type::Phone: return MTP_secureValueTypePhone();
|
case Value::Type::Phone: return MTP_secureValueTypePhone();
|
||||||
@ -734,6 +887,8 @@ void FormController::sendSaveRequest(
|
|||||||
)).done([=](const MTPSecureValue &result) {
|
)).done([=](const MTPSecureValue &result) {
|
||||||
Expects(result.type() == mtpc_secureValue);
|
Expects(result.type() == mtpc_secureValue);
|
||||||
|
|
||||||
|
value->saveRequestId = 0;
|
||||||
|
|
||||||
const auto &data = result.c_secureValue();
|
const auto &data = result.c_secureValue();
|
||||||
value->files = parseFiles(
|
value->files = parseFiles(
|
||||||
data.vfiles.v,
|
data.vfiles.v,
|
||||||
@ -743,26 +898,146 @@ void FormController::sendSaveRequest(
|
|||||||
value->data.parsed = std::move(value->data.parsedInEdit);
|
value->data.parsed = std::move(value->data.parsedInEdit);
|
||||||
value->data.hash = std::move(value->data.hashInEdit);
|
value->data.hash = std::move(value->data.hashInEdit);
|
||||||
|
|
||||||
value->saveRequestId = 0;
|
_valueSaveFinished.fire_copy(value);
|
||||||
|
|
||||||
_valueSaved.fire_copy(value);
|
|
||||||
}).fail([=](const RPCError &error) {
|
}).fail([=](const RPCError &error) {
|
||||||
value->saveRequestId = 0;
|
value->saveRequestId = 0;
|
||||||
if (error.type() == qstr("PHONE_VERIFICATION_NEEDED")) {
|
if (error.type() == qstr("PHONE_VERIFICATION_NEEDED")) {
|
||||||
if (value->type == Value::Type::Phone) {
|
if (value->type == Value::Type::Phone) {
|
||||||
_verificationNeeded.fire_copy(value);
|
startPhoneVerification(value);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else if (error.type() == qstr("EMAIL_VERIFICATION_NEEDED")) {
|
} else if (error.type() == qstr("EMAIL_VERIFICATION_NEEDED")) {
|
||||||
if (value->type == Value::Type::Email) {
|
if (value->type == Value::Type::Email) {
|
||||||
_verificationNeeded.fire_copy(value);
|
startEmailVerification(value);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_view->show(Box<InformBox>("Error saving value:\n" + error.type()));
|
valueSaveFailed(value, error);
|
||||||
}).send();
|
}).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) {
|
void FormController::generateSecret(bytes::const_span password) {
|
||||||
if (_saveSecretRequestId) {
|
if (_saveSecretRequestId) {
|
||||||
return;
|
return;
|
||||||
|
@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "mtproto/sender.h"
|
#include "mtproto/sender.h"
|
||||||
|
#include "boxes/confirm_phone_box.h"
|
||||||
#include "base/weak_ptr.h"
|
#include "base/weak_ptr.h"
|
||||||
|
|
||||||
class BoxContent;
|
class BoxContent;
|
||||||
@ -111,6 +112,16 @@ struct ValueData {
|
|||||||
bytes::vector encryptedSecretInEdit;
|
bytes::vector encryptedSecretInEdit;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct Verification {
|
||||||
|
mtpRequestId requestId = 0;
|
||||||
|
QString phoneCodeHash;
|
||||||
|
int codeLength = 0;
|
||||||
|
std::unique_ptr<SentCodeCall> call;
|
||||||
|
|
||||||
|
QString error;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
struct Value {
|
struct Value {
|
||||||
enum class Type {
|
enum class Type {
|
||||||
PersonalDetails,
|
PersonalDetails,
|
||||||
@ -135,6 +146,9 @@ struct Value {
|
|||||||
std::vector<EditFile> filesInEdit;
|
std::vector<EditFile> filesInEdit;
|
||||||
base::optional<File> selfie;
|
base::optional<File> selfie;
|
||||||
base::optional<EditFile> selfieInEdit;
|
base::optional<EditFile> selfieInEdit;
|
||||||
|
Verification verification;
|
||||||
|
|
||||||
|
int editScreens = 0;
|
||||||
mtpRequestId saveRequestId = 0;
|
mtpRequestId saveRequestId = 0;
|
||||||
|
|
||||||
};
|
};
|
||||||
@ -205,13 +219,20 @@ public:
|
|||||||
QString defaultPhoneNumber() const;
|
QString defaultPhoneNumber() const;
|
||||||
|
|
||||||
rpl::producer<not_null<const EditFile*>> scanUpdated() 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*>> verificationNeeded() const;
|
||||||
|
rpl::producer<not_null<const Value*>> verificationUpdate() const;
|
||||||
|
void verify(not_null<const Value*> value, const QString &code);
|
||||||
|
|
||||||
const Form &form() const;
|
const Form &form() const;
|
||||||
void startValueEdit(not_null<const Value*> value);
|
void startValueEdit(not_null<const Value*> value);
|
||||||
void cancelValueEdit(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);
|
void saveValueEdit(not_null<const Value*> value, ValueMap &&data);
|
||||||
|
bool savingValue(not_null<const Value*> value) const;
|
||||||
|
|
||||||
void cancel();
|
void cancel();
|
||||||
|
|
||||||
@ -274,6 +295,21 @@ private:
|
|||||||
void scanUploadFail(const FullMsgId &fullId);
|
void scanUploadFail(const FullMsgId &fullId);
|
||||||
void scanDeleteRestore(not_null<const Value*> value, int fileIndex, bool deleted);
|
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;
|
bool isEncryptedValue(Value::Type type) const;
|
||||||
void saveEncryptedValue(not_null<Value*> value);
|
void saveEncryptedValue(not_null<Value*> value);
|
||||||
void savePlainTextValue(not_null<Value*> value);
|
void savePlainTextValue(not_null<Value*> value);
|
||||||
@ -295,8 +331,9 @@ private:
|
|||||||
std::map<FileKey, std::unique_ptr<mtpFileLoader>> _fileLoaders;
|
std::map<FileKey, std::unique_ptr<mtpFileLoader>> _fileLoaders;
|
||||||
|
|
||||||
rpl::event_stream<not_null<const EditFile*>> _scanUpdated;
|
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*>> _verificationNeeded;
|
||||||
|
rpl::event_stream<not_null<const Value*>> _verificationUpdate;
|
||||||
|
|
||||||
bytes::vector _secret;
|
bytes::vector _secret;
|
||||||
uint64 _secretId = 0;
|
uint64 _secretId = 0;
|
||||||
|
@ -83,7 +83,9 @@ void Panel::updateTitlePosition() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rpl::producer<> Panel::backRequests() const {
|
rpl::producer<> Panel::backRequests() const {
|
||||||
return _back->entity()->clicks();
|
return rpl::merge(
|
||||||
|
_back->entity()->clicks(),
|
||||||
|
_synteticBackRequests.events());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Panel::setBackAllowed(bool allowed) {
|
void Panel::setBackAllowed(bool allowed) {
|
||||||
@ -100,10 +102,11 @@ void Panel::showAndActivate() {
|
|||||||
setFocus();
|
setFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Panel::eventHook(QEvent *e) {
|
void Panel::keyPressEvent(QKeyEvent *e) {
|
||||||
if (e->type() == QEvent::WindowDeactivate) {
|
if (e->key() == Qt::Key_Escape && _back->toggled()) {
|
||||||
|
_synteticBackRequests.fire({});
|
||||||
}
|
}
|
||||||
return RpWidget::eventHook(e);
|
return RpWidget::keyPressEvent(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Panel::initLayout() {
|
void Panel::initLayout() {
|
||||||
@ -276,6 +279,10 @@ void Panel::showInner(base::unique_qptr<Ui::RpWidget> inner) {
|
|||||||
}, _inner->lifetime());
|
}, _inner->lifetime());
|
||||||
_inner->show();
|
_inner->show();
|
||||||
|
|
||||||
|
if (_layer) {
|
||||||
|
_layer->raise();
|
||||||
|
}
|
||||||
|
|
||||||
showAndActivate();
|
showAndActivate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ protected:
|
|||||||
void mouseMoveEvent(QMouseEvent *e) override;
|
void mouseMoveEvent(QMouseEvent *e) override;
|
||||||
void leaveEventHook(QEvent *e) override;
|
void leaveEventHook(QEvent *e) override;
|
||||||
void leaveToChildEvent(QEvent *e, QWidget *child) override;
|
void leaveToChildEvent(QEvent *e, QWidget *child) override;
|
||||||
bool eventHook(QEvent *e) override;
|
void keyPressEvent(QKeyEvent *e) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void initControls();
|
void initControls();
|
||||||
@ -83,6 +83,7 @@ private:
|
|||||||
object_ptr<Ui::RpWidget> _body;
|
object_ptr<Ui::RpWidget> _body;
|
||||||
base::unique_qptr<Ui::RpWidget> _inner;
|
base::unique_qptr<Ui::RpWidget> _inner;
|
||||||
object_ptr<Window::LayerStackWidget> _layer = { nullptr };
|
object_ptr<Window::LayerStackWidget> _layer = { nullptr };
|
||||||
|
rpl::event_stream<> _synteticBackRequests;
|
||||||
|
|
||||||
bool _useTransparency = true;
|
bool _useTransparency = true;
|
||||||
style::margins _padding;
|
style::margins _padding;
|
||||||
|
@ -225,6 +225,19 @@ PanelController::PanelController(not_null<FormController*> form)
|
|||||||
_panel->showForm();
|
_panel->showForm();
|
||||||
}
|
}
|
||||||
}, lifetime());
|
}, 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);
|
_scopes = ComputeScopes(_form);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -417,8 +430,8 @@ void PanelController::editScope(int index) {
|
|||||||
auto content = [&]() -> object_ptr<Ui::RpWidget> {
|
auto content = [&]() -> object_ptr<Ui::RpWidget> {
|
||||||
switch (_editScope->type) {
|
switch (_editScope->type) {
|
||||||
case Scope::Type::Identity:
|
case Scope::Type::Identity:
|
||||||
case Scope::Type::Address:
|
case Scope::Type::Address: {
|
||||||
return (_editScopeFilesIndex >= 0)
|
auto result = (_editScopeFilesIndex >= 0)
|
||||||
? object_ptr<PanelEditDocument>(
|
? object_ptr<PanelEditDocument>(
|
||||||
_panel.get(),
|
_panel.get(),
|
||||||
this,
|
this,
|
||||||
@ -431,10 +444,17 @@ void PanelController::editScope(int index) {
|
|||||||
this,
|
this,
|
||||||
std::move(GetDocumentScheme(_editScope->type)),
|
std::move(GetDocumentScheme(_editScope->type)),
|
||||||
_editScope->fields->data.parsedInEdit);
|
_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::Phone:
|
||||||
case Scope::Type::Email: {
|
case Scope::Type::Email: {
|
||||||
const auto &parsed = _editScope->fields->data.parsedInEdit;
|
const auto &parsed = _editScope->fields->data.parsedInEdit;
|
||||||
const auto valueIt = parsed.fields.find("value");
|
const auto valueIt = parsed.fields.find("value");
|
||||||
|
_panelHasUnsavedChanges = nullptr;
|
||||||
return object_ptr<PanelEditContact>(
|
return object_ptr<PanelEditContact>(
|
||||||
_panel.get(),
|
_panel.get(),
|
||||||
this,
|
this,
|
||||||
@ -448,48 +468,106 @@ void PanelController::editScope(int index) {
|
|||||||
Unexpected("Type in PanelController::editScope().");
|
Unexpected("Type in PanelController::editScope().");
|
||||||
}();
|
}();
|
||||||
|
|
||||||
_panel->setBackAllowed(true);
|
|
||||||
|
|
||||||
_panel->backRequests(
|
|
||||||
) | rpl::start_with_next([=] {
|
|
||||||
_panel->showForm();
|
|
||||||
}, content->lifetime());
|
|
||||||
|
|
||||||
content->lifetime().add([=] {
|
content->lifetime().add([=] {
|
||||||
cancelValueEdit();
|
cancelValueEdit();
|
||||||
});
|
});
|
||||||
|
|
||||||
_form->valueSaved(
|
_panel->setBackAllowed(true);
|
||||||
) | rpl::start_with_next([=](not_null<const Value*> value) {
|
|
||||||
processValueSaved(value);
|
_panel->backRequests(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
cancelEditScope();
|
||||||
}, content->lifetime());
|
}, content->lifetime());
|
||||||
|
|
||||||
_form->verificationNeeded(
|
_form->valueSaveFinished(
|
||||||
) | rpl::start_with_next([=](not_null<const Value*> value) {
|
) | rpl::start_with_next([=](not_null<const Value*> value) {
|
||||||
processVerificationNeeded(value);
|
processValueSaveFinished(value);
|
||||||
}, content->lifetime());
|
}, content->lifetime());
|
||||||
|
|
||||||
_panel->showEditValue(std::move(content));
|
_panel->showEditValue(std::move(content));
|
||||||
}
|
}
|
||||||
|
|
||||||
void PanelController::processValueSaved(not_null<const Value*> value) {
|
void PanelController::processValueSaveFinished(
|
||||||
|
not_null<const Value*> value) {
|
||||||
Expects(_editScope != nullptr);
|
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 value1 = _editScope->fields;
|
||||||
const auto value2 = (_editScopeFilesIndex >= 0)
|
const auto value2 = (_editScopeFilesIndex >= 0)
|
||||||
? _editScope->files[_editScopeFilesIndex].get()
|
? _editScope->files[_editScopeFilesIndex].get()
|
||||||
: nullptr;
|
: nullptr;
|
||||||
if (value == value1 || value == value2) {
|
if (value == value1 || value == value2) {
|
||||||
if (!value1->saveRequestId && (!value2 || !value2->saveRequestId)) {
|
if (!_form->savingValue(value1)
|
||||||
|
&& (!value2 || !_form->savingValue(value2))) {
|
||||||
_panel->showForm();
|
_panel->showForm();
|
||||||
show(Box<InformBox>("Saved"));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PanelController::processVerificationNeeded(
|
void PanelController::processVerificationNeeded(
|
||||||
not_null<const Value*> value) {
|
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(
|
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() {
|
void PanelController::cancelAuth() {
|
||||||
_form->cancel();
|
_form->cancel();
|
||||||
}
|
}
|
||||||
|
@ -71,6 +71,10 @@ public:
|
|||||||
|
|
||||||
void editScope(int index) override;
|
void editScope(int index) override;
|
||||||
void saveScope(ValueMap &&data, ValueMap &&filesData);
|
void saveScope(ValueMap &&data, ValueMap &&filesData);
|
||||||
|
bool editScopeChanged(
|
||||||
|
const ValueMap &data,
|
||||||
|
const ValueMap &filesData) const;
|
||||||
|
void cancelEditScope();
|
||||||
|
|
||||||
void showBox(object_ptr<BoxContent> box) override;
|
void showBox(object_ptr<BoxContent> box) override;
|
||||||
|
|
||||||
@ -83,7 +87,7 @@ private:
|
|||||||
|
|
||||||
void cancelValueEdit();
|
void cancelValueEdit();
|
||||||
std::vector<ScanInfo> valueFiles(const Value &value) const;
|
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);
|
void processVerificationNeeded(not_null<const Value*> value);
|
||||||
|
|
||||||
ScanInfo collectScanInfo(const EditFile &file) const;
|
ScanInfo collectScanInfo(const EditFile &file) const;
|
||||||
@ -93,8 +97,11 @@ private:
|
|||||||
std::vector<Scope> _scopes;
|
std::vector<Scope> _scopes;
|
||||||
|
|
||||||
std::unique_ptr<Panel> _panel;
|
std::unique_ptr<Panel> _panel;
|
||||||
|
base::lambda<bool()> _panelHasUnsavedChanges;
|
||||||
|
BoxPointer _confirmForgetChangesBox;
|
||||||
Scope *_editScope = nullptr;
|
Scope *_editScope = nullptr;
|
||||||
int _editScopeFilesIndex = -1;
|
int _editScopeFilesIndex = -1;
|
||||||
|
std::map<not_null<const Value*>, BoxPointer> _verificationBoxes;
|
||||||
|
|
||||||
rpl::lifetime _lifetime;
|
rpl::lifetime _lifetime;
|
||||||
|
|
||||||
|
@ -16,11 +16,143 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "ui/widgets/buttons.h"
|
#include "ui/widgets/buttons.h"
|
||||||
#include "ui/widgets/shadow.h"
|
#include "ui/widgets/shadow.h"
|
||||||
#include "ui/wrap/vertical_layout.h"
|
#include "ui/wrap/vertical_layout.h"
|
||||||
|
#include "ui/wrap/fade_wrap.h"
|
||||||
#include "boxes/abstract_box.h"
|
#include "boxes/abstract_box.h"
|
||||||
|
#include "boxes/confirm_phone_box.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "styles/style_passport.h"
|
#include "styles/style_passport.h"
|
||||||
|
#include "styles/style_boxes.h"
|
||||||
|
|
||||||
namespace Passport {
|
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(
|
PanelEditContact::PanelEditContact(
|
||||||
QWidget*,
|
QWidget*,
|
||||||
@ -87,14 +219,17 @@ void PanelEditContact::setupControls(
|
|||||||
_field = _content->add(
|
_field = _content->add(
|
||||||
object_ptr<Ui::InputField>(
|
object_ptr<Ui::InputField>(
|
||||||
_content,
|
_content,
|
||||||
st::passportDetailsField),
|
st::passportDetailsField,
|
||||||
|
nullptr,
|
||||||
|
data),
|
||||||
st::passportContactNewFieldPadding);
|
st::passportContactNewFieldPadding);
|
||||||
} else {
|
} else {
|
||||||
_field = _content->add(
|
_field = _content->add(
|
||||||
object_ptr<Ui::InputField>(
|
object_ptr<Ui::InputField>(
|
||||||
_content,
|
_content,
|
||||||
st::passportContactField,
|
st::passportContactField,
|
||||||
_scheme.newPlaceholder),
|
_scheme.newPlaceholder,
|
||||||
|
data),
|
||||||
st::passportContactFieldPadding);
|
st::passportContactFieldPadding);
|
||||||
}
|
}
|
||||||
_content->add(
|
_content->add(
|
||||||
@ -107,11 +242,13 @@ void PanelEditContact::setupControls(
|
|||||||
st::passportFormLabel),
|
st::passportFormLabel),
|
||||||
st::passportFormLabelPadding));
|
st::passportFormLabelPadding));
|
||||||
|
|
||||||
_done->addClickHandler([=] {
|
const auto submit = [=] {
|
||||||
crl::on_main(this, [=] {
|
crl::on_main(this, [=] {
|
||||||
save();
|
save();
|
||||||
});
|
});
|
||||||
});
|
};
|
||||||
|
connect(_field, &Ui::InputField::submitted, submit);
|
||||||
|
_done->addClickHandler(submit);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PanelEditContact::focusInEvent(QFocusEvent *e) {
|
void PanelEditContact::focusInEvent(QFocusEvent *e) {
|
||||||
@ -148,4 +285,33 @@ void PanelEditContact::save(const QString &value) {
|
|||||||
_controller->saveScope(std::move(data), {});
|
_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
|
} // namespace Passport
|
||||||
|
@ -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
|
} // namespace Passport
|
||||||
|
@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "ui/wrap/vertical_layout.h"
|
#include "ui/wrap/vertical_layout.h"
|
||||||
#include "ui/wrap/fade_wrap.h"
|
#include "ui/wrap/fade_wrap.h"
|
||||||
#include "boxes/abstract_box.h"
|
#include "boxes/abstract_box.h"
|
||||||
|
#include "boxes/confirm_box.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "styles/style_widgets.h"
|
#include "styles/style_widgets.h"
|
||||||
#include "styles/style_boxes.h"
|
#include "styles/style_boxes.h"
|
||||||
@ -25,6 +26,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
|
|
||||||
namespace Passport {
|
namespace Passport {
|
||||||
|
|
||||||
|
struct PanelEditDocument::Result {
|
||||||
|
ValueMap data;
|
||||||
|
ValueMap filesData;
|
||||||
|
};
|
||||||
|
|
||||||
PanelEditDocument::PanelEditDocument(
|
PanelEditDocument::PanelEditDocument(
|
||||||
QWidget*,
|
QWidget*,
|
||||||
not_null<PanelController*> controller,
|
not_null<PanelController*> controller,
|
||||||
@ -144,6 +150,11 @@ void PanelEditDocument::resizeEvent(QResizeEvent *e) {
|
|||||||
updateControlsGeometry();
|
updateControlsGeometry();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PanelEditDocument::hasUnsavedChanges() const {
|
||||||
|
const auto result = collect();
|
||||||
|
return _controller->editScopeChanged(result.data, result.filesData);
|
||||||
|
}
|
||||||
|
|
||||||
void PanelEditDocument::updateControlsGeometry() {
|
void PanelEditDocument::updateControlsGeometry() {
|
||||||
const auto submitTop = height() - _done->height();
|
const auto submitTop = height() - _done->height();
|
||||||
_scroll->setGeometry(0, 0, width(), submitTop);
|
_scroll->setGeometry(0, 0, width(), submitTop);
|
||||||
@ -157,17 +168,23 @@ void PanelEditDocument::updateControlsGeometry() {
|
|||||||
_scroll->updateBars();
|
_scroll->updateBars();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PanelEditDocument::save() {
|
PanelEditDocument::Result PanelEditDocument::collect() const {
|
||||||
auto data = ValueMap();
|
auto result = Result();
|
||||||
auto scanData = ValueMap();
|
|
||||||
for (const auto [i, field] : _details) {
|
for (const auto [i, field] : _details) {
|
||||||
const auto &row = _scheme.rows[i];
|
const auto &row = _scheme.rows[i];
|
||||||
auto &fields = (row.type == Scheme::ValueType::Fields)
|
auto &fields = (row.type == Scheme::ValueType::Fields)
|
||||||
? data
|
? result.data
|
||||||
: scanData;
|
: result.filesData;
|
||||||
fields.fields[row.key] = _details[i]->getValue();
|
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
|
} // namespace Passport
|
||||||
|
@ -56,11 +56,14 @@ public:
|
|||||||
Scheme scheme,
|
Scheme scheme,
|
||||||
const ValueMap &data);
|
const ValueMap &data);
|
||||||
|
|
||||||
|
bool hasUnsavedChanges() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void focusInEvent(QFocusEvent *e) override;
|
void focusInEvent(QFocusEvent *e) override;
|
||||||
void resizeEvent(QResizeEvent *e) override;
|
void resizeEvent(QResizeEvent *e) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
struct Result;
|
||||||
void setupControls(
|
void setupControls(
|
||||||
const ValueMap &data,
|
const ValueMap &data,
|
||||||
const ValueMap *scanData,
|
const ValueMap *scanData,
|
||||||
@ -71,6 +74,7 @@ private:
|
|||||||
std::vector<ScanInfo> &&files);
|
std::vector<ScanInfo> &&files);
|
||||||
void updateControlsGeometry();
|
void updateControlsGeometry();
|
||||||
|
|
||||||
|
Result collect() const;
|
||||||
void save();
|
void save();
|
||||||
|
|
||||||
not_null<PanelController*> _controller;
|
not_null<PanelController*> _controller;
|
||||||
|
Loading…
Reference in New Issue
Block a user