From b935d54fe7ce0a5437c39d8198eaf2c0b2a0ae91 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 14 Aug 2018 14:37:03 +0300 Subject: [PATCH] Support common error for the whole value. It is removed (considered fixed) if anything changes in the data. --- Telegram/SourceFiles/passport/passport.style | 1 + .../passport/passport_form_controller.cpp | 62 +++++++---- .../passport/passport_form_controller.h | 17 ++- .../passport_form_view_controller.cpp | 61 ++++++++--- .../passport/passport_form_view_controller.h | 2 + .../passport/passport_panel_controller.cpp | 101 +++++++++--------- .../passport/passport_panel_controller.h | 8 +- .../passport/passport_panel_edit_document.cpp | 101 +++++++++++++----- .../passport/passport_panel_edit_document.h | 22 +++- .../passport/passport_panel_edit_scans.cpp | 72 ++++++++++++- .../passport/passport_panel_edit_scans.h | 11 ++ 11 files changed, 328 insertions(+), 130 deletions(-) diff --git a/Telegram/SourceFiles/passport/passport.style b/Telegram/SourceFiles/passport/passport.style index 3b92c6ed89..a10bb71564 100644 --- a/Telegram/SourceFiles/passport/passport.style +++ b/Telegram/SourceFiles/passport/passport.style @@ -139,6 +139,7 @@ passportUploadButton: InfoProfileButton { passportUploadButtonPadding: margins(0px, 10px, 0px, 10px); passportUploadHeaderPadding: margins(22px, 14px, 22px, 3px); passportUploadErrorPadding: margins(22px, 5px, 22px, 5px); +passportValueErrorPadding: passportUploadHeaderPadding; passportDeleteButton: InfoProfileButton(passportUploadButton) { textFg: attentionButtonFg; textFgOver: attentionButtonFgOver; diff --git a/Telegram/SourceFiles/passport/passport_form_controller.cpp b/Telegram/SourceFiles/passport/passport_form_controller.cpp index d829a3486b..d874632a9e 100644 --- a/Telegram/SourceFiles/passport/passport_form_controller.cpp +++ b/Telegram/SourceFiles/passport/passport_form_controller.cpp @@ -302,6 +302,20 @@ bool Value::requiresSpecialScan(SpecialFile type) const { Unexpected("Special scan type in requiresSpecialScan."); } +void Value::fillDataFrom(Value &&other) { + const auto savedSelfieRequired = selfieRequired; + const auto savedTranslationRequired = translationRequired; + const auto savedNativeNames = nativeNames; + const auto savedEditScreens = editScreens; + + *this = std::move(other); + + selfieRequired = savedSelfieRequired; + translationRequired = savedTranslationRequired; + nativeNames = savedNativeNames; + editScreens = savedEditScreens; +} + bool Value::scansAreFilled() const { if (!requiresSpecialScan(SpecialFile::FrontSide) && scans.empty()) { return false; @@ -876,12 +890,15 @@ void FormController::fillErrors() { for (const auto &error : _form.pendingErrors) { error.match([&](const MTPDsecureValueError &data) { if (const auto value = find(data.vtype)) { - value->error = qs(data.vtext); + if (CanHaveErrors(value->type)) { + value->error = qs(data.vtext); + } } }, [&](const MTPDsecureValueErrorData &data) { if (const auto value = find(data.vtype)) { const auto key = qs(data.vfield); - if (!SkipFieldCheck(value, key)) { + if (CanHaveErrors(value->type) + && !SkipFieldCheck(value, key)) { value->data.parsed.fields[key].error = qs(data.vtext); } } @@ -894,7 +911,9 @@ void FormController::fillErrors() { } }, [&](const MTPDsecureValueErrorFiles &data) { if (const auto value = find(data.vtype)) { - value->scanMissingError = qs(data.vtext); + if (CanRequireScans(value->type)) { + value->scanMissingError = qs(data.vtext); + } } }, [&](const MTPDsecureValueErrorTranslationFile &data) { const auto hash = bytes::make_span(data.vfile_hash.v); @@ -921,7 +940,7 @@ void FormController::fillErrors() { } } -void FormController::decryptValue(Value &value) { +void FormController::decryptValue(Value &value) const { Expects(!_secret.empty()); if (!validateValueSecrets(value)) { @@ -946,7 +965,7 @@ void FormController::decryptValue(Value &value) { } } -bool FormController::validateValueSecrets(Value &value) { +bool FormController::validateValueSecrets(Value &value) const { if (!value.data.original.isEmpty()) { value.data.secret = DecryptValueSecret( value.data.encryptedSecret, @@ -981,8 +1000,8 @@ bool FormController::validateValueSecrets(Value &value) { return true; } -void FormController::resetValue(Value &value) { - value = Value(value.type); +void FormController::resetValue(Value &value) const { + value.fillDataFrom(Value(value.type)); } rpl::producer FormController::passwordError() const { @@ -1600,6 +1619,9 @@ void FormController::saveValueEdit( return; } + // If we didn't change anything, we don't send save request + // and we don't reset value->error/[scan|translation]MissingError. + // Otherwise we reset them after save by re-parsing the value. const auto nonconst = findValue(value); if (!editValueChanged(nonconst, data)) { nonconst->saveRequestId = -1; @@ -1632,10 +1654,7 @@ void FormController::deleteValueEdit(not_null value) { nonconst->saveRequestId = request(MTPaccount_DeleteSecureValue( MTP_vector(1, ConvertType(nonconst->type)) )).done([=](const MTPBool &result) { - const auto editScreens = value->editScreens; - *nonconst = Value(nonconst->type); - nonconst->editScreens = editScreens; - + resetValue(*nonconst); _valueSaveFinished.fire_copy(value); }).fail([=](const RPCError &error) { nonconst->saveRequestId = 0; @@ -1791,10 +1810,9 @@ void FormController::sendSaveRequest( scansInEdit.push_back(std::move(scan)); } - const auto editScreens = value->editScreens; - *value = parseValue(result, scansInEdit); - decryptValue(*value); - value->editScreens = editScreens; + auto refreshed = parseValue(result, scansInEdit); + decryptValue(refreshed); + value->fillDataFrom(std::move(refreshed)); _valueSaveFinished.fire_copy(value); }).fail([=](const RPCError &error) { @@ -2257,13 +2275,13 @@ bool FormController::parseForm(const MTPaccount_AuthorizationForm &result) { } for (const auto &required : data.vrequired_types.v) { const auto row = CollectRequestedRow(required); - for (const auto value : row.values) { - const auto [i, ok] = _form.values.emplace( - value.type, - Value(value.type)); - i->second.selfieRequired = value.selfieRequired; - i->second.translationRequired = value.translationRequired; - i->second.nativeNames = value.nativeNames; + for (const auto requested : row.values) { + const auto type = requested.type; + const auto [i, ok] = _form.values.emplace(type, Value(type)); + auto &value = i->second; + value.translationRequired = requested.translationRequired; + value.selfieRequired = requested.selfieRequired; + value.nativeNames = requested.nativeNames; } _form.request.push_back(row.values | ranges::view::transform([](const RequestedValue &value) { diff --git a/Telegram/SourceFiles/passport/passport_form_controller.h b/Telegram/SourceFiles/passport/passport_form_controller.h index 32ff2fae83..e730970926 100644 --- a/Telegram/SourceFiles/passport/passport_form_controller.h +++ b/Telegram/SourceFiles/passport/passport_form_controller.h @@ -164,10 +164,14 @@ struct Value { Email, }; + explicit Value(Type type); Value(Value &&other) = default; - Value &operator=(Value &&other) = default; + // Some data is not parsed from server-provided values. + // It should be preserved through re-parsing (for example when saving). + // So we hide "operator=(Value&&)" in private and instead provide this. + void fillDataFrom(Value &&other); bool requiresSpecialScan(SpecialFile type) const; bool scansAreFilled() const; @@ -188,10 +192,13 @@ struct Value { bool selfieRequired = false; bool translationRequired = false; bool nativeNames = false; - int editScreens = 0; + mtpRequestId saveRequestId = 0; +private: + Value &operator=(Value &&other) = default; + }; struct RequestedValue { @@ -395,9 +402,9 @@ private: bytes::const_span passwordBytes, uint64 serverSecretId); void decryptValues(); - void decryptValue(Value &value); - bool validateValueSecrets(Value &value); - void resetValue(Value &value); + void decryptValue(Value &value) const; + bool validateValueSecrets(Value &value) const; + void resetValue(Value &value) const; void fillErrors(); void loadFile(File &file); diff --git a/Telegram/SourceFiles/passport/passport_form_view_controller.cpp b/Telegram/SourceFiles/passport/passport_form_view_controller.cpp index 7cc168aa3d..c315c8c3df 100644 --- a/Telegram/SourceFiles/passport/passport_form_view_controller.cpp +++ b/Telegram/SourceFiles/passport/passport_form_view_controller.cpp @@ -98,6 +98,31 @@ bool InlineDetails(const Form::Request &request, Value::Type details) { Scope::Scope(Type type) : type(type) { } +bool CanRequireSelfie(Value::Type type) { + const auto scope = ScopeTypeForValueType(type); + return (scope == Scope::Type::Address) + || (scope == Scope::Type::Identity); +} + +bool CanRequireScans(Value::Type type) { + const auto scope = ScopeTypeForValueType(type); + return (scope == Scope::Type::Address); +} + +bool CanRequireTranslation(Value::Type type) { + const auto scope = ScopeTypeForValueType(type); + return (scope == Scope::Type::Address) + || (scope == Scope::Type::Identity); +} + +bool CanRequireNativeNames(Value::Type type) { + return (type == Value::Type::PersonalDetails); +} + +bool CanHaveErrors(Value::Type type) { + return (type != Value::Type::Phone) && (type != Value::Type::Email); +} + bool ValidateForm(const Form &form) { base::flat_set values; for (const auto &requested : form.request) { @@ -120,30 +145,36 @@ bool ValidateForm(const Form &form) { values.emplace(type); } } + + // Invalid errors should be skipped while parsing the form. for (const auto &[type, value] : form.values) { + if (value.selfieRequired && !CanRequireSelfie(type)) { + LOG(("API Error: Bad value requiring selfie.")); + return false; + } else if (value.translationRequired + && !CanRequireTranslation(type)) { + LOG(("API Error: Bad value requiring translation.")); + return false; + } else if (value.nativeNames && !CanRequireNativeNames(type)) { + LOG(("API Error: Bad value requiring native names.")); + return false; + } + if (!CanRequireScans(value.type)) { + Assert(value.scanMissingError.isEmpty()); + } if (!value.translationRequired) { for (const auto &scan : value.translations) { - if (!scan.error.isEmpty()) { - LOG(("API Error: " - "Translation error in authorization form value.")); - return false; - } - } - if (!value.translationMissingError.isEmpty()) { - LOG(("API Error: " - "Translations error in authorization form value.")); - return false; + Assert(scan.error.isEmpty()); } + Assert(value.translationMissingError.isEmpty()); } for (const auto &[type, specialScan] : value.specialScans) { - if (!value.requiresSpecialScan(type) - && !specialScan.error.isEmpty()) { - LOG(("API Error: " - "Special scan error in authorization form value.")); - return false; + if (!value.requiresSpecialScan(type)) { + Assert(specialScan.error.isEmpty()); } } } + return true; } diff --git a/Telegram/SourceFiles/passport/passport_form_view_controller.h b/Telegram/SourceFiles/passport/passport_form_view_controller.h index 925c15a41f..cf04347a72 100644 --- a/Telegram/SourceFiles/passport/passport_form_view_controller.h +++ b/Telegram/SourceFiles/passport/passport_form_view_controller.h @@ -34,6 +34,8 @@ struct ScopeRow { QString error; }; +bool CanRequireScans(Value::Type type); +bool CanHaveErrors(Value::Type type); bool ValidateForm(const Form &form); std::vector ComputeScopes(const Form &form); QString ComputeScopeRowReadyString(const Scope &scope); diff --git a/Telegram/SourceFiles/passport/passport_panel_controller.cpp b/Telegram/SourceFiles/passport/passport_panel_controller.cpp index a902495de8..7ec6e508a6 100644 --- a/Telegram/SourceFiles/passport/passport_panel_controller.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_controller.cpp @@ -105,8 +105,8 @@ EditDocumentScheme GetDocumentScheme( return NameValidate(value); }; - // #TODO passport scheme switch (type) { + case Scope::Type::PersonalDetails: case Scope::Type::Identity: { auto result = Scheme(); result.detailsHeader = lang(lng_passport_personal_details); @@ -212,6 +212,7 @@ EditDocumentScheme GetDocumentScheme( return result; } break; + case Scope::Type::AddressDetails: case Scope::Type::Address: { auto result = Scheme(); result.detailsHeader = lang(lng_passport_address); @@ -341,7 +342,7 @@ EditContactScheme GetContactScheme(Scope::Type type) { Unexpected("Type in GetContactScheme()."); } -const std::map &NativeNameKeys() { +const std::map &LatinToNativeMap() { static const auto result = std::map { { qsl("first_name"), qsl("first_name_native") }, { qsl("last_name"), qsl("last_name_native") }, @@ -350,11 +351,11 @@ const std::map &NativeNameKeys() { return result; } -const std::map &LatinNameKeys() { +const std::map &NativeToLatinMap() { static const auto result = std::map { { qsl("first_name_native"), qsl("first_name") }, { qsl("last_name_native"), qsl("last_name") }, - { qsl("_nativemiddle_name"), qsl("middle_name") }, + { qsl("middle_name_native"), qsl("middle_name") }, }; return result; } @@ -363,10 +364,10 @@ bool SkipFieldCheck(not_null value, const QString &key) { if (value->type != Value::Type::PersonalDetails) { return false; } - const auto &namesMap = value->nativeNames - ? NativeNameKeys() - : LatinNameKeys(); - return namesMap.find(key) == end(namesMap); + const auto &dontCheckNames = value->nativeNames + ? LatinToNativeMap() + : NativeToLatinMap(); + return dontCheckNames.find(key) != end(dontCheckNames); } BoxPointer::BoxPointer(QPointer value) @@ -679,37 +680,11 @@ ScanInfo PanelController::collectScanInfo(const EditFile &file) const { file.fields.error }; } -std::vector PanelController::collectErrors( +std::vector PanelController::collectSaveErrors( not_null value) const { using General = ScopeError::General; auto result = std::vector(); - if (!value->error.isEmpty()) { - result.push_back({ General::WholeValue, value->error }); - } - if (!value->scanMissingError.isEmpty()) { - result.push_back({ General::ScanMissing, value->scanMissingError }); - } - if (!value->translationMissingError.isEmpty()) { - result.push_back({ - General::TranslationMissing, - value->translationMissingError }); - } - 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); - } - for (const auto &scan : value->translationsInEdit) { - addFileError(scan); - } - for (const auto &[type, scan] : value->specialScansInEdit) { - addFileError(scan); - } for (const auto &[key, value] : value->data.parsedInEdit.fields) { if (!value.error.isEmpty()) { result.push_back({ key, value.error }); @@ -912,12 +887,10 @@ void PanelController::editScope(int index) { editScope(index, -1); } else { const auto documentIndex = findNonEmptyDocumentIndex(scope); - if (documentIndex >= 0) { - editScope(index, documentIndex); - } else if (scope.documents.size() > 1) { - requestScopeFilesType(index); + if (documentIndex >= 0 || scope.documents.size() == 1) { + editScope(index, (documentIndex >= 0) ? documentIndex : 0); } else { - editWithUpload(index, 0); + requestScopeFilesType(index); } } } @@ -988,7 +961,7 @@ void PanelController::editWithUpload(int index, int documentIndex) { Expects(documentIndex >= 0 && documentIndex < _scopes[index].documents.size()); - const auto &document = _scopes[index].documents[documentIndex]; + const auto document = _scopes[index].documents[documentIndex]; const auto requiresSpecialScan = document->requiresSpecialScan( SpecialFile::FrontSide); const auto allowMany = !requiresSpecialScan; @@ -998,7 +971,7 @@ void PanelController::editWithUpload(int index, int documentIndex) { _scopeDocumentTypeBox = BoxPointer(); } if (!_editScope || !_editDocument) { - editScope(index, documentIndex); + startScopeEdit(index, documentIndex); } if (requiresSpecialScan) { uploadSpecialScan(SpecialFile::FrontSide, std::move(content)); @@ -1026,9 +999,37 @@ void PanelController::readScanError(ReadScanError error) { }())); } +bool PanelController::editRequiresScanUpload( + int index, + int documentIndex) const { + Expects(index >= 0 && index < _scopes.size()); + Expects((documentIndex < 0) + || (documentIndex >= 0 + && documentIndex < _scopes[index].documents.size())); + + if (documentIndex < 0) { + return false; + } + const auto document = _scopes[index].documents[documentIndex]; + if (document->requiresSpecialScan(SpecialFile::FrontSide)) { + const auto &scans = document->specialScans; + return (scans.find(SpecialFile::FrontSide) == end(scans)); + } + return document->scans.empty(); +} + void PanelController::editScope(int index, int documentIndex) { + if (editRequiresScanUpload(index, documentIndex)) { + editWithUpload(index, documentIndex); + } else { + startScopeEdit(index, documentIndex); + } +} + +void PanelController::startScopeEdit(int index, int documentIndex) { Expects(_panel != nullptr); Expects(index >= 0 && index < _scopes.size()); + Expects(_scopes[index].details != 0 || documentIndex >= 0); Expects((documentIndex < 0) || (documentIndex >= 0 && documentIndex < _scopes[index].documents.size())); @@ -1038,7 +1039,6 @@ void PanelController::editScope(int index, int documentIndex) { _editDocument = (documentIndex >= 0) ? _scopes[index].documents[documentIndex].get() : nullptr; - Assert(_editValue || _editDocument); if (_editValue) { _form->startValueEdit(_editValue); @@ -1048,7 +1048,6 @@ void PanelController::editScope(int index, int documentIndex) { } auto content = [&]() -> object_ptr { - // #TODO passport pass and display value->error switch (_editScope->type) { case Scope::Type::Identity: case Scope::Type::Address: { @@ -1060,7 +1059,9 @@ void PanelController::editScope(int index, int documentIndex) { GetDocumentScheme( _editScope->type, _editDocument->type), + _editValue->error, _editValue->data.parsedInEdit, + _editDocument->error, _editDocument->data.parsedInEdit, _editDocument->scanMissingError, valueFiles(*_editDocument), @@ -1071,6 +1072,7 @@ void PanelController::editScope(int index, int documentIndex) { GetDocumentScheme( _editScope->type, _editDocument->type), + _editDocument->error, _editDocument->data.parsedInEdit, _editDocument->scanMissingError, valueFiles(*_editDocument), @@ -1085,10 +1087,11 @@ void PanelController::editScope(int index, int documentIndex) { case Scope::Type::AddressDetails: { Assert(_editValue != nullptr); auto result = object_ptr( - _panel->widget(), - this, - GetDocumentScheme(_editScope->type), - _editValue->data.parsedInEdit); + _panel->widget(), + this, + GetDocumentScheme(_editScope->type), + _editValue->error, + _editValue->data.parsedInEdit); const auto weak = make_weak(result.data()); _panelHasUnsavedChanges = [=] { return weak ? weak->hasUnsavedChanges() : false; @@ -1148,7 +1151,7 @@ void PanelController::processValueSaveFinished( } if ((_editValue == value || _editDocument == value) && !savingScope()) { - if (auto errors = collectErrors(value); !errors.empty()) { + if (auto errors = collectSaveErrors(value); !errors.empty()) { for (auto &&error : errors) { _saveErrors.fire(std::move(error)); } diff --git a/Telegram/SourceFiles/passport/passport_panel_controller.h b/Telegram/SourceFiles/passport/passport_panel_controller.h index 7aef43d96b..6d389f4950 100644 --- a/Telegram/SourceFiles/passport/passport_panel_controller.h +++ b/Telegram/SourceFiles/passport/passport_panel_controller.h @@ -25,8 +25,8 @@ EditDocumentScheme GetDocumentScheme( base::optional scansType = base::none); EditContactScheme GetContactScheme(Scope::Type type); -const std::map &NativeNameKeys(); -const std::map &LatinNameKeys(); +const std::map &LatinToNativeMap(); +const std::map &NativeToLatinMap(); bool SkipFieldCheck(not_null value, const QString &key); struct ScanInfo { @@ -144,6 +144,8 @@ private: void editScope(int index, int documentIndex); void editWithUpload(int index, int documentIndex); + bool editRequiresScanUpload(int index, int documentIndex) const; + void startScopeEdit(int index, int documentIndex); int findNonEmptyDocumentIndex(const Scope &scope) const; void requestScopeFilesType(int index); void cancelValueEdit(); @@ -158,7 +160,7 @@ private: bool hasValueDocument() const; bool hasValueFields() const; ScanInfo collectScanInfo(const EditFile &file) const; - std::vector collectErrors( + std::vector collectSaveErrors( not_null value) const; QString getDefaultContactValue(Scope::Type type) const; void deleteValueSure(bool withDetails); diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp b/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp index 528776c804..b75b1e2487 100644 --- a/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp @@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/checkbox.h" #include "ui/wrap/vertical_layout.h" #include "ui/wrap/fade_wrap.h" +#include "ui/wrap/slide_wrap.h" #include "boxes/abstract_box.h" #include "boxes/confirm_box.h" #include "lang/lang_keys.h" @@ -209,8 +210,10 @@ PanelEditDocument::PanelEditDocument( QWidget*, not_null controller, Scheme scheme, + const QString &error, const ValueMap &data, - const ValueMap &scanData, + const QString &scansError, + const ValueMap &scansData, const QString &missingScansError, std::vector &&files, std::map &&specialFiles) @@ -224,8 +227,10 @@ PanelEditDocument::PanelEditDocument( langFactory(lng_passport_save_value), st::passportPanelSaveValue) { setupControls( + &error, &data, - &scanData, + &scansError, + &scansData, missingScansError, std::move(files), std::move(specialFiles)); @@ -235,7 +240,8 @@ PanelEditDocument::PanelEditDocument( QWidget*, not_null controller, Scheme scheme, - const ValueMap &scanData, + const QString &scansError, + const ValueMap &scansData, const QString &missingScansError, std::vector &&files, std::map &&specialFiles) @@ -250,7 +256,9 @@ PanelEditDocument::PanelEditDocument( st::passportPanelSaveValue) { setupControls( nullptr, - &scanData, + nullptr, + &scansError, + &scansData, missingScansError, std::move(files), std::move(specialFiles)); @@ -260,6 +268,7 @@ PanelEditDocument::PanelEditDocument( QWidget*, not_null controller, Scheme scheme, + const QString &error, const ValueMap &data) : _controller(controller) , _scheme(std::move(scheme)) @@ -270,18 +279,22 @@ PanelEditDocument::PanelEditDocument( this, langFactory(lng_passport_save_value), st::passportPanelSaveValue) { - setupControls(&data, nullptr, QString(), {}, {}); + setupControls(&error, &data, nullptr, nullptr, QString(), {}, {}); } void PanelEditDocument::setupControls( + const QString *error, const ValueMap *data, - const ValueMap *scanData, + const QString *scansError, + const ValueMap *scansData, const QString &missingScansError, std::vector &&files, std::map &&specialFiles) { const auto inner = setupContent( + error, data, - scanData, + scansError, + scansData, missingScansError, std::move(files), std::move(specialFiles)); @@ -298,8 +311,10 @@ void PanelEditDocument::setupControls( } not_null PanelEditDocument::setupContent( + const QString *error, const ValueMap *data, - const ValueMap *scanData, + const QString *scansError, + const ValueMap *scansData, const QString &missingScansError, std::vector &&files, std::map &&specialFiles) { @@ -315,22 +330,17 @@ not_null PanelEditDocument::setupContent( object_ptr( inner, _controller, -// _scheme.scansHeader, -// missingScansError, -// std::move(files), + *scansError, std::move(specialFiles))); - } else if (scanData) { + } else if (scansData) { _editScans = inner->add( object_ptr( inner, _controller, _scheme.scansHeader, + *scansError, missingScansError, std::move(files))); - } else { - inner->add(object_ptr( - inner, - st::passportFormDividerHeight)); } const auto valueOrEmpty = [&]( @@ -348,7 +358,7 @@ not_null PanelEditDocument::setupContent( const auto &row = _scheme.rows[i]; auto fields = (row.valueClass == Scheme::ValueClass::Fields) ? data - : scanData; + : scansData; if (!fields) { continue; } @@ -365,6 +375,18 @@ not_null PanelEditDocument::setupContent( PanelDetailsRow::LabelWidth(row.label)); }); if (maxLabelWidth > 0) { + if (error && !error->isEmpty()) { + _commonError = inner->add( + object_ptr>( + inner, + object_ptr( + inner, + *error, + Ui::FlatLabel::InitType::Simple, + st::passportVerifyErrorLabel), + st::passportValueErrorPadding)); + _commonError->toggle(true, anim::type::instant); + } inner->add( object_ptr( inner, @@ -377,15 +399,28 @@ not_null PanelEditDocument::setupContent( const EditDocumentScheme::Row &row, const ValueMap &fields) { const auto current = valueOrEmpty(fields, row.key); - _details.emplace(i, inner->add(PanelDetailsRow::Create( - inner, - row.inputType, - _controller, - row.label, - maxLabelWidth, - current.text, - current.error, - row.lengthLimit))); + const auto [it, ok] = _details.emplace( + i, + inner->add(PanelDetailsRow::Create( + inner, + row.inputType, + _controller, + row.label, + maxLabelWidth, + current.text, + current.error, + row.lengthLimit))); + const bool details = (&fields == data); + it->second->value( + ) | rpl::skip(1) | rpl::start_with_next([=] { + if (details) { + _fieldsChanged = true; + updateCommonError(); + } else { + Assert(_editScans != nullptr); + _editScans->scanFieldsChanged(true); + } + }, it->second->lifetime()); }); inner->add( @@ -406,6 +441,12 @@ not_null PanelEditDocument::setupContent( return inner; } +void PanelEditDocument::updateCommonError() { + if (_commonError) { + _commonError->toggle(!_fieldsChanged, anim::type::normal); + } +} + void PanelEditDocument::focusInEvent(QFocusEvent *e) { crl::on_main(this, [=] { for (const auto [index, row] : _details) { @@ -451,7 +492,7 @@ PanelEditDocument::Result PanelEditDocument::collect() const { } bool PanelEditDocument::validate() { - const auto error = _editScans + auto error = _editScans ? _editScans->validateGetErrorTop() : base::none; if (error) { @@ -459,6 +500,12 @@ bool PanelEditDocument::validate() { const auto scrolltop = _scroll->mapToGlobal(QPoint(0, 0)); const auto scrolldelta = errortop.y() - scrolltop.y(); _scroll->scrollToY(_scroll->scrollTop() + scrolldelta); + } else if (_commonError && !_fieldsChanged) { + const auto firsttop = _commonError->mapToGlobal(QPoint(0, 0)); + const auto scrolltop = _scroll->mapToGlobal(QPoint(0, 0)); + const auto scrolldelta = firsttop.y() - scrolltop.y(); + _scroll->scrollToY(_scroll->scrollTop() + scrolldelta); + error = firsttop.y(); } auto first = QPointer(); for (const auto [i, field] : base::reversed(_details)) { diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_document.h b/Telegram/SourceFiles/passport/passport_panel_edit_document.h index a1a4ba7d01..8d196dac7b 100644 --- a/Telegram/SourceFiles/passport/passport_panel_edit_document.h +++ b/Telegram/SourceFiles/passport/passport_panel_edit_document.h @@ -14,7 +14,10 @@ class InputField; class ScrollArea; class FadeShadow; class PlainShadow; +class FlatLabel; class RoundButton; +template +class SlideWrap; } // namespace Ui namespace Info { @@ -63,8 +66,10 @@ public: QWidget *parent, not_null controller, Scheme scheme, + const QString &error, const ValueMap &data, - const ValueMap &scanData, + const QString &scansError, + const ValueMap &scansData, const QString &missingScansError, std::vector &&files, std::map &&specialFiles); @@ -72,7 +77,8 @@ public: QWidget *parent, not_null controller, Scheme scheme, - const ValueMap &scanData, + const QString &scansError, + const ValueMap &scansData, const QString &missingScansError, std::vector &&files, std::map &&specialFiles); @@ -80,6 +86,7 @@ public: QWidget *parent, not_null controller, Scheme scheme, + const QString &error, const ValueMap &data); bool hasUnsavedChanges() const; @@ -91,18 +98,23 @@ protected: private: struct Result; void setupControls( + const QString *error, const ValueMap *data, - const ValueMap *scanData, + const QString *scansError, + const ValueMap *scansData, const QString &missingScansError, std::vector &&files, std::map &&specialFiles); not_null setupContent( + const QString *error, const ValueMap *data, - const ValueMap *scanData, + const QString *scansError, + const ValueMap *scansData, const QString &missingScansError, std::vector &&files, std::map &&specialFiles); void updateControlsGeometry(); + void updateCommonError(); Result collect() const; bool validate(); @@ -116,7 +128,9 @@ private: object_ptr _bottomShadow; QPointer _editScans; + QPointer> _commonError; std::map> _details; + bool _fieldsChanged = false; QPointer _delete; diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_scans.cpp b/Telegram/SourceFiles/passport/passport_panel_edit_scans.cpp index 5db59b9dc1..66c6ae3183 100644 --- a/Telegram/SourceFiles/passport/passport_panel_edit_scans.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_edit_scans.cpp @@ -257,12 +257,14 @@ EditScans::EditScans( QWidget *parent, not_null controller, const QString &header, + const QString &error, const QString &errorMissing, std::vector &&files) : RpWidget(parent) , _controller(controller) , _files(std::move(files)) , _initialCount(_files.size()) +, _error(error) , _errorMissing(errorMissing) , _content(this) { setupScans(header); @@ -271,15 +273,20 @@ EditScans::EditScans( EditScans::EditScans( QWidget *parent, not_null controller, + const QString &error, std::map &&specialFiles) : RpWidget(parent) , _controller(controller) , _initialCount(-1) +, _error(error) , _content(this) { setupSpecialScans(std::move(specialFiles)); } bool EditScans::uploadedSomeMore() const { + if (_initialCount < 0) { + return false; + } const auto from = begin(_files) + _initialCount; const auto till = end(_files); return std::find_if(from, till, [](const ScanInfo &file) { @@ -303,6 +310,9 @@ base::optional EditScans::validateGetErrorTop() { [](const ScanInfo &file) { return !file.error.isEmpty(); } ) != end(_files); + if (_commonError && !somethingChanged()) { + suggestResult(_commonError->y()); + } if (_upload && (!exists || ((errorExists || _uploadMoreError) && !uploadedSomeMore()))) { toggleError(true); @@ -334,6 +344,19 @@ void EditScans::setupScans(const QString &header) { const auto inner = _content.data(); inner->move(0, 0); + if (!_error.isEmpty()) { + _commonError = inner->add( + object_ptr>( + inner, + object_ptr( + inner, + _error, + Ui::FlatLabel::InitType::Simple, + st::passportVerifyErrorLabel), + st::passportValueErrorPadding)); + _commonError->toggle(true, anim::type::instant); + } + _divider = inner->add( object_ptr>( inner, @@ -442,6 +465,20 @@ void EditScans::setupSpecialScans(std::map &&files) { const auto inner = _content.data(); inner->move(0, 0); + + if (!_error.isEmpty()) { + _commonError = inner->add( + object_ptr>( + inner, + object_ptr( + inner, + _error, + Ui::FlatLabel::InitType::Simple, + st::passportVerifyErrorLabel), + st::passportValueErrorPadding)); + _commonError->toggle(true, anim::type::instant); + } + for (auto &[type, info] : files) { const auto i = _specialScans.emplace( type, @@ -531,9 +568,27 @@ void EditScans::updateScan(ScanInfo &&info) { _header->show(anim::type::normal); _uploadTexts.fire(uploadButtonText()); } + updateErrorLabels(); +} + +void EditScans::scanFieldsChanged(bool changed) { + if (_scanFieldsChanged != changed) { + _scanFieldsChanged = changed; + updateErrorLabels(); + } +} + +void EditScans::updateErrorLabels() { if (_uploadMoreError) { _uploadMoreError->toggle(!uploadedSomeMore(), anim::type::normal); } + if (_commonError) { + _commonError->toggle(!somethingChanged(), anim::type::normal); + } +} + +bool EditScans::somethingChanged() const { + return uploadedSomeMore() || _scanFieldsChanged || _specialScanChanged; } void EditScans::updateSpecialScan(SpecialFile type, ScanInfo &&info) { @@ -547,8 +602,8 @@ void EditScans::updateSpecialScan(SpecialFile type, ScanInfo &&info) { if (scan.file.key.id) { updateFileRow(scan.row->entity(), info); scan.rowCreated = !info.deleted; - if (!info.deleted) { - hideSpecialScanError(type); + if (scan.file.key.id != info.key.id) { + specialScanChanged(type, true); } } else { const auto requiresBothSides @@ -558,6 +613,7 @@ void EditScans::updateSpecialScan(SpecialFile type, ScanInfo &&info) { scan.wrap->resizeToWidth(width()); scan.row->show(anim::type::normal); scan.header->show(anim::type::normal); + specialScanChanged(type, true); } scan.file = std::move(info); } @@ -569,8 +625,7 @@ void EditScans::updateFileRow( button->setImage(info.thumb); button->setDeleted(info.deleted); button->setError(!info.error.isEmpty()); -}; - +} void EditScans::createSpecialScanRow( SpecialScan &scan, @@ -606,7 +661,6 @@ void EditScans::createSpecialScanRow( }, row->lifetime()); scan.rowCreated = !info.deleted; - hideSpecialScanError(type); } void EditScans::pushScan(const ScanInfo &info) { @@ -793,6 +847,14 @@ void EditScans::hideSpecialScanError(SpecialFile type) { toggleSpecialScanError(type, false); } +void EditScans::specialScanChanged(SpecialFile type, bool changed) { + hideSpecialScanError(type); + if (_specialScanChanged != changed) { + _specialScanChanged = changed; + updateErrorLabels(); + } +} + auto EditScans::findSpecialScan(SpecialFile type) -> SpecialScan& { const auto i = _specialScans.find(type); Assert(i != end(_specialScans)); diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_scans.h b/Telegram/SourceFiles/passport/passport_panel_edit_scans.h index 3dca978613..84e00666f8 100644 --- a/Telegram/SourceFiles/passport/passport_panel_edit_scans.h +++ b/Telegram/SourceFiles/passport/passport_panel_edit_scans.h @@ -44,15 +44,19 @@ public: QWidget *parent, not_null controller, const QString &header, + const QString &error, const QString &errorMissing, std::vector &&files); EditScans( QWidget *parent, not_null controller, + const QString &error, std::map &&specialFiles); base::optional validateGetErrorTop(); + void scanFieldsChanged(bool changed); + static void ChooseScan( QPointer parent, Fn doneCallback, @@ -88,28 +92,35 @@ private: rpl::producer uploadButtonText() const; + void updateErrorLabels(); void toggleError(bool shown); void hideError(); void errorAnimationCallback(); bool uploadedSomeMore() const; + bool somethingChanged() const; void toggleSpecialScanError(SpecialFile type, bool shown); void hideSpecialScanError(SpecialFile type); void specialScanErrorAnimationCallback(SpecialFile type); + void specialScanChanged(SpecialFile type, bool changed); not_null _controller; std::vector _files; int _initialCount = 0; + QString _error; QString _errorMissing; object_ptr _content; QPointer> _divider; QPointer> _header; + QPointer> _commonError; QPointer> _uploadMoreError; QPointer _wrap; std::vector>> _rows; QPointer _upload; rpl::event_stream> _uploadTexts; + bool _scanFieldsChanged = false; + bool _specialScanChanged = false; bool _errorShown = false; Animation _errorAnimation;