mirror of
https://github.com/telegramdesktop/tdesktop
synced 2024-12-27 00:53:22 +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_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
|
||||
|
||||
|
@ -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---
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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() {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -30,6 +30,9 @@ passportPasswordHintLabel: passportPasswordLabel;
|
||||
passportErrorLabel: FlatLabel(passportPasswordLabel) {
|
||||
textFg: boxTextFgError;
|
||||
}
|
||||
passportVerifyErrorLabel: FlatLabel(passportErrorLabel) {
|
||||
align: align(topleft);
|
||||
}
|
||||
|
||||
passportPanelWidth: 392px;
|
||||
passportPanelHeight: 600px;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user