diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 86e39de2b..5bf6b6bf5 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1668,7 +1668,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_passport_error_cant_read" = "Can't read this file. Please choose an image."; "lng_passport_bad_name" = "Please use latin characters only."; "lng_passport_wait_upload" = "Please wait while upload is finished."; -"lng_passport_fix_errors" = "Please correct errors."; "lng_passport_app_out_of_date" = "Sorry, your Telegram app is out of date and can't handle this request. Please update Telegram."; "lng_export_title" = "Export Personal Data"; diff --git a/Telegram/Resources/scheme.tl b/Telegram/Resources/scheme.tl index 587d753cd..7125ec24a 100644 --- a/Telegram/Resources/scheme.tl +++ b/Telegram/Resources/scheme.tl @@ -995,9 +995,9 @@ secureValueTypeTemporaryRegistration#ea02ec33 = SecureValueType; secureValueTypePhone#b320aadb = SecureValueType; secureValueTypeEmail#8e3ca7ee = SecureValueType; -secureValue#b4b4b699 flags:# type:SecureValueType data:flags.0?SecureData front_side:flags.1?SecureFile reverse_side:flags.2?SecureFile selfie:flags.3?SecureFile files:flags.4?Vector plain_data:flags.5?SecurePlainData hash:bytes = SecureValue; +secureValue#187fa0ca flags:# type:SecureValueType data:flags.0?SecureData front_side:flags.1?SecureFile reverse_side:flags.2?SecureFile selfie:flags.3?SecureFile translation:flags.6?Vector files:flags.4?Vector plain_data:flags.5?SecurePlainData hash:bytes = SecureValue; -inputSecureValue#67872e8 flags:# type:SecureValueType data:flags.0?SecureData front_side:flags.1?InputSecureFile reverse_side:flags.2?InputSecureFile selfie:flags.3?InputSecureFile files:flags.4?Vector plain_data:flags.5?SecurePlainData = InputSecureValue; +inputSecureValue#db21d0a7 flags:# type:SecureValueType data:flags.0?SecureData front_side:flags.1?InputSecureFile reverse_side:flags.2?InputSecureFile selfie:flags.3?InputSecureFile translation:flags.6?Vector files:flags.4?Vector plain_data:flags.5?SecurePlainData = InputSecureValue; secureValueHash#ed1ecdb0 type:SecureValueType hash:bytes = SecureValueHash; @@ -1007,10 +1007,13 @@ secureValueErrorReverseSide#868a2aa5 type:SecureValueType file_hash:bytes text:s secureValueErrorSelfie#e537ced6 type:SecureValueType file_hash:bytes text:string = SecureValueError; secureValueErrorFile#7a700873 type:SecureValueType file_hash:bytes text:string = SecureValueError; secureValueErrorFiles#666220e9 type:SecureValueType file_hash:Vector text:string = SecureValueError; +secureValueError#869d758f type:SecureValueType hash:bytes text:string = SecureValueError; +secureValueErrorTranslationFile#a1144770 type:SecureValueType file_hash:bytes text:string = SecureValueError; +secureValueErrorTranslationFiles#34636dd8 type:SecureValueType file_hash:Vector text:string = SecureValueError; secureCredentialsEncrypted#33f0ea47 data:bytes hash:bytes secret:bytes = SecureCredentialsEncrypted; -account.authorizationForm#cb976d53 flags:# selfie_required:flags.1?true required_types:Vector values:Vector errors:Vector users:Vector privacy_policy_url:flags.0?string = account.AuthorizationForm; +account.authorizationForm#ad2e1cd8 flags:# required_types:Vector values:Vector errors:Vector users:Vector privacy_policy_url:flags.0?string = account.AuthorizationForm; account.sentEmailCode#811f854f email_pattern:string length:int = account.SentEmailCode; @@ -1033,6 +1036,9 @@ secureSecretSettings#1527bcac secure_algo:SecurePasswordKdfAlgo secure_secret:by inputCheckPasswordEmpty#9880f658 = InputCheckPasswordSRP; inputCheckPasswordSRP#d27ff082 srp_id:long A:bytes M1:bytes = InputCheckPasswordSRP; +secureRequiredType#829d99da flags:# native_names:flags.0?true selfie_required:flags.1?true translation_required:flags.2?true type:SecureValueType = SecureRequiredType; +secureRequiredTypeOneOf#27477b4 types:Vector = SecureRequiredType; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1321,4 +1327,4 @@ langpack.getStrings#2e1ee318 lang_code:string keys:Vector = Vector; -// LAYER 84 +// LAYER 85 diff --git a/Telegram/SourceFiles/passport/passport_form_controller.cpp b/Telegram/SourceFiles/passport/passport_form_controller.cpp index 8624ce8ef..72ea2be1a 100644 --- a/Telegram/SourceFiles/passport/passport_form_controller.cpp +++ b/Telegram/SourceFiles/passport/passport_form_controller.cpp @@ -132,7 +132,30 @@ MTPSecureValueType ConvertType(Value::Type type) { return MTP_secureValueTypeEmail(); } Unexpected("Type in FormController::submit."); -}; +} + +void CollectToRequestedRow( + RequestedRow &row, + const MTPSecureRequiredType &data) { + data.match([&](const MTPDsecureRequiredType &data) { + row.values.emplace_back(ConvertType(data.vtype)); + auto &value = row.values.back(); + value.selfieRequired = data.is_selfie_required(); + value.translationRequired = data.is_translation_required(); + value.nativeNames = data.is_native_names(); + }, [&](const MTPDsecureRequiredTypeOneOf &data) { + row.values.reserve(row.values.size() + data.vtypes.v.size()); + for (const auto &one : data.vtypes.v) { + CollectToRequestedRow(row, one); + } + }); +} + +RequestedRow CollectRequestedRow(const MTPSecureRequiredType &data) { + auto result = RequestedRow(); + CollectToRequestedRow(result, data); + return result; +} QJsonObject GetJSONFromMap( const std::map &map) { @@ -257,12 +280,13 @@ UploadScanData *UploadScanDataPointer::operator->() const { return _value.get(); } +RequestedValue::RequestedValue(Value::Type type) : type(type) { +} + Value::Value(Type type) : type(type) { } -bool Value::requiresSpecialScan( - SpecialFile type, - bool selfieRequired) const { +bool Value::requiresSpecialScan(SpecialFile type) const { switch (type) { case SpecialFile::FrontSide: return (this->type == Type::Passport) @@ -278,9 +302,11 @@ bool Value::requiresSpecialScan( Unexpected("Special scan type in requiresSpecialScan."); } -bool Value::scansAreFilled(bool selfieRequired) const { - if (!requiresSpecialScan(SpecialFile::FrontSide, selfieRequired)) { - return !scans.empty(); +bool Value::scansAreFilled() const { + if (!requiresSpecialScan(SpecialFile::FrontSide) && scans.empty()) { + return false; + } else if (translationRequired && translations.empty()) { + return false; } const auto types = { SpecialFile::FrontSide, @@ -288,14 +314,13 @@ bool Value::scansAreFilled(bool selfieRequired) const { SpecialFile::Selfie }; for (const auto type : types) { - if (requiresSpecialScan(type, selfieRequired) + if (requiresSpecialScan(type) && (specialScans.find(type) == end(specialScans))) { return false; } } return true; -}; - +} FormController::FormController( not_null controller, @@ -346,8 +371,7 @@ auto FormController::prepareFinalData() -> FinalData { object.insert("files", files); } for (const auto &[type, scan] : value->specialScans) { - const auto selfieRequired = _form.identitySelfieRequired; - if (value->requiresSpecialScan(type, selfieRequired)) { + if (value->requiresSpecialScan(type)) { object.insert( SpecialScanCredentialsKey(type), GetJSONFromFile(scan)); @@ -364,17 +388,21 @@ auto FormController::prepareFinalData() -> FinalData { addValueToJSON(key, value); } }; - const auto scopes = ComputeScopes(this); + const auto scopes = ComputeScopes(_form); for (const auto &scope : scopes) { const auto row = ComputeScopeRow(scope); if (row.ready.isEmpty() || !row.error.isEmpty()) { - errors.push_back(scope.fields); + errors.push_back(scope.details + ? scope.details + : scope.documents[0].get()); continue; } - addValue(scope.fields); + if (scope.details) { + addValue(scope.details); + } if (!scope.documents.empty()) { for (const auto &document : scope.documents) { - if (document->scansAreFilled(scope.selfieRequired)) { + if (document->scansAreFilled()) { addValue(document); break; } @@ -803,6 +831,7 @@ void FormController::decryptValues() { } void FormController::fillErrors() { + // #TODO passport filter by flags const auto find = [&](const MTPSecureValueType &type) -> Value* { const auto converted = ConvertType(type); const auto i = _form.values.find(ConvertType(type)); @@ -835,43 +864,44 @@ void FormController::fillErrors() { } }; for (const auto &error : _form.pendingErrors) { - switch (error.type()) { - case mtpc_secureValueErrorData: { - const auto &data = error.c_secureValueErrorData(); + error.match([&](const MTPDsecureValueError &data) { + if (const auto value = find(data.vtype)) { + value->error = qs(data.vtext); + } + }, [&](const MTPDsecureValueErrorData &data) { if (const auto value = find(data.vtype)) { const auto key = qs(data.vfield); value->data.parsed.fields[key].error = qs(data.vtext); } - } break; - case mtpc_secureValueErrorFile: { - const auto &data = error.c_secureValueErrorFile(); + }, [&](const MTPDsecureValueErrorFile &data) { const auto hash = bytes::make_span(data.vfile_hash.v); if (const auto value = find(data.vtype)) { if (const auto file = scan(*value, hash)) { file->error = qs(data.vtext); } } - } break; - case mtpc_secureValueErrorFiles: { - const auto &data = error.c_secureValueErrorFiles(); + }, [&](const MTPDsecureValueErrorFiles &data) { if (const auto value = find(data.vtype)) { value->scanMissingError = qs(data.vtext); } - } break; - case mtpc_secureValueErrorFrontSide: { - const auto &data = error.c_secureValueErrorFrontSide(); + }, [&](const MTPDsecureValueErrorTranslationFile &data) { + const auto hash = bytes::make_span(data.vfile_hash.v); + if (const auto value = find(data.vtype)) { + if (const auto file = scan(*value, hash)) { // #TODO passport + file->error = qs(data.vtext); + } + } + }, [&](const MTPDsecureValueErrorTranslationFiles &data) { + if (const auto value = find(data.vtype)) { + value->translationMissingError = qs(data.vtext); + } + }, [&](const MTPDsecureValueErrorFrontSide &data) { setSpecialScanError(SpecialFile::FrontSide, data); - } break; - case mtpc_secureValueErrorReverseSide: { - const auto &data = error.c_secureValueErrorReverseSide(); + }, [&](const MTPDsecureValueErrorReverseSide &data) { setSpecialScanError(SpecialFile::ReverseSide, data); - } break; - case mtpc_secureValueErrorSelfie: { - const auto &data = error.c_secureValueErrorSelfie(); + }, [&](const MTPDsecureValueErrorSelfie &data) { setSpecialScanError(SpecialFile::Selfie, data); - } break; - default: Unexpected("Error type in FormController::fillErrors."); - } + }); } } @@ -1312,9 +1342,13 @@ void FormController::startValueEdit(not_null value) { for (auto &scan : nonconst->scans) { loadFile(scan); } + if (nonconst->translationRequired) { + for (auto &scan : nonconst->translations) { + loadFile(scan); + } + } for (auto &[type, scan] : nonconst->specialScans) { - const auto selfieRequired = _form.identitySelfieRequired; - if (nonconst->requiresSpecialScan(type, selfieRequired)) { + if (nonconst->requiresSpecialScan(type)) { loadFile(scan); } } @@ -1324,6 +1358,12 @@ void FormController::startValueEdit(not_null value) { return EditFile(nonconst, file, nullptr); }) | ranges::to_vector; + nonconst->translationsInEdit = ranges::view::all( + nonconst->translations + ) | ranges::view::transform([=](const File &file) { + return EditFile(nonconst, file, nullptr); + }) | ranges::to_vector; + nonconst->specialScansInEdit.clear(); for (const auto &[type, scan] : nonconst->specialScans) { nonconst->specialScansInEdit.emplace(type, EditFile( @@ -1597,7 +1637,7 @@ void FormController::saveEncryptedValue(not_null value) { return; } - const auto inputFile = [](const EditFile &file) { + const auto wrapFile = [](const EditFile &file) { if (const auto uploadData = file.uploadData.get()) { return MTP_inputSecureFileUploaded( MTP_long(file.fields.id), @@ -1611,13 +1651,22 @@ void FormController::saveEncryptedValue(not_null value) { MTP_long(file.fields.accessHash)); }; - auto inputFiles = QVector(); - inputFiles.reserve(value->scansInEdit.size()); + auto files = QVector(); + files.reserve(value->scansInEdit.size()); for (const auto &scan : value->scansInEdit) { if (scan.deleted) { continue; } - inputFiles.push_back(inputFile(scan)); + files.push_back(wrapFile(scan)); + } + + auto translations = QVector(); + translations.reserve(value->translationsInEdit.size()); + for (const auto &scan : value->translationsInEdit) { + if (scan.deleted) { + continue; + } + translations.push_back(wrapFile(scan)); } if (value->data.secret.empty()) { @@ -1639,7 +1688,7 @@ void FormController::saveEncryptedValue(not_null value) { const auto specialFile = [&](SpecialFile type) { const auto i = value->specialScansInEdit.find(type); return (i != end(value->specialScansInEdit) && !i->second.deleted) - ? inputFile(i->second) + ? wrapFile(i->second) : MTPInputSecureFile(); }; const auto frontSide = specialFile(SpecialFile::FrontSide); @@ -1659,6 +1708,9 @@ void FormController::saveEncryptedValue(not_null value) { | (hasSpecialFile(SpecialFile::Selfie) ? MTPDinputSecureValue::Flag::f_selfie : MTPDinputSecureValue::Flag(0)) + | (value->translationsInEdit.empty() + ? MTPDinputSecureValue::Flag(0) + : MTPDinputSecureValue::Flag::f_translation) | (value->scansInEdit.empty() ? MTPDinputSecureValue::Flag(0) : MTPDinputSecureValue::Flag::f_files); @@ -1674,7 +1726,8 @@ void FormController::saveEncryptedValue(not_null value) { frontSide, reverseSide, selfie, - MTP_vector(inputFiles), + MTP_vector(translations), + MTP_vector(files), MTPSecurePlainData())); } @@ -1704,6 +1757,7 @@ void FormController::savePlainTextValue(not_null value) { MTPInputSecureFile(), MTPInputSecureFile(), MTPVector(), + MTPVector(), plain(MTP_string(text)))); } @@ -2157,13 +2211,14 @@ auto FormController::findFile(const FileKey &key) } void FormController::formDone(const MTPaccount_AuthorizationForm &result) { - parseForm(result); - if (!_passwordRequestId) { + if (!parseForm(result)) { + _view->showCriticalError(lang(lng_passport_form_error)); + } else if (!_passwordRequestId) { showForm(); } } -void FormController::parseForm(const MTPaccount_AuthorizationForm &result) { +bool FormController::parseForm(const MTPaccount_AuthorizationForm &result) { Expects(result.type() == mtpc_account_authorizationForm); const auto &data = result.c_account_authorizationForm(); @@ -2177,21 +2232,34 @@ void FormController::parseForm(const MTPaccount_AuthorizationForm &result) { if (alreadyIt != _form.values.end()) { LOG(("API Error: Two values for type %1 in authorization form" "%1").arg(int(type))); - continue; + return false; } _form.values.emplace(type, std::move(parsed)); } - _form.identitySelfieRequired = data.is_selfie_required(); if (data.has_privacy_policy_url()) { _form.privacyPolicyUrl = qs(data.vprivacy_policy_url); } for (const auto &required : data.vrequired_types.v) { - const auto type = ConvertType(required); - _form.request.push_back(type); - _form.values.emplace(type, Value(type)); + 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; + } + _form.request.push_back(row.values + | ranges::view::transform([](const RequestedValue &value) { + return value.type; + }) | ranges::to_vector); + } + if (!ValidateForm(_form)) { + return false; } _bot = App::userLoaded(_request.botId); _form.pendingErrors = data.verrors.v; + return true; } void FormController::formFail(const QString &error) { diff --git a/Telegram/SourceFiles/passport/passport_form_controller.h b/Telegram/SourceFiles/passport/passport_form_controller.h index a709456a9..32ff2fae8 100644 --- a/Telegram/SourceFiles/passport/passport_form_controller.h +++ b/Telegram/SourceFiles/passport/passport_form_controller.h @@ -168,31 +168,52 @@ struct Value { Value(Value &&other) = default; Value &operator=(Value &&other) = default; - bool requiresSpecialScan(SpecialFile type, bool selfieRequired) const; - bool scansAreFilled(bool selfieRequired) const; + bool requiresSpecialScan(SpecialFile type) const; + bool scansAreFilled() const; Type type; ValueData data; std::vector scans; + std::vector translations; std::map specialScans; + QString error; QString scanMissingError; + QString translationMissingError; std::vector scansInEdit; + std::vector translationsInEdit; std::map specialScansInEdit; Verification verification; bytes::vector submitHash; + bool selfieRequired = false; + bool translationRequired = false; + bool nativeNames = false; + int editScreens = 0; mtpRequestId saveRequestId = 0; }; +struct RequestedValue { + explicit RequestedValue(Value::Type type); + + Value::Type type; + bool selfieRequired = false; + bool translationRequired = false; + bool nativeNames = false; +}; + +struct RequestedRow { + std::vector values; +}; + struct Form { + using Request = std::vector>; + std::map values; - std::vector request; - bool identitySelfieRequired = false; + Request request; QString privacyPolicyUrl; QVector pendingErrors; - }; struct PasswordSettings { @@ -333,7 +354,7 @@ private: void formDone(const MTPaccount_AuthorizationForm &result); void formFail(const QString &error); - void parseForm(const MTPaccount_AuthorizationForm &result); + bool parseForm(const MTPaccount_AuthorizationForm &result); void showForm(); Value parseValue( const MTPSecureValue &value, diff --git a/Telegram/SourceFiles/passport/passport_form_view_controller.cpp b/Telegram/SourceFiles/passport/passport_form_view_controller.cpp index d83ca197b..71c005704 100644 --- a/Telegram/SourceFiles/passport/passport_form_view_controller.cpp +++ b/Telegram/SourceFiles/passport/passport_form_view_controller.cpp @@ -18,12 +18,12 @@ namespace { std::map ScopeTypesMap() { return { - { Value::Type::PersonalDetails, Scope::Type::Identity }, + { Value::Type::PersonalDetails, Scope::Type::PersonalDetails }, { Value::Type::Passport, Scope::Type::Identity }, { Value::Type::DriverLicense, Scope::Type::Identity }, { Value::Type::IdentityCard, Scope::Type::Identity }, { Value::Type::InternalPassport, Scope::Type::Identity }, - { Value::Type::Address, Scope::Type::Address }, + { Value::Type::Address, Scope::Type::AddressDetails }, { Value::Type::UtilityBill, Scope::Type::Address }, { Value::Type::BankStatement, Scope::Type::Address }, { Value::Type::RentalAgreement, Scope::Type::Address }, @@ -41,62 +41,141 @@ Scope::Type ScopeTypeForValueType(Value::Type type) { return i->second; } -std::map ScopeFieldsMap() { +std::map ScopeDetailsMap() { return { + { Scope::Type::PersonalDetails, Value::Type::PersonalDetails }, { Scope::Type::Identity, Value::Type::PersonalDetails }, + { Scope::Type::AddressDetails, Value::Type::Address }, { Scope::Type::Address, Value::Type::Address }, { Scope::Type::Phone, Value::Type::Phone }, { Scope::Type::Email, Value::Type::Email }, }; } -Value::Type FieldsTypeForScopeType(Scope::Type type) { - static const auto map = ScopeFieldsMap(); +Value::Type DetailsTypeForScopeType(Scope::Type type) { + static const auto map = ScopeDetailsMap(); const auto i = map.find(type); Assert(i != map.end()); return i->second; } -} // namespace +bool InlineDetails( + const Form::Request &request, + Scope::Type into, + Value::Type details) { + const auto count = ranges::count_if( + request, + [&](const std::vector &types) { + Expects(!types.empty()); -Scope::Scope(Type type, not_null fields) -: type(type) -, fields(fields) { + return ScopeTypeForValueType(types[0]) == into; + }); + if (count != 1) { + return false; + } + const auto has = ranges::find_if( + request, + [&](const std::vector &types) { + Expects(!types.empty()); + + return (types[0] == details); + } + ) != end(request); + return has; } -std::vector ComputeScopes( - not_null controller) { - auto scopes = std::map(); - const auto &form = controller->form(); +bool InlineDetails(const Form::Request &request, Value::Type details) { + if (details == Value::Type::PersonalDetails) { + return InlineDetails(request, Scope::Type::Identity, details); + } else if (details == Value::Type::Address) { + return InlineDetails(request, Scope::Type::Address, details); + } + return false; +} + +} // namespace + +Scope::Scope(Type type) : type(type) { +} + +bool ValidateForm(const Form &form) { + base::flat_set values; + for (const auto &requested : form.request) { + if (requested.empty()) { + LOG(("API Error: Empty types list in authorization form row.")); + return false; + } + const auto scopeType = ScopeTypeForValueType(requested[0]); + const auto ownsDetails = (scopeType != Scope::Type::Identity + && scopeType != Scope::Type::Address); + if (ownsDetails && requested.size() != 1) { + LOG(("API Error: Large types list in authorization form row.")); + return false; + } + for (const auto type : requested) { + if (values.contains(type)) { + LOG(("API Error: Value twice in authorization form row.")); + return false; + } + values.emplace(type); + } + } + for (const auto &[type, value] : form.values) { + 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; + } + } + 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; + } + } + } + return true; +} + +std::vector ComputeScopes(const Form &form) { + auto result = std::vector(); const auto findValue = [&](const Value::Type type) { const auto i = form.values.find(type); Assert(i != form.values.end()); return &i->second; }; - for (const auto type : form.request) { - const auto scopeType = ScopeTypeForValueType(type); - const auto fieldsType = FieldsTypeForScopeType(scopeType); - const auto [i, ok] = scopes.emplace( - scopeType, - Scope(scopeType, findValue(fieldsType))); - i->second.selfieRequired = (scopeType == Scope::Type::Identity) - && form.identitySelfieRequired; - const auto alreadyIt = ranges::find( - i->second.documents, - type, - [](not_null value) { return value->type; }); - if (alreadyIt != end(i->second.documents)) { - LOG(("API Error: Value type %1 multiple times in request." - ).arg(int(type))); + for (const auto &requested : form.request) { + Assert(!requested.empty()); + const auto scopeType = ScopeTypeForValueType(requested[0]); + const auto detailsType = DetailsTypeForScopeType(scopeType); + const auto ownsDetails = (scopeType != Scope::Type::Identity + && scopeType != Scope::Type::Address); + const auto inlineDetails = InlineDetails(form.request, detailsType); + if (ownsDetails && inlineDetails) { continue; - } else if (type != fieldsType) { - i->second.documents.push_back(findValue(type)); } - } - auto result = std::vector(); - result.reserve(scopes.size()); - for (auto &[type, scope] : scopes) { - result.push_back(std::move(scope)); + result.push_back(Scope(scopeType)); + auto &scope = result.back(); + scope.details = (ownsDetails || inlineDetails) + ? findValue(detailsType) + : nullptr; + if (ownsDetails) { + Assert(requested.size() == 1); + } else { + for (const auto type : requested) { + scope.documents.push_back(findValue(type)); + } + } } return result; } @@ -129,7 +208,9 @@ QString JoinScopeRowReadyString( QString ComputeScopeRowReadyString(const Scope &scope) { switch (scope.type) { + case Scope::Type::PersonalDetails: case Scope::Type::Identity: + case Scope::Type::AddressDetails: case Scope::Type::Address: { auto list = std::vector>(); const auto pushListValue = [&]( @@ -153,10 +234,12 @@ QString ComputeScopeRowReadyString(const Scope &scope) { } } }; - const auto &fields = scope.fields->data.parsed.fields; + const auto fields = scope.details + ? &scope.details->data.parsed.fields + : nullptr; const auto document = [&]() -> const Value* { for (const auto &document : scope.documents) { - if (document->scansAreFilled(scope.selfieRequired)) { + if (document->scansAreFilled()) { return document; } } @@ -195,8 +278,11 @@ QString ComputeScopeRowReadyString(const Scope &scope) { for (const auto &row : scheme.rows) { const auto format = row.format; if (row.valueClass == EditDocumentScheme::ValueClass::Fields) { - const auto i = fields.find(row.key); - if (i == end(fields)) { + if (!fields) { + continue; + } + const auto i = fields->find(row.key); + if (i == end(*fields)) { return QString(); } const auto text = i->second.text; @@ -225,8 +311,9 @@ QString ComputeScopeRowReadyString(const Scope &scope) { } break; case Scope::Type::Phone: case Scope::Type::Email: { + Assert(scope.details != nullptr); const auto format = GetContactScheme(scope.type).format; - const auto &fields = scope.fields->data.parsed.fields; + const auto &fields = scope.details->data.parsed.fields; const auto i = fields.find("value"); return (i != end(fields)) ? (format ? format(i->second.text) : i->second.text) @@ -242,19 +329,30 @@ ScopeRow ComputeScopeRow(const Scope &scope) { row.ready = ready; auto errors = QStringList(); const auto addValueErrors = [&](not_null value) { + if (!value->error.isEmpty()) { + errors.push_back(value->error); + } + if (!value->scanMissingError.isEmpty()) { + errors.push_back(value->scanMissingError); + } + if (!value->translationMissingError.isEmpty()) { + errors.push_back(value->translationMissingError); + } for (const auto &scan : value->scans) { if (!scan.error.isEmpty()) { errors.push_back(scan.error); } } + for (const auto &scan : value->translations) { + if (!scan.error.isEmpty()) { + errors.push_back(scan.error); + } + } for (const auto &[type, scan] : value->specialScans) { if (!scan.error.isEmpty()) { errors.push_back(scan.error); } } - if (!value->scanMissingError.isEmpty()) { - errors.push_back(value->scanMissingError); - } for (const auto &[key, value] : value->data.parsed.fields) { if (!value.error.isEmpty()) { errors.push_back(value.error); @@ -263,7 +361,7 @@ ScopeRow ComputeScopeRow(const Scope &scope) { }; const auto document = [&]() -> const Value* { for (const auto &document : scope.documents) { - if (document->scansAreFilled(scope.selfieRequired)) { + if (document->scansAreFilled()) { return document; } } @@ -272,31 +370,35 @@ ScopeRow ComputeScopeRow(const Scope &scope) { if (document) { addValueErrors(document); } - addValueErrors(scope.fields); + if (scope.details) { + addValueErrors(scope.details); + } if (!errors.isEmpty()) { - row.error = lang(lng_passport_fix_errors);// errors.join('\n'); - } - if (row.error.isEmpty() - && row.ready.isEmpty() - && scope.type == Scope::Type::Identity - && scope.selfieRequired) { - auto noSelfieScope = scope; - noSelfieScope.selfieRequired = false; - if (!ComputeScopeRowReadyString(noSelfieScope).isEmpty()) { - // Only selfie is missing. - row.description = lang(lng_passport_identity_selfie); - } + row.error = errors[0];// errors.join('\n'); } + // #TODO passport half-full value + //if (row.error.isEmpty() + // && row.ready.isEmpty() + // && scope.type == Scope::Type::Identity + // && scope.selfieRequired) { + // auto noSelfieScope = scope; + // noSelfieScope.selfieRequired = false; + // if (!ComputeScopeRowReadyString(noSelfieScope).isEmpty()) { + // // Only selfie is missing. + // row.description = lang(lng_passport_identity_selfie); + // } + //} return row; }; switch (scope.type) { + case Scope::Type::PersonalDetails: + return addReadyError({ + lang(lng_passport_personal_details), + lang(lng_passport_personal_details_enter), + }); case Scope::Type::Identity: - if (scope.documents.empty()) { - return addReadyError({ - lang(lng_passport_personal_details), - lang(lng_passport_personal_details_enter), - }); - } else if (scope.documents.size() == 1) { + Assert(!scope.documents.empty()); + if (scope.documents.size() == 1) { switch (scope.documents.front()->type) { case Value::Type::Passport: return addReadyError({ @@ -325,13 +427,14 @@ ScopeRow ComputeScopeRow(const Scope &scope) { lang(lng_passport_identity_title), lang(lng_passport_identity_description), }); - case Scope::Type::Address: - if (scope.documents.empty()) { - return addReadyError({ - lang(lng_passport_address), - lang(lng_passport_address_enter), + case Scope::Type::AddressDetails: + return addReadyError({ + lang(lng_passport_address), + lang(lng_passport_address_enter), }); - } else if (scope.documents.size() == 1) { + case Scope::Type::Address: + Assert(!scope.documents.empty()); + if (scope.documents.size() == 1) { switch (scope.documents.front()->type) { case Value::Type::BankStatement: return addReadyError({ diff --git a/Telegram/SourceFiles/passport/passport_form_view_controller.h b/Telegram/SourceFiles/passport/passport_form_view_controller.h index b32729345..925c15a41 100644 --- a/Telegram/SourceFiles/passport/passport_form_view_controller.h +++ b/Telegram/SourceFiles/passport/passport_form_view_controller.h @@ -13,17 +13,18 @@ namespace Passport { struct Scope { enum class Type { + PersonalDetails, Identity, + AddressDetails, Address, Phone, Email, }; - Scope(Type type, not_null fields); + explicit Scope(Type type); Type type; - not_null fields; + const Value *details = nullptr; std::vector> documents; - bool selfieRequired = false; }; struct ScopeRow { @@ -33,8 +34,8 @@ struct ScopeRow { QString error; }; -std::vector ComputeScopes( - not_null controller); +bool ValidateForm(const Form &form); +std::vector ComputeScopes(const Form &form); QString ComputeScopeRowReadyString(const Scope &scope); ScopeRow ComputeScopeRow(const Scope &scope); diff --git a/Telegram/SourceFiles/passport/passport_panel_controller.cpp b/Telegram/SourceFiles/passport/passport_panel_controller.cpp index c7519e2be..5895895cf 100644 --- a/Telegram/SourceFiles/passport/passport_panel_controller.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_controller.cpp @@ -99,6 +99,7 @@ EditDocumentScheme GetDocumentScheme( return !CountryFormat(value).isEmpty(); }); + // #TODO passport scheme switch (type) { case Scope::Type::Identity: { auto result = Scheme(); @@ -360,7 +361,7 @@ BoxContent *BoxPointer::operator->() const { PanelController::PanelController(not_null form) : _form(form) -, _scopes(ComputeScopes(_form)) { +, _scopes(ComputeScopes(_form->form())) { _form->secretReadyEvents( ) | rpl::start_with_next([=] { ensurePanelCreated(); @@ -378,8 +379,6 @@ PanelController::PanelController(not_null form) }) | rpl::start_with_next([=](not_null field) { _verificationBoxes.erase(field); }, lifetime()); - - _scopes = ComputeScopes(_form); } not_null PanelController::bot() const { @@ -397,12 +396,14 @@ void PanelController::fillRows( bool ready, bool error)> callback) { if (_scopes.empty()) { - _scopes = ComputeScopes(_form); + _scopes = ComputeScopes(_form->form()); } for (const auto &scope : _scopes) { const auto row = ComputeScopeRow(scope); - const auto main = scope.fields; - if (!row.ready.isEmpty()) { + const auto main = scope.details + ? not_null(scope.details) + : scope.documents[0]; + if (main && !row.ready.isEmpty()) { _submitErrors.erase( ranges::remove(_submitErrors, main), _submitErrors.end()); @@ -546,9 +547,7 @@ void PanelController::uploadSpecialScan( QByteArray &&content) { Expects(_editScope != nullptr); Expects(_editDocument != nullptr); - Expects(_editDocument->requiresSpecialScan( - type, - _editScope->selfieRequired)); + Expects(_editDocument->requiresSpecialScan(type)); _form->uploadSpecialScan(_editDocument, type, std::move(content)); } @@ -556,9 +555,7 @@ void PanelController::uploadSpecialScan( void PanelController::deleteSpecialScan(SpecialFile type) { Expects(_editScope != nullptr); Expects(_editDocument != nullptr); - Expects(_editDocument->requiresSpecialScan( - type, - _editScope->selfieRequired)); + Expects(_editDocument->requiresSpecialScan(type)); _form->deleteSpecialScan(_editDocument, type); } @@ -566,9 +563,7 @@ void PanelController::deleteSpecialScan(SpecialFile type) { void PanelController::restoreSpecialScan(SpecialFile type) { Expects(_editScope != nullptr); Expects(_editDocument != nullptr); - Expects(_editDocument->requiresSpecialScan( - type, - _editScope->selfieRequired)); + Expects(_editDocument->requiresSpecialScan(type)); _form->restoreSpecialScan(_editDocument, type); } @@ -642,9 +637,15 @@ ScanInfo PanelController::collectScanInfo(const EditFile &file) const { std::vector PanelController::collectErrors( not_null value) const { auto result = std::vector(); + if (!value->error.isEmpty()) { + result.push_back({ FileKey(), value->error }); + } if (!value->scanMissingError.isEmpty()) { result.push_back({ FileKey(), value->scanMissingError }); } + if (!value->translationMissingError.isEmpty()) { + result.push_back({ FileKey(), value->translationMissingError }); + } const auto addFileError = [&](const EditFile &file) { if (!file.fields.error.isEmpty()) { const auto key = FileKey{ file.fields.id, file.fields.dcId }; @@ -654,6 +655,9 @@ std::vector PanelController::collectErrors( 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); } @@ -671,13 +675,14 @@ auto PanelController::deleteValueLabel() const if (hasValueDocument()) { return Lang::Viewer(lng_passport_delete_document); - } - if (!hasValueFields()) { + } else if (!hasValueFields()) { return base::none; } switch (_editScope->type) { + case Scope::Type::PersonalDetails: case Scope::Type::Identity: return Lang::Viewer(lng_passport_delete_details); + case Scope::Type::AddressDetails: case Scope::Type::Address: return Lang::Viewer(lng_passport_delete_address); case Scope::Type::Email: @@ -700,27 +705,26 @@ bool PanelController::hasValueDocument() const { } bool PanelController::hasValueFields() const { - Expects(_editValue != nullptr); - - return !_editValue->data.parsed.fields.empty(); + return _editValue && !_editValue->data.parsed.fields.empty(); } void PanelController::deleteValue() { Expects(_editScope != nullptr); + Expects(hasValueDocument() || hasValueFields()); if (savingScope()) { return; } const auto text = [&] { switch (_editScope->type) { + case Scope::Type::PersonalDetails: + return lang(lng_passport_delete_details_sure); case Scope::Type::Identity: - return lang(hasValueDocument() - ? lng_passport_delete_document_sure - : lng_passport_delete_details_sure); + return lang(lng_passport_delete_document_sure); + case Scope::Type::AddressDetails: + return lang(lng_passport_delete_address_sure); case Scope::Type::Address: - return lang(hasValueDocument() - ? lng_passport_delete_document_sure - : lng_passport_delete_address_sure); + return lang(lng_passport_delete_document_sure); case Scope::Type::Phone: return lang(lng_passport_delete_phone_sure); case Scope::Type::Email: @@ -745,7 +749,7 @@ void PanelController::deleteValue() { } void PanelController::deleteValueSure(bool withDetails) { - Expects(_editValue != nullptr); + Expects(!withDetails || _editValue != nullptr); if (hasValueDocument()) { _form->deleteValueEdit(_editDocument); @@ -830,21 +834,22 @@ int PanelController::findNonEmptyDocumentIndex(const Scope &scope) const { const auto &documents = scope.documents; const auto i = ranges::find_if( documents, - [&](not_null document) { - return document->scansAreFilled(scope.selfieRequired); + [](not_null document) { + return document->scansAreFilled(); }); if (i != end(documents)) { return (i - begin(documents)); } // If we have a document where only selfie is not filled - return it. - const auto j = ranges::find_if( - documents, - [&](not_null document) { - return document->scansAreFilled(false); - }); - if (j != end(documents)) { - return (j - begin(documents)); - } + // #TODO passport half-full value + //const auto j = ranges::find_if( + // documents, + // [&](not_null document) { + // return document->scansAreFilled(false); + // }); + //if (j != end(documents)) { + // return (j - begin(documents)); + //} return -1; } @@ -936,8 +941,7 @@ void PanelController::editWithUpload(int index, int documentIndex) { const auto &document = _scopes[index].documents[documentIndex]; const auto requiresSpecialScan = document->requiresSpecialScan( - SpecialFile::FrontSide, - false); + SpecialFile::FrontSide); const auto allowMany = !requiresSpecialScan; const auto widget = _panel->widget(); EditScans::ChooseScan(widget.get(), [=](QByteArray &&content) { @@ -981,21 +985,26 @@ void PanelController::editScope(int index, int documentIndex) { && documentIndex < _scopes[index].documents.size())); _editScope = &_scopes[index]; - _editValue = _editScope->fields; + _editValue = _editScope->details; _editDocument = (documentIndex >= 0) ? _scopes[index].documents[documentIndex].get() : nullptr; + Assert(_editValue || _editDocument); - _form->startValueEdit(_editValue); + if (_editValue) { + _form->startValueEdit(_editValue); + } if (_editDocument) { _form->startValueEdit(_editDocument); } auto content = [&]() -> object_ptr { + // #TODO passport pass and display value->error switch (_editScope->type) { case Scope::Type::Identity: case Scope::Type::Address: { - auto result = _editDocument + Assert(_editDocument != nullptr); + auto result = _editValue ? object_ptr( _panel->widget(), this, @@ -1007,6 +1016,7 @@ void PanelController::editScope(int index, int documentIndex) { _editDocument->scanMissingError, valueFiles(*_editDocument), valueSpecialFiles(*_editDocument)) + // #TODO passport document without details : object_ptr( _panel->widget(), this, @@ -1018,8 +1028,23 @@ void PanelController::editScope(int index, int documentIndex) { }; return std::move(result); } break; + case Scope::Type::PersonalDetails: + case Scope::Type::AddressDetails: { + Assert(_editValue != nullptr); + auto result = object_ptr( + _panel->widget(), + this, + GetDocumentScheme(_editScope->type), + _editValue->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: { + Assert(_editValue != nullptr); const auto &parsed = _editValue->data.parsedInEdit; const auto valueIt = parsed.fields.find("value"); const auto value = (valueIt == end(parsed.fields) @@ -1081,16 +1106,12 @@ void PanelController::processValueSaveFinished( } bool PanelController::uploadingScopeScan() const { - Expects(_editValue != nullptr); - - return _form->uploadingScan(_editValue) + return (_editValue && _form->uploadingScan(_editValue)) || (_editDocument && _form->uploadingScan(_editDocument)); } bool PanelController::savingScope() const { - Expects(_editValue != nullptr); - - return _form->savingValue(_editValue) + return (_editValue && _form->savingValue(_editValue)) || (_editDocument && _form->savingValue(_editDocument)); } @@ -1173,7 +1194,7 @@ std::map PanelController::valueSpecialFiles( SpecialFile::Selfie }; for (const auto type : types) { - if (value.requiresSpecialScan(type, _editScope->selfieRequired)) { + if (value.requiresSpecialScan(type)) { const auto i = value.specialScansInEdit.find(type); const auto j = result.emplace( type, @@ -1190,7 +1211,9 @@ void PanelController::cancelValueEdit() { Expects(_editScope != nullptr); _editScopeBoxes.clear(); - _form->cancelValueEdit(base::take(_editValue)); + if (const auto value = base::take(_editValue)) { + _form->cancelValueEdit(value); + } if (const auto document = base::take(_editDocument)) { _form->cancelValueEdit(document); } @@ -1199,7 +1222,6 @@ void PanelController::cancelValueEdit() { void PanelController::saveScope(ValueMap &&data, ValueMap &&filesData) { Expects(_panel != nullptr); - Expects(_editValue != nullptr); if (uploadingScopeScan()) { showToast(lang(lng_passport_wait_upload)); @@ -1208,7 +1230,9 @@ void PanelController::saveScope(ValueMap &&data, ValueMap &&filesData) { return; } - _form->saveValueEdit(_editValue, std::move(data)); + if (_editValue) { + _form->saveValueEdit(_editValue, std::move(data)); + } if (_editDocument) { _form->saveValueEdit(_editDocument, std::move(filesData)); } else { @@ -1219,12 +1243,11 @@ void PanelController::saveScope(ValueMap &&data, ValueMap &&filesData) { bool PanelController::editScopeChanged( const ValueMap &data, const ValueMap &filesData) const { - Expects(_editValue != nullptr); - - if (_form->editValueChanged(_editValue, data)) { + if (_editValue && _form->editValueChanged(_editValue, data)) { + return true; + } else if (_editDocument + && _form->editValueChanged(_editDocument, filesData)) { return true; - } else if (_editDocument) { - return _form->editValueChanged(_editDocument, filesData); } return false; }