diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index cd5feff9a..67b0fbcbf 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1534,6 +1534,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_passport_identity_card_upload" = "Upload a scan of your identity card"; "lng_passport_identity_license" = "Driver's license"; "lng_passport_identity_license_upload" = "Upload a scan of your driver's license"; +"lng_passport_identity_internal" = "Internal passport"; +"lng_passport_identity_internal_upload" = "Upload a scan of your internal passport"; "lng_passport_identity_about" = "Your document must contain your photograph, name and surname, date of birth, citizenship, document issue date and document number."; "lng_passport_address_title" = "Residential address"; "lng_passport_address_description" = "Upload a proof of your address"; @@ -1543,6 +1545,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_passport_address_statement_upload" = "Upload a scan of your bank statement"; "lng_passport_address_agreement" = "Tenancy agreement"; "lng_passport_address_agreement_upload" = "Upload a scan of your tenancy agreement"; +"lng_passport_address_registration" = "Passport registration"; +"lng_passport_address_registration_upload" = "Upload a scan of your passport registration"; +"lng_passport_address_temporary" = "Temporary registration"; +"lng_passport_address_temporary_upload" = "Upload a scan of your temporary registration"; "lng_passport_address_about" = "To confirm your address please upload a scan or photo of the selected document (all pages)."; "lng_passport_document_type" = "Please choose the type of your document:"; "lng_passport_upload_document" = "Upload document"; @@ -1563,8 +1569,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_passport_upload_more" = "Upload additional scans"; "lng_passport_selfie_title" = "Selfie"; "lng_passport_selfie_name" = "Photo"; -"lng_passport_selfie_description" = "Take a picture of yourself holding hour document."; +"lng_passport_selfie_description" = "Take a picture of yourself holding your document."; "lng_passport_upload_selfie" = "Upload selfie"; +"lng_passport_front_side_title" = "Front side"; +"lng_passport_front_side_name" = "Scan"; +"lng_passport_front_side_description" = "Upload front side of your document."; +"lng_passport_upload_front_side" = "Upload front side scan"; +"lng_passport_reverse_side_title" = "Reverse side"; +"lng_passport_reverse_side_name" = "Scan"; +"lng_passport_reverse_side_description" = "Upload reverse side of your document."; +"lng_passport_upload_reverse_side" = "Upload reverse side scan"; "lng_passport_personal_details" = "Personal details"; "lng_passport_personal_details_enter" = "Enter your personal details"; "lng_passport_choose_image" = "Choose scan image"; diff --git a/Telegram/Resources/scheme.tl b/Telegram/Resources/scheme.tl index 0f79d5205..32dfaaa67 100644 --- a/Telegram/Resources/scheme.tl +++ b/Telegram/Resources/scheme.tl @@ -982,23 +982,28 @@ secureValueTypePersonalDetails#9d2a81e3 = SecureValueType; secureValueTypePassport#3dac6a00 = SecureValueType; secureValueTypeDriverLicense#6e425c4 = SecureValueType; secureValueTypeIdentityCard#a0d0744b = SecureValueType; +secureValueTypeInternalPassport#99a48f23 = SecureValueType; secureValueTypeAddress#cbe31e26 = SecureValueType; secureValueTypeUtilityBill#fc36954e = SecureValueType; secureValueTypeBankStatement#89137c0d = SecureValueType; secureValueTypeRentalAgreement#8b883488 = SecureValueType; +secureValueTypePassportRegistration#99e3806a = SecureValueType; +secureValueTypeTemporaryRegistration#ea02ec33 = SecureValueType; secureValueTypePhone#b320aadb = SecureValueType; secureValueTypeEmail#8e3ca7ee = SecureValueType; -secureValue#ec4134c8 flags:# type:SecureValueType data:flags.0?SecureData files:flags.1?Vector plain_data:flags.2?SecurePlainData selfie:flags.3?SecureFile hash:bytes = SecureValue; +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; -inputSecureValue#c0da30f0 flags:# type:SecureValueType data:flags.0?SecureData files:flags.1?Vector plain_data:flags.2?SecurePlainData selfie:flags.3?InputSecureFile = InputSecureValue; +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; secureValueHash#ed1ecdb0 type:SecureValueType hash:bytes = SecureValueHash; secureValueErrorData#e8a40bd9 type:SecureValueType data_hash:bytes field:string text:string = SecureValueError; +secureValueErrorFrontSide#be3dfa type:SecureValueType file_hash:bytes text:string = SecureValueError; +secureValueErrorReverseSide#868a2aa5 type:SecureValueType file_hash:bytes text:string = SecureValueError; +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; -secureValueErrorSelfie#e537ced6 type:SecureValueType file_hash:bytes text:string = SecureValueError; secureCredentialsEncrypted#33f0ea47 data:bytes hash:bytes secret:bytes = SecureCredentialsEncrypted; @@ -1287,4 +1292,4 @@ langpack.getStrings#2e1ee318 lang_code:string keys:Vector = Vector; -// LAYER 80 +// LAYER 81 diff --git a/Telegram/SourceFiles/codegen/scheme/codegen_scheme.py b/Telegram/SourceFiles/codegen/scheme/codegen_scheme.py index ae17c8c23..c2199331d 100644 --- a/Telegram/SourceFiles/codegen/scheme/codegen_scheme.py +++ b/Telegram/SourceFiles/codegen/scheme/codegen_scheme.py @@ -55,7 +55,7 @@ addChildParentFlags('MTPDchannelForbidden', 'MTPDchannel'); parentFlagsCheck = {}; countedTypeIdExceptions = {}; -for i in range(77,81): +for i in range(77, 82): countedTypeIdExceptions[i] = {} countedTypeIdExceptions[i]['channel'] = True countedTypeIdExceptions['ipPortSecret'] = True diff --git a/Telegram/SourceFiles/history/history_service.cpp b/Telegram/SourceFiles/history/history_service.cpp index 97724f315..475f7bebc 100644 --- a/Telegram/SourceFiles/history/history_service.cpp +++ b/Telegram/SourceFiles/history/history_service.cpp @@ -177,12 +177,15 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { case mtpc_secureValueTypePassport: case mtpc_secureValueTypeDriverLicense: case mtpc_secureValueTypeIdentityCard: + case mtpc_secureValueTypeInternalPassport: return lang(lng_action_secure_proof_of_identity); case mtpc_secureValueTypeAddress: return lang(lng_action_secure_address); case mtpc_secureValueTypeUtilityBill: case mtpc_secureValueTypeBankStatement: case mtpc_secureValueTypeRentalAgreement: + case mtpc_secureValueTypePassportRegistration: + case mtpc_secureValueTypeTemporaryRegistration: return lang(lng_action_secure_proof_of_address); case mtpc_secureValueTypePhone: return lang(lng_action_secure_phone); diff --git a/Telegram/SourceFiles/passport/passport_form_controller.cpp b/Telegram/SourceFiles/passport/passport_form_controller.cpp index c5fdb6d41..b3dec4d13 100644 --- a/Telegram/SourceFiles/passport/passport_form_controller.cpp +++ b/Telegram/SourceFiles/passport/passport_form_controller.cpp @@ -69,41 +69,64 @@ QImage ReadImage(bytes::const_span buffer) { Value::Type ConvertType(const MTPSecureValueType &type) { using Type = Value::Type; switch (type.type()) { - case mtpc_secureValueTypePersonalDetails: return Type::PersonalDetails; - case mtpc_secureValueTypePassport: return Type::Passport; - case mtpc_secureValueTypeDriverLicense: return Type::DriverLicense; - case mtpc_secureValueTypeIdentityCard: return Type::IdentityCard; - case mtpc_secureValueTypeAddress: return Type::Address; - case mtpc_secureValueTypeUtilityBill: return Type::UtilityBill; - case mtpc_secureValueTypeBankStatement: return Type::BankStatement; - case mtpc_secureValueTypeRentalAgreement: return Type::RentalAgreement; - case mtpc_secureValueTypePhone: return Type::Phone; - case mtpc_secureValueTypeEmail: return Type::Email; + case mtpc_secureValueTypePersonalDetails: + return Type::PersonalDetails; + case mtpc_secureValueTypePassport: + return Type::Passport; + case mtpc_secureValueTypeDriverLicense: + return Type::DriverLicense; + case mtpc_secureValueTypeIdentityCard: + return Type::IdentityCard; + case mtpc_secureValueTypeInternalPassport: + return Type::InternalPassport; + case mtpc_secureValueTypeAddress: + return Type::Address; + case mtpc_secureValueTypeUtilityBill: + return Type::UtilityBill; + case mtpc_secureValueTypeBankStatement: + return Type::BankStatement; + case mtpc_secureValueTypeRentalAgreement: + return Type::RentalAgreement; + case mtpc_secureValueTypePassportRegistration: + return Type::PassportRegistration; + case mtpc_secureValueTypeTemporaryRegistration: + return Type::TemporaryRegistration; + case mtpc_secureValueTypePhone: + return Type::Phone; + case mtpc_secureValueTypeEmail: + return Type::Email; } Unexpected("Type in secureValueType type."); }; MTPSecureValueType ConvertType(Value::Type type) { + using Type = Value::Type; switch (type) { - case Value::Type::PersonalDetails: + case Type::PersonalDetails: return MTP_secureValueTypePersonalDetails(); - case Value::Type::Passport: + case Type::Passport: return MTP_secureValueTypePassport(); - case Value::Type::DriverLicense: + case Type::DriverLicense: return MTP_secureValueTypeDriverLicense(); - case Value::Type::IdentityCard: + case Type::IdentityCard: return MTP_secureValueTypeIdentityCard(); - case Value::Type::Address: + case Type::InternalPassport: + return MTP_secureValueTypeInternalPassport(); + case Type::Address: return MTP_secureValueTypeAddress(); - case Value::Type::UtilityBill: + case Type::UtilityBill: return MTP_secureValueTypeUtilityBill(); - case Value::Type::BankStatement: + case Type::BankStatement: return MTP_secureValueTypeBankStatement(); - case Value::Type::RentalAgreement: + case Type::RentalAgreement: return MTP_secureValueTypeRentalAgreement(); - case Value::Type::Phone: + case Type::PassportRegistration: + return MTP_secureValueTypePassportRegistration(); + case Type::TemporaryRegistration: + return MTP_secureValueTypeTemporaryRegistration(); + case Type::Phone: return MTP_secureValueTypePhone(); - case Value::Type::Email: + case Type::Email: return MTP_secureValueTypeEmail(); } Unexpected("Type in FormController::submit."); @@ -135,21 +158,34 @@ FormRequest PreprocessRequest(const FormRequest &request) { } QString ValueCredentialsKey(Value::Type type) { + using Type = Value::Type; switch (type) { - case Value::Type::PersonalDetails: return "personal_details"; - case Value::Type::Passport: return "passport"; - case Value::Type::DriverLicense: return "driver_license"; - case Value::Type::IdentityCard: return "identity_card"; - case Value::Type::Address: return "address"; - case Value::Type::UtilityBill: return "utility_bill"; - case Value::Type::BankStatement: return "bank_statement"; - case Value::Type::RentalAgreement: return "rental_agreement"; - case Value::Type::Phone: - case Value::Type::Email: return QString(); + case Type::PersonalDetails: return "personal_details"; + case Type::Passport: return "passport"; + case Type::DriverLicense: return "driver_license"; + case Type::IdentityCard: return "identity_card"; + case Type::InternalPassport: return "internal_passport"; + case Type::Address: return "address"; + case Type::UtilityBill: return "utility_bill"; + case Type::BankStatement: return "bank_statement"; + case Type::RentalAgreement: return "rental_agreement"; + case Type::PassportRegistration: return "passport_registration"; + case Type::TemporaryRegistration: return "temporary_registration"; + case Type::Phone: + case Type::Email: return QString(); } Unexpected("Type in ValueCredentialsKey."); } +QString SpecialScanCredentialsKey(SpecialFile type) { + switch (type) { + case SpecialFile::FrontSide: return "front_side"; + case SpecialFile::ReverseSide: return "reverse_side"; + case SpecialFile::Selfie: return "selfie"; + } + Unexpected("Type in SpecialScanCredentialsKey."); +} + } // namespace FormRequest::FormRequest( @@ -215,6 +251,43 @@ UploadScanData *UploadScanDataPointer::operator->() const { Value::Value(Type type) : type(type) { } +bool Value::requiresSpecialScan( + SpecialFile type, + bool selfieRequired) const { + switch (type) { + case SpecialFile::FrontSide: + return (this->type == Type::Passport) + || (this->type == Type::DriverLicense) + || (this->type == Type::IdentityCard) + || (this->type == Type::InternalPassport); + case SpecialFile::ReverseSide: + return (this->type == Type::DriverLicense) + || (this->type == Type::IdentityCard); + case SpecialFile::Selfie: + return selfieRequired; + } + Unexpected("Special scan type in requiresSpecialScan."); +} + +bool Value::scansAreFilled(bool selfieRequired) const { + if (!requiresSpecialScan(SpecialFile::FrontSide, selfieRequired)) { + return !scans.empty(); + } + const auto types = { + SpecialFile::FrontSide, + SpecialFile::ReverseSide, + SpecialFile::Selfie + }; + for (const auto type : types) { + if (requiresSpecialScan(type, selfieRequired) + && (specialScans.find(type) == end(specialScans))) { + return false; + } + } + return true; +}; + + FormController::FormController( not_null controller, const FormRequest &request) @@ -266,8 +339,13 @@ auto FormController::prepareFinalData() -> FinalData { } object.insert("files", files); } - if (_form.identitySelfieRequired && value->selfie) { - object.insert("selfie", GetJSONFromFile(*value->selfie)); + for (const auto &[type, scan] : value->specialScans) { + const auto selfieRequired = _form.identitySelfieRequired; + if (value->requiresSpecialScan(type, selfieRequired)) { + object.insert( + SpecialScanCredentialsKey(type), + GetJSONFromFile(scan)); + } } secureData.insert(key, object); }; @@ -290,7 +368,7 @@ auto FormController::prepareFinalData() -> FinalData { addValue(scope.fields); if (!scope.documents.empty()) { for (const auto &document : scope.documents) { - if (!document->scans.empty()) { + if (document->scansAreFilled(scope.selfieRequired)) { addValue(document); break; } @@ -549,6 +627,18 @@ void FormController::fillErrors() { LOG(("API Error: File not found for error value.")); return nullptr; }; + const auto setSpecialScanError = [&](SpecialFile type, auto &&data) { + if (const auto value = find(data.vtype)) { + const auto i = value->specialScans.find(type); + if (i != value->specialScans.end()) { + i->second.error = qs(data.vtext); + } else { + LOG(("API Error: " + "Special scan %1 not found for error value." + ).arg(int(type))); + } + } + }; for (const auto &error : _form.pendingErrors) { switch (error.type()) { case mtpc_secureValueErrorData: { @@ -573,16 +663,19 @@ void FormController::fillErrors() { value->scanMissingError = qs(data.vtext); } } break; + case mtpc_secureValueErrorFrontSide: { + const auto &data = error.c_secureValueErrorFrontSide(); + setSpecialScanError(SpecialFile::FrontSide, data); + } break; + case mtpc_secureValueErrorReverseSide: { + const auto &data = error.c_secureValueErrorReverseSide(); + setSpecialScanError(SpecialFile::ReverseSide, data); + } break; case mtpc_secureValueErrorSelfie: { const auto &data = error.c_secureValueErrorSelfie(); - if (const auto value = find(data.vtype)) { - if (value->selfie) { - value->selfie->error = qs(data.vtext); - } else { - LOG(("API Error: Selfie not found for error value.")); - } - } + setSpecialScanError(SpecialFile::Selfie, data); } break; + default: Unexpected("Error type in FormController::fillErrors."); } } } @@ -639,8 +732,10 @@ bool FormController::validateValueSecrets(Value &value) { return false; } } - if (value.selfie && !validateFileSecret(*value.selfie)) { - return false; + for (auto &[type, file] : value.specialScans) { + if (!validateFileSecret(file)) { + return false; + } } return true; } @@ -692,25 +787,40 @@ void FormController::restoreScan( scanDeleteRestore(value, scanIndex, false); } -void FormController::uploadSelfie( +void FormController::uploadSpecialScan( not_null value, + SpecialFile type, QByteArray &&content) { const auto nonconst = findValue(value); - nonconst->selfieInEdit = EditFile{ nonconst, File(), nullptr }; - auto &file = *nonconst->selfieInEdit; + auto scanInEdit = EditFile{ nonconst, File(), nullptr }; + auto i = nonconst->specialScansInEdit.find(type); + if (i != nonconst->specialScansInEdit.end()) { + i->second = std::move(scanInEdit); + } else { + i = nonconst->specialScansInEdit.emplace( + type, + std::move(scanInEdit)).first; + } + auto &file = i->second; encryptFile(file, std::move(content), [=](UploadScanData &&result) { + const auto i = nonconst->specialScansInEdit.find(type); + Assert(i != nonconst->specialScansInEdit.end()); uploadEncryptedFile( - *nonconst->selfieInEdit, + i->second, std::move(result)); }); } -void FormController::deleteSelfie(not_null value) { - selfieDeleteRestore(value, true); +void FormController::deleteSpecialScan( + not_null value, + SpecialFile type) { + specialScanDeleteRestore(value, type, true); } -void FormController::restoreSelfie(not_null value) { - selfieDeleteRestore(value, false); +void FormController::restoreSpecialScan( + not_null value, + SpecialFile type) { + specialScanDeleteRestore(value, type, false); } void FormController::prepareFile( @@ -779,13 +889,14 @@ void FormController::scanDeleteRestore( _scanUpdated.fire(&scan); } -void FormController::selfieDeleteRestore( +void FormController::specialScanDeleteRestore( not_null value, + SpecialFile type, bool deleted) { - Expects(value->selfieInEdit.has_value()); - const auto nonconst = findValue(value); - auto &scan = *nonconst->selfieInEdit; + const auto i = nonconst->specialScansInEdit.find(type); + Assert(i != nonconst->specialScansInEdit.end()); + auto &scan = i->second; scan.deleted = deleted; _scanUpdated.fire(&scan); } @@ -1006,8 +1117,11 @@ void FormController::startValueEdit(not_null value) { for (auto &scan : nonconst->scans) { loadFile(scan); } - if (nonconst->selfie && _form.identitySelfieRequired) { - loadFile(*nonconst->selfie); + for (auto &[type, scan] : nonconst->specialScans) { + const auto selfieRequired = _form.identitySelfieRequired; + if (nonconst->requiresSpecialScan(type, selfieRequired)) { + loadFile(scan); + } } nonconst->scansInEdit = ranges::view::all( nonconst->scans @@ -1015,13 +1129,12 @@ void FormController::startValueEdit(not_null value) { return EditFile(nonconst, file, nullptr); }) | ranges::to_vector; - if (nonconst->selfie) { - nonconst->selfieInEdit = EditFile( + nonconst->specialScansInEdit.clear(); + for (const auto &[type, scan] : nonconst->specialScans) { + nonconst->specialScansInEdit.emplace(type, EditFile( nonconst, - *nonconst->selfie, - nullptr); - } else { - nonconst->selfieInEdit = base::none; + scan, + nullptr)); } nonconst->data.parsedInEdit = nonconst->data.parsed; @@ -1116,18 +1229,27 @@ bool FormController::savingValue(not_null value) const { } bool FormController::uploadingScan(not_null value) const { + const auto uploading = [](const EditFile &file) { + return file.uploadData + && file.uploadData->fullId + && !file.deleted; + }; + if (ranges::find_if(value->scansInEdit, uploading) + != end(value->scansInEdit)) { + return true; + } + if (ranges::find_if(value->specialScansInEdit, [&](const auto &pair) { + return uploading(pair.second); + }) != end(value->specialScansInEdit)) { + return true; + } for (const auto &scan : value->scansInEdit) { - if (scan.uploadData - && scan.uploadData->fullId - && !scan.deleted) { + if (uploading(scan)) { return true; } } - if (value->selfieInEdit) { - const auto &selfie = *value->selfieInEdit; - if (selfie.uploadData - && selfie.uploadData->fullId - && !selfie.deleted) { + for (const auto &[type, scan] : value->specialScansInEdit) { + if (uploading(scan)) { return true; } } @@ -1155,7 +1277,7 @@ void FormController::clearValueEdit(not_null value) { return; } value->scansInEdit.clear(); - value->selfieInEdit = base::none; + value->specialScansInEdit.clear(); value->data.encryptedSecretInEdit.clear(); value->data.hashInEdit.clear(); value->data.parsedInEdit = ValueMap(); @@ -1200,8 +1322,10 @@ bool FormController::editValueChanged( return true; } } - if (value->selfieInEdit && editFileChanged(*value->selfieInEdit)) { - return true; + for (const auto &[type, scan] : value->specialScansInEdit) { + if (editFileChanged(scan)) { + return true; + } } auto existing = value->data.parsed.fields; for (const auto &[key, value] : data.fields) { @@ -1230,7 +1354,7 @@ void FormController::saveValueEdit( nonconst->saveRequestId = -1; crl::on_main(this, [=] { base::take(nonconst->scansInEdit); - base::take(nonconst->selfieInEdit); + base::take(nonconst->specialScansInEdit); base::take(nonconst->data.encryptedSecretInEdit); base::take(nonconst->data.hashInEdit); base::take(nonconst->data.parsedInEdit); @@ -1313,41 +1437,36 @@ void FormController::saveEncryptedValue(not_null value) { _secret, value->data.hashInEdit); - const auto selfie = (value->selfieInEdit - && !value->selfieInEdit->deleted) - ? inputFile(*value->selfieInEdit) - : MTPInputSecureFile(); + const auto hasSpecialFile = [&](SpecialFile type) { + const auto i = value->specialScansInEdit.find(type); + return (i != end(value->specialScansInEdit) && !i->second.deleted); + }; + const auto specialFile = [&](SpecialFile type) { + const auto i = value->specialScansInEdit.find(type); + return (i != end(value->specialScansInEdit) && !i->second.deleted) + ? inputFile(i->second) + : MTPInputSecureFile(); + }; + const auto frontSide = specialFile(SpecialFile::FrontSide); + const auto reverseSide = specialFile(SpecialFile::ReverseSide); + const auto selfie = specialFile(SpecialFile::Selfie); - const auto type = [&] { - switch (value->type) { - case Value::Type::PersonalDetails: - return MTP_secureValueTypePersonalDetails(); - case Value::Type::Passport: - return MTP_secureValueTypePassport(); - case Value::Type::DriverLicense: - return MTP_secureValueTypeDriverLicense(); - case Value::Type::IdentityCard: - return MTP_secureValueTypeIdentityCard(); - case Value::Type::Address: - return MTP_secureValueTypeAddress(); - case Value::Type::UtilityBill: - return MTP_secureValueTypeUtilityBill(); - case Value::Type::BankStatement: - return MTP_secureValueTypeBankStatement(); - case Value::Type::RentalAgreement: - return MTP_secureValueTypeRentalAgreement(); - } - Unexpected("Value type in saveEncryptedValue()."); - }(); + const auto type = ConvertType(value->type); const auto flags = (value->data.parsedInEdit.fields.empty() ? MTPDinputSecureValue::Flag(0) : MTPDinputSecureValue::Flag::f_data) + | (hasSpecialFile(SpecialFile::FrontSide) + ? MTPDinputSecureValue::Flag::f_front_side + : MTPDinputSecureValue::Flag(0)) + | (hasSpecialFile(SpecialFile::ReverseSide) + ? MTPDinputSecureValue::Flag::f_reverse_side + : MTPDinputSecureValue::Flag(0)) + | (hasSpecialFile(SpecialFile::Selfie) + ? MTPDinputSecureValue::Flag::f_selfie + : MTPDinputSecureValue::Flag(0)) | (value->scansInEdit.empty() ? MTPDinputSecureValue::Flag(0) - : MTPDinputSecureValue::Flag::f_files) - | ((value->selfieInEdit && !value->selfieInEdit->deleted) - ? MTPDinputSecureValue::Flag::f_selfie - : MTPDinputSecureValue::Flag(0)); + : MTPDinputSecureValue::Flag::f_files); Assert(flags != MTPDinputSecureValue::Flags(0)); sendSaveRequest(value, MTP_inputSecureValue( @@ -1357,9 +1476,11 @@ void FormController::saveEncryptedValue(not_null value) { MTP_bytes(encryptedData.bytes), MTP_bytes(value->data.hashInEdit), MTP_bytes(value->data.encryptedSecretInEdit)), + frontSide, + reverseSide, + selfie, MTP_vector(inputFiles), - MTPSecurePlainData(), - selfie)); + MTPSecurePlainData())); } void FormController::savePlainTextValue(not_null value) { @@ -1384,9 +1505,11 @@ void FormController::savePlainTextValue(not_null value) { MTP_flags(MTPDinputSecureValue::Flag::f_plain_data), type, MTPSecureData(), + MTPInputSecureFile(), + MTPInputSecureFile(), + MTPInputSecureFile(), MTPVector(), - plain(MTP_string(text)), - MTPInputSecureFile())); + plain(MTP_string(text)))); } void FormController::sendSaveRequest( @@ -1398,13 +1521,13 @@ void FormController::sendSaveRequest( data, MTP_long(_secretId) )).done([=](const MTPSecureValue &result) { - auto filesInEdit = base::take(value->scansInEdit); - if (auto selfie = base::take(value->selfieInEdit)) { - filesInEdit.push_back(std::move(*selfie)); + auto scansInEdit = base::take(value->scansInEdit); + for (auto &[type, scan] : base::take(value->specialScansInEdit)) { + scansInEdit.push_back(std::move(scan)); } const auto editScreens = value->editScreens; - *value = parseValue(result, filesInEdit); + *value = parseValue(result, scansInEdit); decryptValue(*value); value->editScreens = editScreens; @@ -1737,8 +1860,21 @@ auto FormController::parseValue( if (data.has_files()) { result.scans = parseFiles(data.vfiles.v, editData); } + const auto parseSpecialScan = [&]( + SpecialFile type, + const MTPSecureFile &file) { + if (auto parsed = parseFile(file, editData)) { + result.specialScans.emplace(type, std::move(*parsed)); + } + }; + if (data.has_front_side()) { + parseSpecialScan(SpecialFile::FrontSide, data.vfront_side); + } + if (data.has_reverse_side()) { + parseSpecialScan(SpecialFile::ReverseSide, data.vreverse_side); + } if (data.has_selfie()) { - result.selfie = parseFile(data.vselfie, editData); + parseSpecialScan(SpecialFile::Selfie, data.vselfie); } if (data.has_plain_data()) { switch (data.vplain_data.type()) { @@ -1765,8 +1901,10 @@ auto FormController::findEditFile(const FullMsgId &fullId) -> EditFile* { return &scan; } } - if (value.selfieInEdit && found(*value.selfieInEdit)) { - return &*value.selfieInEdit; + for (auto &[special, scan] : value.specialScansInEdit) { + if (found(scan)) { + return &scan; + } } } return nullptr; @@ -1782,8 +1920,10 @@ auto FormController::findEditFile(const FileKey &key) -> EditFile* { return &scan; } } - if (value.selfieInEdit && found(*value.selfieInEdit)) { - return &*value.selfieInEdit; + for (auto &[special, scan] : value.specialScansInEdit) { + if (found(scan)) { + return &scan; + } } } return nullptr; @@ -1800,8 +1940,10 @@ auto FormController::findFile(const FileKey &key) return { &value, &scan }; } } - if (value.selfie && found(*value.selfie)) { - return { &value, &*value.selfie }; + for (auto &[special, scan] : value.specialScans) { + if (found(scan)) { + return { &value, &scan }; + } } } return { nullptr, nullptr }; diff --git a/Telegram/SourceFiles/passport/passport_form_controller.h b/Telegram/SourceFiles/passport/passport_form_controller.h index df453e130..b470dc353 100644 --- a/Telegram/SourceFiles/passport/passport_form_controller.h +++ b/Telegram/SourceFiles/passport/passport_form_controller.h @@ -132,16 +132,27 @@ struct Verification { }; +struct Form; + +enum class SpecialFile { + FrontSide, + ReverseSide, + Selfie, +}; + struct Value { enum class Type { PersonalDetails, Passport, DriverLicense, IdentityCard, + InternalPassport, Address, UtilityBill, BankStatement, RentalAgreement, + PassportRegistration, + TemporaryRegistration, Phone, Email, }; @@ -150,13 +161,16 @@ struct Value { Value(Value &&other) = default; Value &operator=(Value &&other) = default; + bool requiresSpecialScan(SpecialFile type, bool selfieRequired) const; + bool scansAreFilled(bool selfieRequired) const; + Type type; ValueData data; std::vector scans; - std::vector scansInEdit; + std::map specialScans; QString scanMissingError; - base::optional selfie; - base::optional selfieInEdit; + std::vector scansInEdit; + std::map specialScansInEdit; Verification verification; bytes::vector submitHash; @@ -244,9 +258,16 @@ public: void uploadScan(not_null value, QByteArray &&content); void deleteScan(not_null value, int fileIndex); void restoreScan(not_null value, int fileIndex); - void uploadSelfie(not_null value, QByteArray &&content); - void deleteSelfie(not_null value); - void restoreSelfie(not_null value); + void uploadSpecialScan( + not_null value, + SpecialFile type, + QByteArray &&content); + void deleteSpecialScan( + not_null value, + SpecialFile type); + void restoreSpecialScan( + not_null value, + SpecialFile type); rpl::producer<> secretReadyEvents() const; @@ -349,8 +370,9 @@ private: not_null value, int fileIndex, bool deleted); - void selfieDeleteRestore( + void specialScanDeleteRestore( not_null value, + SpecialFile type, bool deleted); QString getPhoneFromValue(not_null value) const; diff --git a/Telegram/SourceFiles/passport/passport_form_view_controller.cpp b/Telegram/SourceFiles/passport/passport_form_view_controller.cpp index 9ea0ba8d6..35fb183c9 100644 --- a/Telegram/SourceFiles/passport/passport_form_view_controller.cpp +++ b/Telegram/SourceFiles/passport/passport_form_view_controller.cpp @@ -22,10 +22,13 @@ std::map ScopeTypesMap() { { 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::UtilityBill, Scope::Type::Address }, { Value::Type::BankStatement, Scope::Type::Address }, { Value::Type::RentalAgreement, Scope::Type::Address }, + { Value::Type::PassportRegistration, Scope::Type::Address }, + { Value::Type::TemporaryRegistration, Scope::Type::Address }, { Value::Type::Phone, Scope::Type::Phone }, { Value::Type::Email, Scope::Type::Email }, }; @@ -111,7 +114,7 @@ QString ComputeScopeRowReadyString(const Scope &scope) { const auto &fields = scope.fields->data.parsed.fields; const auto document = [&]() -> const Value* { for (const auto &document : scope.documents) { - if (!document->scans.empty()) { + if (document->scansAreFilled(scope.selfieRequired)) { return document; } } @@ -119,25 +122,31 @@ QString ComputeScopeRowReadyString(const Scope &scope) { }(); if (document && scope.documents.size() > 1) { pushListValue([&] { + using Type = Value::Type; switch (document->type) { - case Value::Type::Passport: + case Type::Passport: return lang(lng_passport_identity_passport); - case Value::Type::DriverLicense: + case Type::DriverLicense: return lang(lng_passport_identity_license); - case Value::Type::IdentityCard: + case Type::IdentityCard: return lang(lng_passport_identity_card); - case Value::Type::BankStatement: + case Type::InternalPassport: + return lang(lng_passport_identity_internal); + case Type::BankStatement: return lang(lng_passport_address_statement); - case Value::Type::UtilityBill: + case Type::UtilityBill: return lang(lng_passport_address_bill); - case Value::Type::RentalAgreement: + case Type::RentalAgreement: return lang(lng_passport_address_agreement); + case Type::PassportRegistration: + return lang(lng_passport_address_registration); + case Type::TemporaryRegistration: + return lang(lng_passport_address_temporary); default: Unexpected("Files type in ComputeScopeRowReadyString."); } }()); } - if (!scope.documents.empty() - && (!document || (scope.selfieRequired && !document->selfie))) { + if (!scope.documents.empty() && !document) { return QString(); } const auto scheme = GetDocumentScheme(scope.type); @@ -155,8 +164,6 @@ QString ComputeScopeRowReadyString(const Scope &scope) { 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)) { @@ -195,8 +202,10 @@ ScopeRow ComputeScopeRow(const Scope &scope) { errors.push_back(scan.error); } } - if (value->selfie && !value->selfie->error.isEmpty()) { - errors.push_back(value->selfie->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); @@ -236,6 +245,11 @@ ScopeRow ComputeScopeRow(const Scope &scope) { lang(lng_passport_identity_license), lang(lng_passport_identity_license_upload), }); + case Value::Type::InternalPassport: + return addReadyError({ + lang(lng_passport_identity_internal), + lang(lng_passport_identity_internal_upload), + }); default: Unexpected("Identity type in ComputeScopeRow."); } } @@ -266,6 +280,16 @@ ScopeRow ComputeScopeRow(const Scope &scope) { lang(lng_passport_address_agreement), lang(lng_passport_address_agreement_upload), }); + case Value::Type::PassportRegistration: + return addReadyError({ + lang(lng_passport_address_registration), + lang(lng_passport_address_registration_upload), + }); + case Value::Type::TemporaryRegistration: + return addReadyError({ + lang(lng_passport_address_temporary), + lang(lng_passport_address_temporary_upload), + }); default: Unexpected("Address type in ComputeScopeRow."); } } diff --git a/Telegram/SourceFiles/passport/passport_panel_controller.cpp b/Telegram/SourceFiles/passport/passport_panel_controller.cpp index 959f9e89a..16366bc2d 100644 --- a/Telegram/SourceFiles/passport/passport_panel_controller.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_controller.cpp @@ -92,6 +92,9 @@ EditDocumentScheme GetDocumentScheme( case Value::Type::IdentityCard: result.scansHeader = lang(lng_passport_identity_card); break; + case Value::Type::InternalPassport: + result.scansHeader = lang(lng_passport_identity_internal); + break; default: Unexpected("scansType in GetDocumentScheme:Identity."); } @@ -174,6 +177,12 @@ EditDocumentScheme GetDocumentScheme( case Value::Type::RentalAgreement: result.scansHeader = lang(lng_passport_address_agreement); break; + case Value::Type::PassportRegistration: + result.scansHeader = lang(lng_passport_address_registration); + break; + case Value::Type::TemporaryRegistration: + result.scansHeader = lang(lng_passport_address_temporary); + break; default: Unexpected("scansType in GetDocumentScheme:Address."); } @@ -496,28 +505,30 @@ void PanelController::restoreScan(int fileIndex) { _form->restoreScan(_editDocument, fileIndex); } -void PanelController::uploadSelfie(QByteArray &&content) { +void PanelController::uploadSpecialScan( + SpecialFile type, + QByteArray &&content) { Expects(_editScope != nullptr); Expects(_editDocument != nullptr); Expects(_editScope->selfieRequired); - _form->uploadSelfie(_editDocument, std::move(content)); + _form->uploadSpecialScan(_editDocument, type, std::move(content)); } -void PanelController::deleteSelfie() { +void PanelController::deleteSpecialScan(SpecialFile type) { Expects(_editScope != nullptr); Expects(_editDocument != nullptr); Expects(_editScope->selfieRequired); - _form->deleteSelfie(_editDocument); + _form->deleteSpecialScan(_editDocument, type); } -void PanelController::restoreSelfie() { +void PanelController::restoreSpecialScan(SpecialFile type) { Expects(_editScope != nullptr); Expects(_editDocument != nullptr); Expects(_editScope->selfieRequired); - _form->restoreSelfie(_editDocument); + _form->restoreSpecialScan(_editDocument, type); } rpl::producer PanelController::scanUpdated() const { @@ -566,15 +577,23 @@ ScanInfo PanelController::collectScanInfo(const EditFile &file) const { return formatDownloadText(0, file.fields.size); } }(); - auto isSelfie = (file.value == _editDocument) - && (_editDocument->selfieInEdit.has_value()) - && (&file == &*_editDocument->selfieInEdit); + const auto specialType = [&]() -> base::optional { + if (file.value != _editDocument) { + return base::none; + } + for (const auto &[type, scan] : _editDocument->specialScansInEdit) { + if (&file == &scan) { + return type; + } + } + return base::none; + }(); return { FileKey{ file.fields.id, file.fields.dcId }, !file.fields.error.isEmpty() ? file.fields.error : status, file.fields.image, file.deleted, - isSelfie, + specialType, file.fields.error }; } @@ -593,8 +612,8 @@ std::vector PanelController::collectErrors( for (const auto &scan : value->scansInEdit) { addFileError(scan); } - if (value->selfieInEdit) { - addFileError(*value->selfieInEdit); + for (const auto &[type, scan] : value->specialScansInEdit) { + addFileError(scan); } for (const auto &[key, value] : value->data.parsedInEdit.fields) { if (!value.error.isEmpty()) { @@ -635,7 +654,7 @@ bool PanelController::hasValueDocument() const { } return !_editDocument->data.parsed.fields.empty() || !_editDocument->scans.empty() - || _editDocument->selfie.has_value(); + || !_editDocument->specialScans.empty(); } bool PanelController::hasValueFields() const { @@ -748,13 +767,15 @@ void PanelController::ensurePanelCreated() { } } -int PanelController::findNonEmptyIndex( - const std::vector> &files) const { - const auto i = ranges::find_if(files, [](not_null file) { - return !file->scans.empty(); - }); - if (i != end(files)) { - return (i - begin(files)); +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); + }); + if (i != end(documents)) { + return (i - begin(documents)); } return -1; } @@ -764,14 +785,14 @@ void PanelController::editScope(int index) { Expects(_panel != nullptr); Expects(index >= 0 && index < _scopes.size()); - if (_scopes[index].documents.empty()) { + const auto &scope = _scopes[index]; + if (scope.documents.empty()) { editScope(index, -1); } else { - const auto documentIndex = findNonEmptyIndex( - _scopes[index].documents); + const auto documentIndex = findNonEmptyDocumentIndex(scope); if (documentIndex >= 0) { editScope(index, documentIndex); - } else if (_scopes[index].documents.size() > 1) { + } else if (scope.documents.size() > 1) { requestScopeFilesType(index); } else { editWithUpload(index, 0); @@ -802,6 +823,8 @@ void PanelController::requestScopeFilesType(int index) { return lang(lng_passport_identity_card); case Value::Type::DriverLicense: return lang(lng_passport_identity_license); + case Value::Type::InternalPassport: + return lang(lng_passport_identity_internal); default: Unexpected("IdentityType in requestScopeFilesType"); } @@ -823,6 +846,10 @@ void PanelController::requestScopeFilesType(int index) { return lang(lng_passport_address_statement); case Value::Type::RentalAgreement: return lang(lng_passport_address_agreement); + case Value::Type::PassportRegistration: + return lang(lng_passport_address_registration); + case Value::Type::TemporaryRegistration: + return lang(lng_passport_address_temporary); default: Unexpected("AddressType in requestScopeFilesType"); } @@ -842,7 +869,13 @@ void PanelController::editWithUpload(int index, int documentIndex) { EditScans::ChooseScan(_panel.get(), [=](QByteArray &&content) { base::take(_scopeDocumentTypeBox); editScope(index, documentIndex); - uploadScan(std::move(content)); + if (_scopes[index].documents[documentIndex]->requiresSpecialScan( + SpecialFile::FrontSide, + false)) { + uploadSpecialScan(SpecialFile::FrontSide, std::move(content)); + } else { + uploadScan(std::move(content)); + } }, [=](ReadScanError error) { readScanError(error); }); @@ -897,9 +930,7 @@ void PanelController::editScope(int index, int documentIndex) { _editDocument->data.parsedInEdit, _editDocument->scanMissingError, valueFiles(*_editDocument), - (_editScope->selfieRequired - ? valueSelfie(*_editDocument) - : nullptr)) + valueSpecialFiles(*_editDocument)) : object_ptr( _panel.get(), this, @@ -1050,13 +1081,26 @@ std::vector PanelController::valueFiles( return result; } -std::unique_ptr PanelController::valueSelfie( +std::map PanelController::valueSpecialFiles( const Value &value) const { - if (value.selfieInEdit) { - return std::make_unique( - collectScanInfo(*value.selfieInEdit)); + auto result = std::map(); + const auto types = { + SpecialFile::FrontSide, + SpecialFile::ReverseSide, + SpecialFile::Selfie + }; + for (const auto type : types) { + if (value.requiresSpecialScan(type, _editScope->selfieRequired)) { + const auto i = value.specialScansInEdit.find(type); + const auto j = result.emplace( + type, + (i != end(value.specialScansInEdit) + ? collectScanInfo(i->second) + : ScanInfo())).first; + j->second.special = type; + } } - return std::make_unique(); + return result; } void PanelController::cancelValueEdit() { diff --git a/Telegram/SourceFiles/passport/passport_panel_controller.h b/Telegram/SourceFiles/passport/passport_panel_controller.h index 71784d31d..05cc68e6e 100644 --- a/Telegram/SourceFiles/passport/passport_panel_controller.h +++ b/Telegram/SourceFiles/passport/passport_panel_controller.h @@ -30,7 +30,7 @@ struct ScanInfo { QString status; QImage thumb; bool deleted = false; - bool selfie = false; + base::optional special; QString error; }; @@ -81,9 +81,9 @@ public: void uploadScan(QByteArray &&content); void deleteScan(int fileIndex); void restoreScan(int fileIndex); - void uploadSelfie(QByteArray &&content); - void deleteSelfie(); - void restoreSelfie(); + void uploadSpecialScan(SpecialFile type, QByteArray &&content); + void deleteSpecialScan(SpecialFile type); + void restoreSpecialScan(SpecialFile type); rpl::producer scanUpdated() const; rpl::producer saveErrors() const; void readScanError(ReadScanError error); @@ -133,12 +133,12 @@ private: void editScope(int index, int documentIndex); void editWithUpload(int index, int documentIndex); - int findNonEmptyIndex( - const std::vector> &files) const; + int findNonEmptyDocumentIndex(const Scope &scope) const; void requestScopeFilesType(int index); void cancelValueEdit(); std::vector valueFiles(const Value &value) const; - std::unique_ptr valueSelfie(const Value &value) const; + std::map valueSpecialFiles( + const Value &value) const; void processValueSaveFinished(not_null value); void processVerificationNeeded(not_null value); diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp b/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp index 2590a7b89..bfb41bf33 100644 --- a/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp @@ -213,7 +213,7 @@ PanelEditDocument::PanelEditDocument( const ValueMap &scanData, const QString &missingScansError, std::vector &&files, - std::unique_ptr &&selfie) + std::map &&specialFiles) : _controller(controller) , _scheme(std::move(scheme)) , _scroll(this, st::passportPanelScroll) @@ -228,7 +228,7 @@ PanelEditDocument::PanelEditDocument( &scanData, missingScansError, std::move(files), - std::move(selfie)); + std::move(specialFiles)); } PanelEditDocument::PanelEditDocument( @@ -245,7 +245,7 @@ PanelEditDocument::PanelEditDocument( this, langFactory(lng_passport_save_value), st::passportPanelSaveValue) { - setupControls(data, nullptr, QString(), {}, nullptr); + setupControls(data, nullptr, QString(), {}, {}); } void PanelEditDocument::setupControls( @@ -253,13 +253,13 @@ void PanelEditDocument::setupControls( const ValueMap *scanData, const QString &missingScansError, std::vector &&files, - std::unique_ptr &&selfie) { + std::map &&specialFiles) { const auto inner = setupContent( data, scanData, missingScansError, std::move(files), - std::move(selfie)); + std::move(specialFiles)); using namespace rpl::mappers; @@ -277,7 +277,7 @@ not_null PanelEditDocument::setupContent( const ValueMap *scanData, const QString &missingScansError, std::vector &&files, - std::unique_ptr &&selfie) { + std::map &&specialFiles) { const auto inner = _scroll->setOwnedWidget( object_ptr(this)); _scroll->widthValue( @@ -285,15 +285,23 @@ not_null PanelEditDocument::setupContent( inner->resizeToWidth(width); }, inner->lifetime()); - if (scanData) { + if (!specialFiles.empty()) { + _editScans = inner->add( + object_ptr( + inner, + _controller, +// _scheme.scansHeader, +// missingScansError, +// std::move(files), + std::move(specialFiles))); + } else if (scanData) { _editScans = inner->add( object_ptr( inner, _controller, _scheme.scansHeader, missingScansError, - std::move(files), - std::move(selfie))); + std::move(files))); } else { inner->add(object_ptr( inner, diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_document.h b/Telegram/SourceFiles/passport/passport_panel_edit_document.h index e185feb70..ec1e496aa 100644 --- a/Telegram/SourceFiles/passport/passport_panel_edit_document.h +++ b/Telegram/SourceFiles/passport/passport_panel_edit_document.h @@ -30,6 +30,7 @@ struct ValueMap; struct ScanInfo; class EditScans; class PanelDetailsRow; +enum class SpecialFile; enum class PanelDetailsType; struct EditDocumentScheme { @@ -64,7 +65,7 @@ public: const ValueMap &scanData, const QString &missingScansError, std::vector &&files, - std::unique_ptr &&selfie); + std::map &&specialFiles); PanelEditDocument( QWidget *parent, not_null controller, @@ -84,13 +85,13 @@ private: const ValueMap *scanData, const QString &missingScansError, std::vector &&files, - std::unique_ptr &&selfie); + std::map &&specialFiles); not_null setupContent( const ValueMap &data, const ValueMap *scanData, const QString &missingScansError, std::vector &&files, - std::unique_ptr &&selfie); + std::map &&specialFiles); void updateControlsGeometry(); Result collect() const; diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_scans.cpp b/Telegram/SourceFiles/passport/passport_panel_edit_scans.cpp index d79fc2def..ef49bc53e 100644 --- a/Telegram/SourceFiles/passport/passport_panel_edit_scans.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_edit_scans.cpp @@ -106,6 +106,19 @@ private: }; +struct EditScans::SpecialScan { + SpecialScan(ScanInfo &&file); + + ScanInfo file; + QPointer> header; + QPointer wrap; + base::unique_qptr> row; + QPointer upload; + bool errorShown = false; + Animation errorAnimation; + +}; + ScanButton::ScanButton( QWidget *parent, const style::PassportScanRow &st, @@ -235,21 +248,34 @@ void ScanButton::paintEvent(QPaintEvent *e) { width()); } +EditScans::SpecialScan::SpecialScan(ScanInfo &&file) +: file(std::move(file)) { +} + EditScans::EditScans( QWidget *parent, not_null controller, const QString &header, const QString &errorMissing, - std::vector &&files, - std::unique_ptr &&selfie) + std::vector &&files) : RpWidget(parent) , _controller(controller) , _files(std::move(files)) -, _selfie(std::move(selfie)) , _initialCount(_files.size()) , _errorMissing(errorMissing) , _content(this) { - setupContent(header); + setupScans(header); +} + +EditScans::EditScans( + QWidget *parent, + not_null controller, + std::map &&specialFiles) +: RpWidget(parent) +, _controller(controller) +, _initialCount(-1) +, _content(this) { + setupSpecialScans(std::move(specialFiles)); } bool EditScans::uploadedSomeMore() const { @@ -261,6 +287,13 @@ bool EditScans::uploadedSomeMore() const { } base::optional EditScans::validateGetErrorTop() { + auto result = base::optional(); + const auto suggestResult = [&](int value) { + if (!result || *result > value) { + result = value; + } + }; + const auto exists = ranges::find_if( _files, [](const ScanInfo &file) { return !file.deleted; }) != end(_files); @@ -269,11 +302,10 @@ base::optional EditScans::validateGetErrorTop() { [](const ScanInfo &file) { return !file.error.isEmpty(); } ) != end(_files); - auto result = base::optional(); - if (!exists - || ((errorExists || _uploadMoreError) && !uploadedSomeMore())) { + if (_upload && (!exists + || ((errorExists || _uploadMoreError) && !uploadedSomeMore()))) { toggleError(true); - result = (_files.size() > 5) ? _upload->y() : _header->y(); + suggestResult((_files.size() > 5) ? _upload->y() : _header->y()); } const auto nonDeletedErrorIt = ranges::find_if( @@ -283,24 +315,21 @@ base::optional EditScans::validateGetErrorTop() { }); if (nonDeletedErrorIt != end(_files)) { const auto index = (nonDeletedErrorIt - begin(_files)); - toggleError(true); - if (!result) { - result = _rows[index]->y(); - } +// toggleError(true); + suggestResult(_rows[index]->y()); } - if (_selfie - && (!_selfie->key.id - || _selfie->deleted - || !_selfie->error.isEmpty())) { - toggleSelfieError(true); - if (!result) { - result = _selfieHeader->y(); + for (const auto &[type, scan] : _specialScans) { + if (!scan.file.key.id + || scan.file.deleted + || !scan.file.error.isEmpty()) { + toggleSpecialScanError(type, true); + suggestResult(scan.header->y()); } } return result; } -void EditScans::setupContent(const QString &header) { +void EditScans::setupScans(const QString &header) { const auto inner = _content.data(); inner->move(0, 0); @@ -356,43 +385,92 @@ void EditScans::setupContent(const QString &header) { inner, st::passportFormDividerHeight)); - if (_selfie) { - _selfieHeader = inner->add( + init(); +} + +void EditScans::setupSpecialScans(std::map &&files) { + const auto title = [](SpecialFile type) { + switch (type) { + case SpecialFile::FrontSide: + return lang(lng_passport_front_side_title); + case SpecialFile::ReverseSide: + return lang(lng_passport_reverse_side_title); + case SpecialFile::Selfie: + return lang(lng_passport_selfie_title); + } + Unexpected("Type in special row title."); + }; + const auto uploadKey = [](SpecialFile type) { + switch (type) { + case SpecialFile::FrontSide: + return lng_passport_upload_front_side; + case SpecialFile::ReverseSide: + return lng_passport_upload_reverse_side; + case SpecialFile::Selfie: + return lng_passport_upload_selfie; + } + Unexpected("Type in special row upload key."); + }; + const auto description = [](SpecialFile type) { + switch (type) { + case SpecialFile::FrontSide: + return lang(lng_passport_front_side_description); + case SpecialFile::ReverseSide: + return lang(lng_passport_reverse_side_description); + case SpecialFile::Selfie: + return lang(lng_passport_selfie_description); + } + Unexpected("Type in special row upload key."); + }; + + const auto inner = _content.data(); + inner->move(0, 0); + for (auto &[type, info] : files) { + const auto i = _specialScans.emplace( + type, + SpecialScan(std::move(info))).first; + auto &scan = i->second; + + scan.header = inner->add( object_ptr>( inner, object_ptr( inner, - lang(lng_passport_selfie_title), + title(type), Ui::FlatLabel::InitType::Simple, st::passportFormHeader), st::passportUploadHeaderPadding)); - _selfieHeader->toggle(_selfie->key.id != 0, anim::type::instant); - _selfieWrap = inner->add(object_ptr(inner)); - if (_selfie->key.id) { - createSelfieRow(*_selfie); + scan.header->toggle(scan.file.key.id != 0, anim::type::instant); + scan.wrap = inner->add(object_ptr(inner)); + if (scan.file.key.id) { + createSpecialScanRow(scan, scan.file); } - _selfieUpload = inner->add( + scan.upload = inner->add( object_ptr( inner, Lang::Viewer( - lng_passport_upload_selfie + uploadKey(type) ) | Info::Profile::ToUpperValue(), st::passportUploadButton), st::passportUploadButtonPadding); - _selfieUpload->addClickHandler([=] { - chooseSelfie(); + scan.upload->addClickHandler([=] { + chooseSpecialScan(type); }); inner->add(object_ptr( inner, object_ptr( _content, - lang(lng_passport_selfie_description), + description(type), Ui::FlatLabel::InitType::Simple, st::passportFormLabel), st::passportFormLabelPadding)); } + init(); +} + +void EditScans::init() { _controller->scanUpdated( ) | rpl::start_with_next([=](ScanInfo &&info) { updateScan(std::move(info)); @@ -410,8 +488,8 @@ void EditScans::setupContent(const QString &header) { } void EditScans::updateScan(ScanInfo &&info) { - if (info.selfie) { - updateSelfie(std::move(info)); + if (info.special) { + updateSpecialScan(*info.special, std::move(info)); return; } const auto i = ranges::find(_files, info.key, [](const ScanInfo &file) { @@ -438,24 +516,26 @@ void EditScans::updateScan(ScanInfo &&info) { } } -void EditScans::updateSelfie(ScanInfo &&info) { +void EditScans::updateSpecialScan(SpecialFile type, ScanInfo &&info) { Expects(info.key.id != 0); - if (!_selfie) { + const auto i = _specialScans.find(type); + if (i == end(_specialScans)) { return; } - if (_selfie->key.id) { - updateFileRow(_selfieRow->entity(), info); + auto &scan = i->second; + if (scan.file.key.id) { + updateFileRow(scan.row->entity(), info); if (!info.deleted) { - hideSelfieError(); + hideSpecialScanError(type); } } else { - createSelfieRow(info); - _selfieWrap->resizeToWidth(width()); - _selfieRow->show(anim::type::normal); - _selfieHeader->show(anim::type::normal); + createSpecialScanRow(scan, info); + scan.wrap->resizeToWidth(width()); + scan.row->show(anim::type::normal); + scan.header->show(anim::type::normal); } - *_selfie = std::move(info); + scan.file = std::move(info); } void EditScans::updateFileRow( @@ -468,24 +548,37 @@ void EditScans::updateFileRow( }; -void EditScans::createSelfieRow(const ScanInfo &info) { - _selfieRow = createScan( - _selfieWrap, - info, - lang(lng_passport_selfie_name)); - const auto row = _selfieRow->entity(); +void EditScans::createSpecialScanRow( + SpecialScan &scan, + const ScanInfo &info) { + Expects(scan.file.special.has_value()); + + const auto type = *scan.file.special; + const auto name = [&] { + switch (type) { + case SpecialFile::FrontSide: + return lang(lng_passport_front_side_name); + case SpecialFile::ReverseSide: + return lang(lng_passport_reverse_side_name); + case SpecialFile::Selfie: + return lang(lng_passport_selfie_name); + } + Unexpected("Type in special file name."); + }(); + scan.row = createScan(scan.wrap, info, name); + const auto row = scan.row->entity(); row->deleteClicks( ) | rpl::start_with_next([=] { - _controller->deleteSelfie(); + _controller->deleteSpecialScan(type); }, row->lifetime()); row->restoreClicks( ) | rpl::start_with_next([=] { - _controller->restoreSelfie(); + _controller->restoreSpecialScan(type); }, row->lifetime()); - hideSelfieError(); + hideSpecialScanError(type); } void EditScans::pushScan(const ScanInfo &info) { @@ -541,9 +634,9 @@ void EditScans::chooseScan() { }); } -void EditScans::chooseSelfie() { +void EditScans::chooseSpecialScan(SpecialFile type) { ChooseScan(this, [=](QByteArray &&content) { - _controller->uploadSelfie(std::move(content)); + _controller->uploadSpecialScan(type, std::move(content)); }, [=](ReadScanError error) { _controller->readScanError(error); }); @@ -643,32 +736,42 @@ void EditScans::errorAnimationCallback() { } } -void EditScans::hideSelfieError() { - toggleSelfieError(false); +void EditScans::hideSpecialScanError(SpecialFile type) { + toggleSpecialScanError(type, false); } -void EditScans::toggleSelfieError(bool shown) { - if (_selfieErrorShown != shown) { - _selfieErrorShown = shown; - _selfieErrorAnimation.start( - [=] { selfieErrorAnimationCallback(); }, - _selfieErrorShown ? 0. : 1., - _selfieErrorShown ? 1. : 0., +auto EditScans::findSpecialScan(SpecialFile type) -> SpecialScan& { + const auto i = _specialScans.find(type); + Assert(i != end(_specialScans)); + return i->second; +} + +void EditScans::toggleSpecialScanError(SpecialFile type, bool shown) { + auto &scan = findSpecialScan(type); + if (scan.errorShown != shown) { + scan.errorShown = shown; + scan.errorAnimation.start( + [=] { specialScanErrorAnimationCallback(type); }, + scan.errorShown ? 0. : 1., + scan.errorShown ? 1. : 0., st::passportDetailsField.duration); } } -void EditScans::selfieErrorAnimationCallback() { - const auto error = _selfieErrorAnimation.current( - _selfieErrorShown ? 1. : 0.); +void EditScans::specialScanErrorAnimationCallback(SpecialFile type) { + auto &scan = findSpecialScan(type); + const auto error = scan.errorAnimation.current( + scan.errorShown ? 1. : 0.); if (error == 0.) { - _selfieUpload->setColorOverride(base::none); + scan.upload->setColorOverride(base::none); } else { - _selfieUpload->setColorOverride(anim::color( + scan.upload->setColorOverride(anim::color( st::passportUploadButton.textFg, st::boxTextFgError, error)); } } +EditScans::~EditScans() = default; + } // namespace Passport diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_scans.h b/Telegram/SourceFiles/passport/passport_panel_edit_scans.h index 375c42785..e50df6aa5 100644 --- a/Telegram/SourceFiles/passport/passport_panel_edit_scans.h +++ b/Telegram/SourceFiles/passport/passport_panel_edit_scans.h @@ -26,6 +26,7 @@ class Button; namespace Passport { +enum class SpecialFile; class PanelController; class ScanButton; struct ScanInfo; @@ -44,8 +45,11 @@ public: not_null controller, const QString &header, const QString &errorMissing, - std::vector &&files, - std::unique_ptr &&selfie); + std::vector &&files); + EditScans( + QWidget *parent, + not_null controller, + std::map &&specialFiles); base::optional validateGetErrorTop(); @@ -54,21 +58,31 @@ public: base::lambda doneCallback, base::lambda errorCallback); + ~EditScans(); + private: - void setupContent(const QString &header); + struct SpecialScan; + + void setupScans(const QString &header); + void setupSpecialScans(std::map &&files); + void init(); + void chooseScan(); - void chooseSelfie(); + void chooseSpecialScan(SpecialFile type); void updateScan(ScanInfo &&info); - void updateSelfie(ScanInfo &&info); + void updateSpecialScan(SpecialFile type, ScanInfo &&info); void updateFileRow( not_null button, const ScanInfo &info); void pushScan(const ScanInfo &info); - void createSelfieRow(const ScanInfo &info); + void createSpecialScanRow( + SpecialScan &scan, + const ScanInfo &info); base::unique_qptr> createScan( not_null parent, const ScanInfo &info, const QString &name); + SpecialScan &findSpecialScan(SpecialFile type); rpl::producer uploadButtonText() const; @@ -77,13 +91,12 @@ private: void errorAnimationCallback(); bool uploadedSomeMore() const; - void toggleSelfieError(bool shown); - void hideSelfieError(); - void selfieErrorAnimationCallback(); + void toggleSpecialScanError(SpecialFile type, bool shown); + void hideSpecialScanError(SpecialFile type); + void specialScanErrorAnimationCallback(SpecialFile type); not_null _controller; std::vector _files; - std::unique_ptr _selfie; int _initialCount = 0; QString _errorMissing; @@ -98,12 +111,7 @@ private: bool _errorShown = false; Animation _errorAnimation; - QPointer> _selfieHeader; - QPointer _selfieWrap; - base::unique_qptr> _selfieRow; - QPointer _selfieUpload; - bool _selfieErrorShown = false; - Animation _selfieErrorAnimation; + std::map _specialScans; };