Handle errors when saving passport values.

This commit is contained in:
John Preston 2018-04-15 19:06:53 +04:00
parent 4f1a633019
commit 03b7a3ca2b
13 changed files with 329 additions and 88 deletions

View File

@ -1604,6 +1604,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_passport_success" = "Authorization successfull!";
"lng_passport_stop_sure" = "Are you sure you want to stop this authorization?";
"lng_passport_stop" = "Stop";
"lng_passport_restart_sure" = "Unexpected error has occurred. Perhaps some changes were done from a different Telegram application. Would you like to restart this authorization?";
"lng_passport_restart" = "Restart";
// Wnd specific

View File

@ -30,5 +30,29 @@ inline QString ProxyConfigError() {
return qsl("The proxy you are using is not configured correctly and will be disabled. Please find another one.");
}
inline QString NoAuthorizationBot() {
return qsl("Could not get authorization bot.");
}
inline QString SecureSaveError() {
return qsl("Error saving value.");
}
inline QString SecureAcceptError() {
return qsl("Error acception form.");
}
inline QString PassportCorrupted() {
return qsl("It seems your Telegram Passport data was corrupted.\n\nYou can reset your Telegram Passport and restart this authorization.");
}
inline QString PassportCorruptedReset() {
return qsl("Reset");
}
inline QString PassportCorruptedResetSure() {
return qsl("Are you sure you want to reset your Telegram Passport data?");
}
} // namespace Hard
} // namespace Lang

View File

@ -137,8 +137,10 @@ passportFormPolicy: FlatLabel(passportFormLabel) {
}
}
passportFormPolicyPadding: margins(22px, 7px, 22px, 28px);
passportContactNewFieldPadding: margins(22px, 0px, 22px, 28px);
passportContactFieldPadding: margins(22px, 14px, 22px, 28px);
passportContactNewFieldPadding: margins(22px, 0px, 22px, 14px);
passportContactFieldPadding: margins(22px, 14px, 22px, 14px);
passportContactErrorPadding: margins(22px, 0px, 22px, 0px);
passportContactErrorMargin: margins(0px, 0px, 0px, 14px);
passportRowPadding: margins(22px, 8px, 25px, 8px);
passportRowIconSkip: 10px;

View File

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "passport/passport_panel_controller.h"
#include "boxes/confirm_box.h"
#include "lang/lang_keys.h"
#include "lang/lang_hardcoded.h"
#include "base/openssl_help.h"
#include "base/qthelp_url.h"
#include "mainwindow.h"
@ -28,6 +29,35 @@ namespace {
constexpr auto kDocumentScansLimit = 20;
bool ForwardServiceErrorRequired(const QString &error) {
return (error == qstr("BOT_INVALID"))
|| (error == qstr("PUBLIC_KEY_REQUIRED"))
|| (error == qstr("PUBLIC_KEY_INVALID"))
|| (error == qstr("SCOPE_EMPTY"))
|| (error == qstr("PAYLOAD_EMPTY"));
}
bool SaveErrorRequiresRestart(const QString &error) {
return (error == qstr("PASSWORD_REQUIRED"))
|| (error == qstr("SECURE_SECRET_REQUIRED"))
|| (error == qstr("SECURE_SECRET_INVALID"));
}
bool AcceptErrorRequiresRestart(const QString &error) {
return (error == qstr("PASSWORD_REQUIRED"))
|| (error == qstr("SECURE_SECRET_REQUIRED"))
|| (error == qstr("SECURE_VALUE_EMPTY"))
|| (error == qstr("SECURE_VALUE_HASH_INVALID"));
}
std::map<QString, QString> GetTexts(const ValueMap &map) {
auto result = std::map<QString, QString>();
for (const auto &[key, value] : map.fields) {
result[key] = value.text;
}
return result;
}
QImage ReadImage(bytes::const_span buffer) {
return App::readImage(QByteArray::fromRawData(
reinterpret_cast<const char*>(buffer.data()),
@ -137,15 +167,15 @@ EditFile::EditFile(
not_null<const Value*> value,
const File &fields,
std::unique_ptr<UploadScanData> &&uploadData)
: value(value)
, fields(std::move(fields))
, uploadData(std::move(uploadData))
, guard(std::make_shared<bool>(true)) {
: value(value)
, fields(std::move(fields))
, uploadData(std::move(uploadData))
, guard(std::make_shared<bool>(true)) {
}
UploadScanDataPointer::UploadScanDataPointer(
std::unique_ptr<UploadScanData> &&value)
: _value(std::move(value)) {
: _value(std::move(value)) {
}
UploadScanDataPointer::UploadScanDataPointer(
@ -211,6 +241,7 @@ bytes::vector FormController::passwordHashForAuth(
}
auto FormController::prepareFinalData() -> FinalData {
auto errors = std::vector<not_null<const Value*>>();
auto hashes = QVector<MTPSecureValueHash>();
auto secureData = QJsonObject();
const auto addValueToJSON = [&](
@ -244,13 +275,11 @@ auto FormController::prepareFinalData() -> FinalData {
addValueToJSON(key, value);
}
};
auto hasErrors = false;
const auto scopes = ComputeScopes(this);
for (const auto &scope : scopes) {
const auto ready = ComputeScopeRowReadyString(scope);
if (ready.isEmpty()) {
hasErrors = true;
findValue(scope.fields)->error = QString();
errors.push_back(scope.fields);
continue;
}
addValue(scope.fields);
@ -263,28 +292,28 @@ auto FormController::prepareFinalData() -> FinalData {
}
}
}
if (hasErrors) {
return {};
}
auto json = QJsonObject();
json.insert("secure_data", secureData);
json.insert("payload", _request.payload);
if (errors.empty()) {
json.insert("secure_data", secureData);
json.insert("payload", _request.payload);
}
return {
hashes,
QJsonDocument(json).toJson(QJsonDocument::Compact)
QJsonDocument(json).toJson(QJsonDocument::Compact),
errors
};
}
bool FormController::submit() {
std::vector<not_null<const Value*>> FormController::submitGetErrors() {
if (_submitRequestId || _submitSuccess|| _cancelled) {
return true;
return {};
}
const auto prepared = prepareFinalData();
if (prepared.hashes.empty()) {
return false;
if (!prepared.errors.empty()) {
return prepared.errors;
}
const auto credentialsEncryptedData = EncryptData(
bytes::make_span(prepared.credentials));
@ -313,10 +342,15 @@ bool FormController::submit() {
[=] { cancel(); });
}).fail([=](const RPCError &error) {
_submitRequestId = 0;
_view->show(Box<InformBox>(
"Failed sending data :(\n" + error.type()));
if (AcceptErrorRequiresRestart(error.type())) {
suggestRestart();
} else {
_view->show(Box<InformBox>(
Lang::Hard::SecureAcceptError() + "\n" + error.type()));
}
}).send();
return true;
return {};
}
void FormController::submitPassword(const QString &password) {
@ -407,10 +441,14 @@ void FormController::decryptValue(Value &value) {
return;
}
if (!value.data.original.isEmpty()) {
value.data.parsed.fields = DeserializeData(DecryptData(
const auto fields = DeserializeData(DecryptData(
bytes::make_span(value.data.original),
value.data.hash,
value.data.secret));
value.data.parsed.fields.clear();
for (const auto [key, text] : fields) {
value.data.parsed.fields[key] = { text };
}
}
}
@ -991,11 +1029,11 @@ bool FormController::editValueChanged(
for (const auto &[key, value] : data.fields) {
const auto i = existing.find(key);
if (i != existing.end()) {
if (i->second != value) {
if (i->second.text != value.text) {
return true;
}
existing.erase(i);
} else if (!value.isEmpty()) {
} else if (!value.text.isEmpty()) {
return true;
}
}
@ -1018,7 +1056,6 @@ void FormController::saveValueEdit(
base::take(nonconst->data.encryptedSecretInEdit);
base::take(nonconst->data.hashInEdit);
base::take(nonconst->data.parsedInEdit);
base::take(nonconst->error);
nonconst->saveRequestId = 0;
_valueSaveFinished.fire_copy(nonconst);
});
@ -1049,7 +1086,7 @@ void FormController::deleteValueEdit(not_null<const Value*> value) {
_valueSaveFinished.fire_copy(value);
}).fail([=](const RPCError &error) {
nonconst->saveRequestId = 0;
valueSaveFailed(nonconst, error);
valueSaveShowError(nonconst, error);
}).send();
}
@ -1090,7 +1127,7 @@ void FormController::saveEncryptedValue(not_null<Value*> value) {
value->data.secret = GenerateSecretBytes();
}
const auto encryptedData = EncryptData(
SerializeData(value->data.parsedInEdit.fields),
SerializeData(GetTexts(value->data.parsedInEdit)),
value->data.secret);
value->data.hashInEdit = encryptedData.hash;
value->data.encryptedSecretInEdit = EncryptValueSecret(
@ -1197,18 +1234,37 @@ void FormController::sendSaveRequest(
_valueSaveFinished.fire_copy(value);
}).fail([=](const RPCError &error) {
value->saveRequestId = 0;
if (error.type() == qstr("PHONE_VERIFICATION_NEEDED")) {
const auto code = error.type();
if (code == qstr("PHONE_VERIFICATION_NEEDED")) {
if (value->type == Value::Type::Phone) {
startPhoneVerification(value);
return;
}
} else if (error.type() == qstr("EMAIL_VERIFICATION_NEEDED")) {
} else if (code == qstr("PHONE_NUMBER_INVALID")) {
if (value->type == Value::Type::Phone) {
value->data.parsedInEdit.fields["value"].error
= lang(lng_bad_phone);
valueSaveFailed(value);
return;
}
} else if (code == qstr("EMAIL_VERIFICATION_NEEDED")) {
if (value->type == Value::Type::Email) {
startEmailVerification(value);
return;
}
} else if (code == qstr("EMAIL_INVALID")) {
if (value->type == Value::Type::Email) {
value->data.parsedInEdit.fields["value"].error
= lang(lng_cloud_password_bad_email);
valueSaveFailed(value);
return;
}
}
if (SaveErrorRequiresRestart(code)) {
suggestRestart();
} else {
valueSaveShowError(value, error);
}
valueSaveFailed(value, error);
}).send();
}
@ -1233,7 +1289,7 @@ QString FormController::getPlainTextFromValue(
const auto i = value->data.parsedInEdit.fields.find("value");
Assert(i != end(value->data.parsedInEdit.fields));
return i->second;
return i->second.text;
}
void FormController::startPhoneVerification(not_null<Value*> value) {
@ -1291,7 +1347,7 @@ void FormController::startPhoneVerification(not_null<Value*> value) {
_verificationNeeded.fire_copy(value);
}).fail([=](const RPCError &error) {
value->verification.requestId = 0;
valueSaveFailed(value, error);
valueSaveShowError(value, error);
}).send();
}
@ -1308,7 +1364,7 @@ void FormController::startEmailVerification(not_null<Value*> value) {
: -1;
_verificationNeeded.fire_copy(value);
}).fail([=](const RPCError &error) {
valueSaveFailed(value, error);
valueSaveShowError(value, error);
}).send();
}
@ -1326,10 +1382,15 @@ void FormController::requestPhoneCall(not_null<Value*> value) {
}).send();
}
void FormController::valueSaveFailed(
void FormController::valueSaveShowError(
not_null<Value*> value,
const RPCError &error) {
_view->show(Box<InformBox>("Error saving value:\n" + error.type()));
_view->show(Box<InformBox>(
Lang::Hard::SecureSaveError() + "\n" + error.type()));
valueSaveFailed(value);
}
void FormController::valueSaveFailed(not_null<Value*> value) {
valueEditFailed(value);
_valueSaveFinished.fire_copy(value);
}
@ -1378,16 +1439,24 @@ void FormController::generateSecret(bytes::const_span password) {
callback();
}
}).fail([=](const RPCError &error) {
// #TODO wrong password hash error?
Ui::show(Box<InformBox>("Saving encrypted value failed."));
_saveSecretRequestId = 0;
suggestRestart();
}).send();
}
void FormController::suggestRestart() {
_suggestingRestart = true;
_view->show(Box<ConfirmBox>(
lang(lng_passport_restart_sure),
lang(lng_passport_restart),
[=] { _controller->showPassportForm(_request); },
[=] { cancel(); }));
}
void FormController::requestForm() {
if (_request.payload.isEmpty()) {
_formRequestId = -1;
Ui::show(Box<InformBox>(lang(lng_passport_form_error)));
formFail("PAYLOAD_EMPTY");
return;
}
_formRequestId = request(MTPaccount_GetAuthorizationForm(
@ -1398,7 +1467,7 @@ void FormController::requestForm() {
_formRequestId = 0;
formDone(result);
}).fail([=](const RPCError &error) {
formFail(error);
formFail(error.type());
}).send();
}
@ -1496,11 +1565,11 @@ auto FormController::parseValue(
switch (data.vplain_data.type()) {
case mtpc_securePlainPhone: {
const auto &fields = data.vplain_data.c_securePlainPhone();
result.data.parsed.fields["value"] = qs(fields.vphone);
result.data.parsed.fields["value"].text = qs(fields.vphone);
} break;
case mtpc_securePlainEmail: {
const auto &fields = data.vplain_data.c_securePlainEmail();
result.data.parsed.fields["value"] = qs(fields.vemail);
result.data.parsed.fields["value"].text = qs(fields.vemail);
} break;
}
}
@ -1596,8 +1665,10 @@ void FormController::parseForm(const MTPaccount_AuthorizationForm &result) {
_bot = App::userLoaded(_request.botId);
}
void FormController::formFail(const RPCError &error) {
Ui::show(Box<InformBox>(lang(lng_passport_form_error)));
void FormController::formFail(const QString &error) {
_serviceErrorText = error;
_view->showCriticalError(
lang(lng_passport_form_error) + "\n" + error);
}
void FormController::requestPassword() {
@ -1606,7 +1677,7 @@ void FormController::requestPassword() {
_passwordRequestId = 0;
passwordDone(result);
}).fail([=](const RPCError &error) {
formFail(error);
formFail(error.type());
}).send();
}
@ -1627,7 +1698,7 @@ void FormController::passwordDone(const MTPaccount_Password &result) {
void FormController::showForm() {
if (!_bot) {
Ui::show(Box<InformBox>("Could not get authorization bot."));
formFail(Lang::Hard::NoAuthorizationBot());
return;
}
if (!_password.salt.empty()) {
@ -1639,10 +1710,6 @@ void FormController::showForm() {
}
}
void FormController::passwordFail(const RPCError &error) {
Ui::show(Box<InformBox>("Could not get authorization form."));
}
void FormController::parsePassword(const MTPDaccount_noPassword &result) {
_password.unconfirmedPattern = qs(result.vemail_unconfirmed_pattern);
_password.newSalt = bytes::make_vector(result.vnew_salt.v);
@ -1661,25 +1728,41 @@ void FormController::parsePassword(const MTPDaccount_password &result) {
}
void FormController::cancel() {
if (!_submitSuccess) {
if (!_submitSuccess && _serviceErrorText.isEmpty()) {
_view->show(Box<ConfirmBox>(
lang(lng_passport_stop_sure),
lang(lng_passport_stop),
[=] { cancelSure(); }));
[=] { cancelSure(); },
[=] { cancelAbort(); }));
} else {
cancelSure();
}
}
void FormController::cancelAbort() {
if (_cancelled || _submitSuccess) {
return;
} else if (_suggestingRestart) {
suggestRestart();
}
}
void FormController::cancelSure() {
if (!_cancelled) {
_cancelled = true;
const auto url = qthelp::url_append_query(
_request.callbackUrl,
_submitSuccess ? "tg_passport=success" : "tg_passport=cancel");
UrlClickHandler::doOpen(url);
if (!_request.callbackUrl.isEmpty()
&& (_serviceErrorText.isEmpty()
|| ForwardServiceErrorRequired(_serviceErrorText))) {
const auto url = qthelp::url_append_query(
_request.callbackUrl,
(_submitSuccess
? "tg_passport=success"
: (_serviceErrorText.isEmpty()
? "tg_passport=cancel"
: "tg_passport=error&error=" + _serviceErrorText)));
UrlClickHandler::doOpen(url);
}
const auto timeout = _view->closeGetDuration();
App::CallDelayed(timeout, this, [=] {
_controller->clearPassportForm();

View File

@ -84,6 +84,7 @@ struct File {
int downloadOffset = 0;
QImage image;
QString error;
};
struct EditFile {
@ -99,8 +100,13 @@ struct EditFile {
bool deleted = false;
};
struct ValueField {
QString text;
QString error;
};
struct ValueMap {
std::map<QString, QString> fields;
std::map<QString, ValueField> fields;
};
struct ValueData {
@ -146,13 +152,13 @@ struct Value {
ValueData data;
std::vector<File> scans;
std::vector<EditFile> scansInEdit;
QString scanMissingError;
base::optional<File> selfie;
base::optional<EditFile> selfieInEdit;
Verification verification;
bytes::vector submitHash;
int editScreens = 0;
base::optional<QString> error;
mtpRequestId saveRequestId = 0;
};
@ -208,7 +214,7 @@ public:
void show();
UserData *bot() const;
QString privacyPolicyUrl() const;
bool submit();
std::vector<not_null<const Value*>> submitGetErrors();
void submitPassword(const QString &password);
rpl::producer<QString> passwordError() const;
QString passwordHint() const;
@ -253,6 +259,7 @@ private:
struct FinalData {
QVector<MTPSecureValueHash> hashes;
QByteArray credentials;
std::vector<not_null<const Value*>> errors;
};
EditFile *findEditFile(const FullMsgId &fullId);
EditFile *findEditFile(const FileKey &key);
@ -263,7 +270,7 @@ private:
void requestPassword();
void formDone(const MTPaccount_AuthorizationForm &result);
void formFail(const RPCError &error);
void formFail(const QString &error);
void parseForm(const MTPaccount_AuthorizationForm &result);
void showForm();
Value parseValue(
@ -280,7 +287,6 @@ private:
const std::vector<EditFile> &source) const;
void passwordDone(const MTPaccount_Password &result);
void passwordFail(const RPCError &error);
void parsePassword(const MTPDaccount_noPassword &settings);
void parsePassword(const MTPDaccount_password &settings);
bytes::vector passwordHashForAuth(bytes::const_span password) const;
@ -327,7 +333,8 @@ private:
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 valueSaveShowError(not_null<Value*> value, const RPCError &error);
void valueSaveFailed(not_null<Value*> value);
void requestPhoneCall(not_null<Value*> value);
void verificationError(
not_null<Value*> value,
@ -345,7 +352,9 @@ private:
const MTPInputSecureValue &data);
FinalData prepareFinalData();
void suggestRestart();
void cancelSure();
void cancelAbort();
not_null<Window::Controller*> _controller;
FormRequest _request;
@ -373,6 +382,8 @@ private:
rpl::event_stream<QString> _passwordError;
mtpRequestId _submitRequestId = 0;
bool _submitSuccess = false;
bool _suggestingRestart = false;
QString _serviceErrorText;
rpl::lifetime _uploaderSubscriptions;
rpl::lifetime _lifetime;

View File

@ -147,20 +147,26 @@ QString ComputeScopeRowReadyString(const Scope &scope) {
const auto i = fields.find(row.key);
if (i == end(fields)) {
return QString();
} else if (row.validate && !row.validate(i->second)) {
}
const auto text = i->second.text;
if (row.validate && !row.validate(text)) {
return QString();
}
pushListValue(format ? format(i->second) : i->second);
pushListValue(format ? format(text) : text);
} else if (scope.documents.empty()) {
continue;
} else if (!document) {
return QString();
} else {
const auto i = document->data.parsed.fields.find(row.key);
if (i == end(document->data.parsed.fields)) {
return QString();
} else if (row.validate && !row.validate(i->second)) {
}
const auto text = i->second.text;
if (row.validate && !row.validate(text)) {
return QString();
}
pushListValue(i->second);
pushListValue(text);
}
}
return list.join(", ");
@ -171,7 +177,7 @@ QString ComputeScopeRowReadyString(const Scope &scope) {
const auto &fields = scope.fields->data.parsed.fields;
const auto i = fields.find("value");
return (i != end(fields))
? (format ? format(i->second) : i->second)
? (format ? format(i->second.text) : i->second.text)
: QString();
} break;
}
@ -182,13 +188,14 @@ ScopeRow ComputeScopeRow(const Scope &scope) {
const auto addReadyError = [&](ScopeRow &&row) {
const auto ready = ComputeScopeRowReadyString(scope);
row.ready = ready;
row.error = scope.fields->error.has_value()
? (!scope.fields->error->isEmpty()
? *scope.fields->error
: !ready.isEmpty()
? ready
: row.description)
: QString();
// #TODO passport bot errors
//row.error = scope.fields->error.has_value()
// ? (!scope.fields->error->isEmpty()
// ? *scope.fields->error
// : !ready.isEmpty()
// ? ready
// : row.description)
// : QString();
return row;
};
switch (scope.type) {

View File

@ -43,6 +43,7 @@ public:
virtual void showAskPassword() = 0;
virtual void showNoPassword() = 0;
virtual void showPasswordUnconfirmed() = 0;
virtual void showCriticalError(const QString &error) = 0;
virtual void editScope(int index) = 0;
virtual void showBox(object_ptr<BoxContent> box) = 0;

View File

@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/shadow.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "ui/wrap/padding_wrap.h"
#include "ui/wrap/fade_wrap.h"
#include "lang/lang_keys.h"
#include "window/layer_widget.h"
@ -231,6 +232,23 @@ void Panel::showPasswordUnconfirmed() {
setBackAllowed(false);
}
void Panel::showCriticalError(const QString &error) {
auto container = base::make_unique_q<Ui::PaddingWrap<Ui::FlatLabel>>(
_body,
object_ptr<Ui::FlatLabel>(
_body,
error,
Ui::FlatLabel::InitType::Simple,
st::passportErrorLabel),
style::margins(0, st::passportPanelHeight / 3, 0, 0));
container->widthValue(
) | rpl::start_with_next([label = container->entity()](int width) {
label->resize(width, label->height());
}, container->lifetime());
showInner(std::move(container));
setBackAllowed(false);
}
void Panel::showForm() {
showInner(base::make_unique_q<PanelForm>(_body, _controller));
setBackAllowed(false);
@ -484,7 +502,7 @@ void Panel::paintOpaqueBorder(Painter &p) const {
}
void Panel::closeEvent(QCloseEvent *e) {
// #TODO
// #TODO passport
}
void Panel::mousePressEvent(QMouseEvent *e) {

View File

@ -39,6 +39,7 @@ public:
void showNoPassword();
void showPasswordUnconfirmed();
void showForm();
void showCriticalError(const QString &error);
void showEditValue(object_ptr<Ui::RpWidget> form);
void showBox(object_ptr<BoxContent> box);

View File

@ -359,6 +359,13 @@ void PanelController::fillRows(
}
for (const auto &scope : _scopes) {
const auto row = ComputeScopeRow(scope);
const auto main = scope.fields;
if (!row.ready.isEmpty()) {
_submitErrors.erase(
ranges::remove(_submitErrors, main),
_submitErrors.end());
}
const auto submitError = base::contains(_submitErrors, main);
callback(
row.title,
(!row.error.isEmpty()
@ -367,7 +374,7 @@ void PanelController::fillRows(
? row.ready
: row.description),
!row.ready.isEmpty(),
!row.error.isEmpty());
!row.error.isEmpty() || submitError);
}
}
@ -380,7 +387,8 @@ rpl::producer<> PanelController::refillRows() const {
}
void PanelController::submitForm() {
if (!_form->submit()) {
_submitErrors = _form->submitGetErrors();
if (!_submitErrors.empty()) {
_submitFailed.fire({});
}
}
@ -466,6 +474,10 @@ rpl::producer<ScanInfo> PanelController::scanUpdated() const {
});
}
rpl::producer<ScopeError> PanelController::saveErrors() const {
return _saveErrors.events();
}
ScanInfo PanelController::collectScanInfo(const EditFile &file) const {
Expects(_editScope != nullptr);
Expects(_editDocument != nullptr);
@ -507,7 +519,34 @@ ScanInfo PanelController::collectScanInfo(const EditFile &file) const {
status,
file.fields.image,
file.deleted,
isSelfie };
isSelfie,
file.fields.error };
}
std::vector<ScopeError> PanelController::collectErrors(
not_null<const Value*> value) const {
auto result = std::vector<ScopeError>();
if (!value->scanMissingError.isEmpty()) {
result.push_back({ FileKey(), value->scanMissingError });
}
const auto addFileError = [&](const EditFile &file) {
if (!file.fields.error.isEmpty()) {
const auto key = FileKey{ file.fields.id, file.fields.dcId };
result.push_back({ key, file.fields.error });
}
};
for (const auto &scan : value->scansInEdit) {
addFileError(scan);
}
if (value->selfieInEdit) {
addFileError(*value->selfieInEdit);
}
for (const auto &[key, value] : value->data.parsedInEdit.fields) {
if (!value.error.isEmpty()) {
result.push_back({ key, value.error });
}
}
return result;
}
auto PanelController::deleteValueLabel() const
@ -625,6 +664,11 @@ void PanelController::showPasswordUnconfirmed() {
_panel->showPasswordUnconfirmed();
}
void PanelController::showCriticalError(const QString &error) {
ensurePanelCreated();
_panel->showCriticalError(error);
}
void PanelController::ensurePanelCreated() {
if (!_panel) {
_panel = std::make_unique<Panel>(this);
@ -783,7 +827,7 @@ void PanelController::editScope(int index, int documentIndex) {
const auto valueIt = parsed.fields.find("value");
const auto value = (valueIt == end(parsed.fields)
? QString()
: valueIt->second);
: valueIt->second.text);
const auto existing = getDefaultContactValue(_editScope->type);
_panelHasUnsavedChanges = nullptr;
return object_ptr<PanelEditContact>(
@ -829,7 +873,13 @@ void PanelController::processValueSaveFinished(
}
if ((_editValue == value || _editDocument == value) && !savingScope()) {
_panel->showForm();
if (auto errors = collectErrors(value); !errors.empty()) {
for (auto &&error : errors) {
_saveErrors.fire(std::move(error));
}
} else {
_panel->showForm();
}
}
}
@ -849,7 +899,7 @@ void PanelController::processVerificationNeeded(
}
const auto textIt = value->data.parsedInEdit.fields.find("value");
Assert(textIt != end(value->data.parsedInEdit.fields));
const auto text = textIt->second;
const auto text = textIt->second.text;
const auto type = value->type;
const auto update = _form->verificationUpdate(
) | rpl::filter([=](not_null<const Value*> field) {

View File

@ -29,6 +29,16 @@ struct ScanInfo {
QImage thumb;
bool deleted = false;
bool selfie = false;
QString error;
};
struct ScopeError {
// FileKey:id != 0 - file_hash error (bad scan / selfie)
// FileKey:id == 0 - vector<file_hash> error (scan missing)
// QString - data_hash with such key error (bad value)
base::variant<FileKey, QString> key;
QString text;
};
@ -68,6 +78,7 @@ public:
void deleteSelfie();
void restoreSelfie();
rpl::producer<ScanInfo> scanUpdated() const;
rpl::producer<ScopeError> saveErrors() const;
base::optional<rpl::producer<QString>> deleteValueLabel() const;
void deleteValue();
@ -78,6 +89,7 @@ public:
void showAskPassword() override;
void showNoPassword() override;
void showPasswordUnconfirmed() override;
void showCriticalError(const QString &error) override;
void fillRows(
base::lambda<void(
@ -123,12 +135,16 @@ private:
bool hasValueDocument() const;
bool hasValueFields() const;
ScanInfo collectScanInfo(const EditFile &file) const;
std::vector<ScopeError> collectErrors(
not_null<const Value*> value) const;
QString getDefaultContactValue(Scope::Type type) const;
void deleteValueSure(bool withDetails);
not_null<FormController*> _form;
std::vector<Scope> _scopes;
rpl::event_stream<> _submitFailed;
std::vector<not_null<const Value*>> _submitErrors;
rpl::event_stream<ScopeError> _saveErrors;
std::unique_ptr<Panel> _panel;
base::lambda<bool()> _panelHasUnsavedChanges;

View File

@ -16,6 +16,7 @@ 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/slide_wrap.h"
#include "ui/wrap/fade_wrap.h"
#include "boxes/abstract_box.h"
#include "boxes/confirm_phone_box.h"
@ -260,6 +261,18 @@ void PanelEditContact::setupControls(
}, _field->lifetime());
_content->add(std::move(wrap), fieldPadding);
const auto errorWrap = _content->add(
object_ptr<Ui::SlideWrap<Ui::FlatLabel>>(
_content,
object_ptr<Ui::FlatLabel>(
_content,
QString(),
Ui::FlatLabel::InitType::Simple,
st::passportVerifyErrorLabel),
st::passportContactErrorPadding),
st::passportContactErrorMargin);
errorWrap->hide(anim::type::instant);
_content->add(
object_ptr<PanelLabel>(
_content,
@ -282,12 +295,24 @@ void PanelEditContact::setupControls(
});
}
_controller->saveErrors(
) | rpl::start_with_next([=](const ScopeError &error) {
if (error.key == QString("value")) {
_field->showError();
errorWrap->entity()->setText(error.text);
errorWrap->show(anim::type::normal);
}
}, lifetime());
const auto submit = [=] {
crl::on_main(this, [=] {
save();
});
};
connect(_field, &Ui::MaskedInputField::submitted, submit);
connect(_field, &Ui::MaskedInputField::changed, [=] {
errorWrap->hide(anim::type::normal);
});
_done->addClickHandler(submit);
}
@ -323,7 +348,7 @@ void PanelEditContact::save() {
void PanelEditContact::save(const QString &value) {
auto data = ValueMap();
data.fields["value"] = value;
data.fields["value"].text = value;
_controller->saveScope(std::move(data), {});
}

View File

@ -305,7 +305,7 @@ not_null<Ui::RpWidget*> PanelEditDocument::setupContent(
if (const auto i = fields.find(key); i != fields.end()) {
return i->second;
}
return QString();
return ValueField();
};
for (auto i = 0, count = int(_scheme.rows.size()); i != count; ++i) {
@ -316,13 +316,14 @@ not_null<Ui::RpWidget*> PanelEditDocument::setupContent(
if (!fields) {
continue;
}
const auto current = valueOrEmpty(*fields, row.key);
_details.emplace(i, inner->add(PanelDetailsRow::Create(
inner,
row.inputType,
_controller,
row.label,
valueOrEmpty(*fields, row.key),
QString(),
current.text,
current.error,
row.lengthLimit)));
}
@ -382,7 +383,7 @@ PanelEditDocument::Result PanelEditDocument::collect() const {
auto &fields = (row.valueClass == Scheme::ValueClass::Fields)
? result.data
: result.filesData;
fields.fields[row.key] = field->valueCurrent();
fields.fields[row.key].text = field->valueCurrent();
}
return result;
}