Update scheme, special scans for identity type.

This commit is contained in:
John Preston 2018-05-12 01:55:56 +03:00
parent 72b29dd90d
commit 6aecb81c23
13 changed files with 655 additions and 281 deletions

View File

@ -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_card_upload" = "Upload a scan of your identity card";
"lng_passport_identity_license" = "Driver's license"; "lng_passport_identity_license" = "Driver's license";
"lng_passport_identity_license_upload" = "Upload a scan of your 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_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_title" = "Residential address";
"lng_passport_address_description" = "Upload a proof of your 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_statement_upload" = "Upload a scan of your bank statement";
"lng_passport_address_agreement" = "Tenancy agreement"; "lng_passport_address_agreement" = "Tenancy agreement";
"lng_passport_address_agreement_upload" = "Upload a scan of your 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_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_document_type" = "Please choose the type of your document:";
"lng_passport_upload_document" = "Upload 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_upload_more" = "Upload additional scans";
"lng_passport_selfie_title" = "Selfie"; "lng_passport_selfie_title" = "Selfie";
"lng_passport_selfie_name" = "Photo"; "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_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" = "Personal details";
"lng_passport_personal_details_enter" = "Enter your personal details"; "lng_passport_personal_details_enter" = "Enter your personal details";
"lng_passport_choose_image" = "Choose scan image"; "lng_passport_choose_image" = "Choose scan image";

View File

@ -982,23 +982,28 @@ secureValueTypePersonalDetails#9d2a81e3 = SecureValueType;
secureValueTypePassport#3dac6a00 = SecureValueType; secureValueTypePassport#3dac6a00 = SecureValueType;
secureValueTypeDriverLicense#6e425c4 = SecureValueType; secureValueTypeDriverLicense#6e425c4 = SecureValueType;
secureValueTypeIdentityCard#a0d0744b = SecureValueType; secureValueTypeIdentityCard#a0d0744b = SecureValueType;
secureValueTypeInternalPassport#99a48f23 = SecureValueType;
secureValueTypeAddress#cbe31e26 = SecureValueType; secureValueTypeAddress#cbe31e26 = SecureValueType;
secureValueTypeUtilityBill#fc36954e = SecureValueType; secureValueTypeUtilityBill#fc36954e = SecureValueType;
secureValueTypeBankStatement#89137c0d = SecureValueType; secureValueTypeBankStatement#89137c0d = SecureValueType;
secureValueTypeRentalAgreement#8b883488 = SecureValueType; secureValueTypeRentalAgreement#8b883488 = SecureValueType;
secureValueTypePassportRegistration#99e3806a = SecureValueType;
secureValueTypeTemporaryRegistration#ea02ec33 = SecureValueType;
secureValueTypePhone#b320aadb = SecureValueType; secureValueTypePhone#b320aadb = SecureValueType;
secureValueTypeEmail#8e3ca7ee = SecureValueType; secureValueTypeEmail#8e3ca7ee = SecureValueType;
secureValue#ec4134c8 flags:# type:SecureValueType data:flags.0?SecureData files:flags.1?Vector<SecureFile> 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<SecureFile> plain_data:flags.5?SecurePlainData hash:bytes = SecureValue;
inputSecureValue#c0da30f0 flags:# type:SecureValueType data:flags.0?SecureData files:flags.1?Vector<InputSecureFile> 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<InputSecureFile> plain_data:flags.5?SecurePlainData = InputSecureValue;
secureValueHash#ed1ecdb0 type:SecureValueType hash:bytes = SecureValueHash; secureValueHash#ed1ecdb0 type:SecureValueType hash:bytes = SecureValueHash;
secureValueErrorData#e8a40bd9 type:SecureValueType data_hash:bytes field:string text:string = SecureValueError; 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; secureValueErrorFile#7a700873 type:SecureValueType file_hash:bytes text:string = SecureValueError;
secureValueErrorFiles#666220e9 type:SecureValueType file_hash:Vector<bytes> text:string = SecureValueError; secureValueErrorFiles#666220e9 type:SecureValueType file_hash:Vector<bytes> text:string = SecureValueError;
secureValueErrorSelfie#e537ced6 type:SecureValueType file_hash:bytes text:string = SecureValueError;
secureCredentialsEncrypted#33f0ea47 data:bytes hash:bytes secret:bytes = SecureCredentialsEncrypted; secureCredentialsEncrypted#33f0ea47 data:bytes hash:bytes secret:bytes = SecureCredentialsEncrypted;
@ -1287,4 +1292,4 @@ langpack.getStrings#2e1ee318 lang_code:string keys:Vector<string> = Vector<LangP
langpack.getDifference#b2e4d7d from_version:int = LangPackDifference; langpack.getDifference#b2e4d7d from_version:int = LangPackDifference;
langpack.getLanguages#800fd57d = Vector<LangPackLanguage>; langpack.getLanguages#800fd57d = Vector<LangPackLanguage>;
// LAYER 80 // LAYER 81

View File

@ -55,7 +55,7 @@ addChildParentFlags('MTPDchannelForbidden', 'MTPDchannel');
parentFlagsCheck = {}; parentFlagsCheck = {};
countedTypeIdExceptions = {}; countedTypeIdExceptions = {};
for i in range(77,81): for i in range(77, 82):
countedTypeIdExceptions[i] = {} countedTypeIdExceptions[i] = {}
countedTypeIdExceptions[i]['channel'] = True countedTypeIdExceptions[i]['channel'] = True
countedTypeIdExceptions['ipPortSecret'] = True countedTypeIdExceptions['ipPortSecret'] = True

View File

@ -177,12 +177,15 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
case mtpc_secureValueTypePassport: case mtpc_secureValueTypePassport:
case mtpc_secureValueTypeDriverLicense: case mtpc_secureValueTypeDriverLicense:
case mtpc_secureValueTypeIdentityCard: case mtpc_secureValueTypeIdentityCard:
case mtpc_secureValueTypeInternalPassport:
return lang(lng_action_secure_proof_of_identity); return lang(lng_action_secure_proof_of_identity);
case mtpc_secureValueTypeAddress: case mtpc_secureValueTypeAddress:
return lang(lng_action_secure_address); return lang(lng_action_secure_address);
case mtpc_secureValueTypeUtilityBill: case mtpc_secureValueTypeUtilityBill:
case mtpc_secureValueTypeBankStatement: case mtpc_secureValueTypeBankStatement:
case mtpc_secureValueTypeRentalAgreement: case mtpc_secureValueTypeRentalAgreement:
case mtpc_secureValueTypePassportRegistration:
case mtpc_secureValueTypeTemporaryRegistration:
return lang(lng_action_secure_proof_of_address); return lang(lng_action_secure_proof_of_address);
case mtpc_secureValueTypePhone: case mtpc_secureValueTypePhone:
return lang(lng_action_secure_phone); return lang(lng_action_secure_phone);

View File

@ -69,41 +69,64 @@ QImage ReadImage(bytes::const_span buffer) {
Value::Type ConvertType(const MTPSecureValueType &type) { Value::Type ConvertType(const MTPSecureValueType &type) {
using Type = Value::Type; using Type = Value::Type;
switch (type.type()) { switch (type.type()) {
case mtpc_secureValueTypePersonalDetails: return Type::PersonalDetails; case mtpc_secureValueTypePersonalDetails:
case mtpc_secureValueTypePassport: return Type::Passport; return Type::PersonalDetails;
case mtpc_secureValueTypeDriverLicense: return Type::DriverLicense; case mtpc_secureValueTypePassport:
case mtpc_secureValueTypeIdentityCard: return Type::IdentityCard; return Type::Passport;
case mtpc_secureValueTypeAddress: return Type::Address; case mtpc_secureValueTypeDriverLicense:
case mtpc_secureValueTypeUtilityBill: return Type::UtilityBill; return Type::DriverLicense;
case mtpc_secureValueTypeBankStatement: return Type::BankStatement; case mtpc_secureValueTypeIdentityCard:
case mtpc_secureValueTypeRentalAgreement: return Type::RentalAgreement; return Type::IdentityCard;
case mtpc_secureValueTypePhone: return Type::Phone; case mtpc_secureValueTypeInternalPassport:
case mtpc_secureValueTypeEmail: return Type::Email; 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."); Unexpected("Type in secureValueType type.");
}; };
MTPSecureValueType ConvertType(Value::Type type) { MTPSecureValueType ConvertType(Value::Type type) {
using Type = Value::Type;
switch (type) { switch (type) {
case Value::Type::PersonalDetails: case Type::PersonalDetails:
return MTP_secureValueTypePersonalDetails(); return MTP_secureValueTypePersonalDetails();
case Value::Type::Passport: case Type::Passport:
return MTP_secureValueTypePassport(); return MTP_secureValueTypePassport();
case Value::Type::DriverLicense: case Type::DriverLicense:
return MTP_secureValueTypeDriverLicense(); return MTP_secureValueTypeDriverLicense();
case Value::Type::IdentityCard: case Type::IdentityCard:
return MTP_secureValueTypeIdentityCard(); return MTP_secureValueTypeIdentityCard();
case Value::Type::Address: case Type::InternalPassport:
return MTP_secureValueTypeInternalPassport();
case Type::Address:
return MTP_secureValueTypeAddress(); return MTP_secureValueTypeAddress();
case Value::Type::UtilityBill: case Type::UtilityBill:
return MTP_secureValueTypeUtilityBill(); return MTP_secureValueTypeUtilityBill();
case Value::Type::BankStatement: case Type::BankStatement:
return MTP_secureValueTypeBankStatement(); return MTP_secureValueTypeBankStatement();
case Value::Type::RentalAgreement: case Type::RentalAgreement:
return MTP_secureValueTypeRentalAgreement(); 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(); return MTP_secureValueTypePhone();
case Value::Type::Email: case Type::Email:
return MTP_secureValueTypeEmail(); return MTP_secureValueTypeEmail();
} }
Unexpected("Type in FormController::submit."); Unexpected("Type in FormController::submit.");
@ -135,21 +158,34 @@ FormRequest PreprocessRequest(const FormRequest &request) {
} }
QString ValueCredentialsKey(Value::Type type) { QString ValueCredentialsKey(Value::Type type) {
using Type = Value::Type;
switch (type) { switch (type) {
case Value::Type::PersonalDetails: return "personal_details"; case Type::PersonalDetails: return "personal_details";
case Value::Type::Passport: return "passport"; case Type::Passport: return "passport";
case Value::Type::DriverLicense: return "driver_license"; case Type::DriverLicense: return "driver_license";
case Value::Type::IdentityCard: return "identity_card"; case Type::IdentityCard: return "identity_card";
case Value::Type::Address: return "address"; case Type::InternalPassport: return "internal_passport";
case Value::Type::UtilityBill: return "utility_bill"; case Type::Address: return "address";
case Value::Type::BankStatement: return "bank_statement"; case Type::UtilityBill: return "utility_bill";
case Value::Type::RentalAgreement: return "rental_agreement"; case Type::BankStatement: return "bank_statement";
case Value::Type::Phone: case Type::RentalAgreement: return "rental_agreement";
case Value::Type::Email: return QString(); case Type::PassportRegistration: return "passport_registration";
case Type::TemporaryRegistration: return "temporary_registration";
case Type::Phone:
case Type::Email: return QString();
} }
Unexpected("Type in ValueCredentialsKey."); 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 } // namespace
FormRequest::FormRequest( FormRequest::FormRequest(
@ -215,6 +251,43 @@ UploadScanData *UploadScanDataPointer::operator->() const {
Value::Value(Type type) : type(type) { 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( FormController::FormController(
not_null<Window::Controller*> controller, not_null<Window::Controller*> controller,
const FormRequest &request) const FormRequest &request)
@ -266,8 +339,13 @@ auto FormController::prepareFinalData() -> FinalData {
} }
object.insert("files", files); object.insert("files", files);
} }
if (_form.identitySelfieRequired && value->selfie) { for (const auto &[type, scan] : value->specialScans) {
object.insert("selfie", GetJSONFromFile(*value->selfie)); const auto selfieRequired = _form.identitySelfieRequired;
if (value->requiresSpecialScan(type, selfieRequired)) {
object.insert(
SpecialScanCredentialsKey(type),
GetJSONFromFile(scan));
}
} }
secureData.insert(key, object); secureData.insert(key, object);
}; };
@ -290,7 +368,7 @@ auto FormController::prepareFinalData() -> FinalData {
addValue(scope.fields); addValue(scope.fields);
if (!scope.documents.empty()) { if (!scope.documents.empty()) {
for (const auto &document : scope.documents) { for (const auto &document : scope.documents) {
if (!document->scans.empty()) { if (document->scansAreFilled(scope.selfieRequired)) {
addValue(document); addValue(document);
break; break;
} }
@ -549,6 +627,18 @@ void FormController::fillErrors() {
LOG(("API Error: File not found for error value.")); LOG(("API Error: File not found for error value."));
return nullptr; 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) { for (const auto &error : _form.pendingErrors) {
switch (error.type()) { switch (error.type()) {
case mtpc_secureValueErrorData: { case mtpc_secureValueErrorData: {
@ -573,16 +663,19 @@ void FormController::fillErrors() {
value->scanMissingError = qs(data.vtext); value->scanMissingError = qs(data.vtext);
} }
} break; } 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: { case mtpc_secureValueErrorSelfie: {
const auto &data = error.c_secureValueErrorSelfie(); const auto &data = error.c_secureValueErrorSelfie();
if (const auto value = find(data.vtype)) { setSpecialScanError(SpecialFile::Selfie, data);
if (value->selfie) {
value->selfie->error = qs(data.vtext);
} else {
LOG(("API Error: Selfie not found for error value."));
}
}
} break; } break;
default: Unexpected("Error type in FormController::fillErrors.");
} }
} }
} }
@ -639,8 +732,10 @@ bool FormController::validateValueSecrets(Value &value) {
return false; return false;
} }
} }
if (value.selfie && !validateFileSecret(*value.selfie)) { for (auto &[type, file] : value.specialScans) {
return false; if (!validateFileSecret(file)) {
return false;
}
} }
return true; return true;
} }
@ -692,25 +787,40 @@ void FormController::restoreScan(
scanDeleteRestore(value, scanIndex, false); scanDeleteRestore(value, scanIndex, false);
} }
void FormController::uploadSelfie( void FormController::uploadSpecialScan(
not_null<const Value*> value, not_null<const Value*> value,
SpecialFile type,
QByteArray &&content) { QByteArray &&content) {
const auto nonconst = findValue(value); const auto nonconst = findValue(value);
nonconst->selfieInEdit = EditFile{ nonconst, File(), nullptr }; auto scanInEdit = EditFile{ nonconst, File(), nullptr };
auto &file = *nonconst->selfieInEdit; 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) { encryptFile(file, std::move(content), [=](UploadScanData &&result) {
const auto i = nonconst->specialScansInEdit.find(type);
Assert(i != nonconst->specialScansInEdit.end());
uploadEncryptedFile( uploadEncryptedFile(
*nonconst->selfieInEdit, i->second,
std::move(result)); std::move(result));
}); });
} }
void FormController::deleteSelfie(not_null<const Value*> value) { void FormController::deleteSpecialScan(
selfieDeleteRestore(value, true); not_null<const Value*> value,
SpecialFile type) {
specialScanDeleteRestore(value, type, true);
} }
void FormController::restoreSelfie(not_null<const Value*> value) { void FormController::restoreSpecialScan(
selfieDeleteRestore(value, false); not_null<const Value*> value,
SpecialFile type) {
specialScanDeleteRestore(value, type, false);
} }
void FormController::prepareFile( void FormController::prepareFile(
@ -779,13 +889,14 @@ void FormController::scanDeleteRestore(
_scanUpdated.fire(&scan); _scanUpdated.fire(&scan);
} }
void FormController::selfieDeleteRestore( void FormController::specialScanDeleteRestore(
not_null<const Value*> value, not_null<const Value*> value,
SpecialFile type,
bool deleted) { bool deleted) {
Expects(value->selfieInEdit.has_value());
const auto nonconst = findValue(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; scan.deleted = deleted;
_scanUpdated.fire(&scan); _scanUpdated.fire(&scan);
} }
@ -1006,8 +1117,11 @@ void FormController::startValueEdit(not_null<const Value*> value) {
for (auto &scan : nonconst->scans) { for (auto &scan : nonconst->scans) {
loadFile(scan); loadFile(scan);
} }
if (nonconst->selfie && _form.identitySelfieRequired) { for (auto &[type, scan] : nonconst->specialScans) {
loadFile(*nonconst->selfie); const auto selfieRequired = _form.identitySelfieRequired;
if (nonconst->requiresSpecialScan(type, selfieRequired)) {
loadFile(scan);
}
} }
nonconst->scansInEdit = ranges::view::all( nonconst->scansInEdit = ranges::view::all(
nonconst->scans nonconst->scans
@ -1015,13 +1129,12 @@ void FormController::startValueEdit(not_null<const Value*> value) {
return EditFile(nonconst, file, nullptr); return EditFile(nonconst, file, nullptr);
}) | ranges::to_vector; }) | ranges::to_vector;
if (nonconst->selfie) { nonconst->specialScansInEdit.clear();
nonconst->selfieInEdit = EditFile( for (const auto &[type, scan] : nonconst->specialScans) {
nonconst->specialScansInEdit.emplace(type, EditFile(
nonconst, nonconst,
*nonconst->selfie, scan,
nullptr); nullptr));
} else {
nonconst->selfieInEdit = base::none;
} }
nonconst->data.parsedInEdit = nonconst->data.parsed; nonconst->data.parsedInEdit = nonconst->data.parsed;
@ -1116,18 +1229,27 @@ bool FormController::savingValue(not_null<const Value*> value) const {
} }
bool FormController::uploadingScan(not_null<const Value*> value) const { bool FormController::uploadingScan(not_null<const Value*> 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) { for (const auto &scan : value->scansInEdit) {
if (scan.uploadData if (uploading(scan)) {
&& scan.uploadData->fullId
&& !scan.deleted) {
return true; return true;
} }
} }
if (value->selfieInEdit) { for (const auto &[type, scan] : value->specialScansInEdit) {
const auto &selfie = *value->selfieInEdit; if (uploading(scan)) {
if (selfie.uploadData
&& selfie.uploadData->fullId
&& !selfie.deleted) {
return true; return true;
} }
} }
@ -1155,7 +1277,7 @@ void FormController::clearValueEdit(not_null<Value*> value) {
return; return;
} }
value->scansInEdit.clear(); value->scansInEdit.clear();
value->selfieInEdit = base::none; value->specialScansInEdit.clear();
value->data.encryptedSecretInEdit.clear(); value->data.encryptedSecretInEdit.clear();
value->data.hashInEdit.clear(); value->data.hashInEdit.clear();
value->data.parsedInEdit = ValueMap(); value->data.parsedInEdit = ValueMap();
@ -1200,8 +1322,10 @@ bool FormController::editValueChanged(
return true; return true;
} }
} }
if (value->selfieInEdit && editFileChanged(*value->selfieInEdit)) { for (const auto &[type, scan] : value->specialScansInEdit) {
return true; if (editFileChanged(scan)) {
return true;
}
} }
auto existing = value->data.parsed.fields; auto existing = value->data.parsed.fields;
for (const auto &[key, value] : data.fields) { for (const auto &[key, value] : data.fields) {
@ -1230,7 +1354,7 @@ void FormController::saveValueEdit(
nonconst->saveRequestId = -1; nonconst->saveRequestId = -1;
crl::on_main(this, [=] { crl::on_main(this, [=] {
base::take(nonconst->scansInEdit); base::take(nonconst->scansInEdit);
base::take(nonconst->selfieInEdit); base::take(nonconst->specialScansInEdit);
base::take(nonconst->data.encryptedSecretInEdit); base::take(nonconst->data.encryptedSecretInEdit);
base::take(nonconst->data.hashInEdit); base::take(nonconst->data.hashInEdit);
base::take(nonconst->data.parsedInEdit); base::take(nonconst->data.parsedInEdit);
@ -1313,41 +1437,36 @@ void FormController::saveEncryptedValue(not_null<Value*> value) {
_secret, _secret,
value->data.hashInEdit); value->data.hashInEdit);
const auto selfie = (value->selfieInEdit const auto hasSpecialFile = [&](SpecialFile type) {
&& !value->selfieInEdit->deleted) const auto i = value->specialScansInEdit.find(type);
? inputFile(*value->selfieInEdit) return (i != end(value->specialScansInEdit) && !i->second.deleted);
: MTPInputSecureFile(); };
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 = [&] { const auto type = ConvertType(value->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 flags = (value->data.parsedInEdit.fields.empty() const auto flags = (value->data.parsedInEdit.fields.empty()
? MTPDinputSecureValue::Flag(0) ? MTPDinputSecureValue::Flag(0)
: MTPDinputSecureValue::Flag::f_data) : 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() | (value->scansInEdit.empty()
? MTPDinputSecureValue::Flag(0) ? MTPDinputSecureValue::Flag(0)
: MTPDinputSecureValue::Flag::f_files) : MTPDinputSecureValue::Flag::f_files);
| ((value->selfieInEdit && !value->selfieInEdit->deleted)
? MTPDinputSecureValue::Flag::f_selfie
: MTPDinputSecureValue::Flag(0));
Assert(flags != MTPDinputSecureValue::Flags(0)); Assert(flags != MTPDinputSecureValue::Flags(0));
sendSaveRequest(value, MTP_inputSecureValue( sendSaveRequest(value, MTP_inputSecureValue(
@ -1357,9 +1476,11 @@ void FormController::saveEncryptedValue(not_null<Value*> value) {
MTP_bytes(encryptedData.bytes), MTP_bytes(encryptedData.bytes),
MTP_bytes(value->data.hashInEdit), MTP_bytes(value->data.hashInEdit),
MTP_bytes(value->data.encryptedSecretInEdit)), MTP_bytes(value->data.encryptedSecretInEdit)),
frontSide,
reverseSide,
selfie,
MTP_vector<MTPInputSecureFile>(inputFiles), MTP_vector<MTPInputSecureFile>(inputFiles),
MTPSecurePlainData(), MTPSecurePlainData()));
selfie));
} }
void FormController::savePlainTextValue(not_null<Value*> value) { void FormController::savePlainTextValue(not_null<Value*> value) {
@ -1384,9 +1505,11 @@ void FormController::savePlainTextValue(not_null<Value*> value) {
MTP_flags(MTPDinputSecureValue::Flag::f_plain_data), MTP_flags(MTPDinputSecureValue::Flag::f_plain_data),
type, type,
MTPSecureData(), MTPSecureData(),
MTPInputSecureFile(),
MTPInputSecureFile(),
MTPInputSecureFile(),
MTPVector<MTPInputSecureFile>(), MTPVector<MTPInputSecureFile>(),
plain(MTP_string(text)), plain(MTP_string(text))));
MTPInputSecureFile()));
} }
void FormController::sendSaveRequest( void FormController::sendSaveRequest(
@ -1398,13 +1521,13 @@ void FormController::sendSaveRequest(
data, data,
MTP_long(_secretId) MTP_long(_secretId)
)).done([=](const MTPSecureValue &result) { )).done([=](const MTPSecureValue &result) {
auto filesInEdit = base::take(value->scansInEdit); auto scansInEdit = base::take(value->scansInEdit);
if (auto selfie = base::take(value->selfieInEdit)) { for (auto &[type, scan] : base::take(value->specialScansInEdit)) {
filesInEdit.push_back(std::move(*selfie)); scansInEdit.push_back(std::move(scan));
} }
const auto editScreens = value->editScreens; const auto editScreens = value->editScreens;
*value = parseValue(result, filesInEdit); *value = parseValue(result, scansInEdit);
decryptValue(*value); decryptValue(*value);
value->editScreens = editScreens; value->editScreens = editScreens;
@ -1737,8 +1860,21 @@ auto FormController::parseValue(
if (data.has_files()) { if (data.has_files()) {
result.scans = parseFiles(data.vfiles.v, editData); 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()) { if (data.has_selfie()) {
result.selfie = parseFile(data.vselfie, editData); parseSpecialScan(SpecialFile::Selfie, data.vselfie);
} }
if (data.has_plain_data()) { if (data.has_plain_data()) {
switch (data.vplain_data.type()) { switch (data.vplain_data.type()) {
@ -1765,8 +1901,10 @@ auto FormController::findEditFile(const FullMsgId &fullId) -> EditFile* {
return &scan; return &scan;
} }
} }
if (value.selfieInEdit && found(*value.selfieInEdit)) { for (auto &[special, scan] : value.specialScansInEdit) {
return &*value.selfieInEdit; if (found(scan)) {
return &scan;
}
} }
} }
return nullptr; return nullptr;
@ -1782,8 +1920,10 @@ auto FormController::findEditFile(const FileKey &key) -> EditFile* {
return &scan; return &scan;
} }
} }
if (value.selfieInEdit && found(*value.selfieInEdit)) { for (auto &[special, scan] : value.specialScansInEdit) {
return &*value.selfieInEdit; if (found(scan)) {
return &scan;
}
} }
} }
return nullptr; return nullptr;
@ -1800,8 +1940,10 @@ auto FormController::findFile(const FileKey &key)
return { &value, &scan }; return { &value, &scan };
} }
} }
if (value.selfie && found(*value.selfie)) { for (auto &[special, scan] : value.specialScans) {
return { &value, &*value.selfie }; if (found(scan)) {
return { &value, &scan };
}
} }
} }
return { nullptr, nullptr }; return { nullptr, nullptr };

View File

@ -132,16 +132,27 @@ struct Verification {
}; };
struct Form;
enum class SpecialFile {
FrontSide,
ReverseSide,
Selfie,
};
struct Value { struct Value {
enum class Type { enum class Type {
PersonalDetails, PersonalDetails,
Passport, Passport,
DriverLicense, DriverLicense,
IdentityCard, IdentityCard,
InternalPassport,
Address, Address,
UtilityBill, UtilityBill,
BankStatement, BankStatement,
RentalAgreement, RentalAgreement,
PassportRegistration,
TemporaryRegistration,
Phone, Phone,
Email, Email,
}; };
@ -150,13 +161,16 @@ struct Value {
Value(Value &&other) = default; Value(Value &&other) = default;
Value &operator=(Value &&other) = default; Value &operator=(Value &&other) = default;
bool requiresSpecialScan(SpecialFile type, bool selfieRequired) const;
bool scansAreFilled(bool selfieRequired) const;
Type type; Type type;
ValueData data; ValueData data;
std::vector<File> scans; std::vector<File> scans;
std::vector<EditFile> scansInEdit; std::map<SpecialFile, File> specialScans;
QString scanMissingError; QString scanMissingError;
base::optional<File> selfie; std::vector<EditFile> scansInEdit;
base::optional<EditFile> selfieInEdit; std::map<SpecialFile, EditFile> specialScansInEdit;
Verification verification; Verification verification;
bytes::vector submitHash; bytes::vector submitHash;
@ -244,9 +258,16 @@ public:
void uploadScan(not_null<const Value*> value, QByteArray &&content); void uploadScan(not_null<const Value*> value, QByteArray &&content);
void deleteScan(not_null<const Value*> value, int fileIndex); void deleteScan(not_null<const Value*> value, int fileIndex);
void restoreScan(not_null<const Value*> value, int fileIndex); void restoreScan(not_null<const Value*> value, int fileIndex);
void uploadSelfie(not_null<const Value*> value, QByteArray &&content); void uploadSpecialScan(
void deleteSelfie(not_null<const Value*> value); not_null<const Value*> value,
void restoreSelfie(not_null<const Value*> value); SpecialFile type,
QByteArray &&content);
void deleteSpecialScan(
not_null<const Value*> value,
SpecialFile type);
void restoreSpecialScan(
not_null<const Value*> value,
SpecialFile type);
rpl::producer<> secretReadyEvents() const; rpl::producer<> secretReadyEvents() const;
@ -349,8 +370,9 @@ private:
not_null<const Value*> value, not_null<const Value*> value,
int fileIndex, int fileIndex,
bool deleted); bool deleted);
void selfieDeleteRestore( void specialScanDeleteRestore(
not_null<const Value*> value, not_null<const Value*> value,
SpecialFile type,
bool deleted); bool deleted);
QString getPhoneFromValue(not_null<const Value*> value) const; QString getPhoneFromValue(not_null<const Value*> value) const;

View File

@ -22,10 +22,13 @@ std::map<Value::Type, Scope::Type> ScopeTypesMap() {
{ Value::Type::Passport, Scope::Type::Identity }, { Value::Type::Passport, Scope::Type::Identity },
{ Value::Type::DriverLicense, Scope::Type::Identity }, { Value::Type::DriverLicense, Scope::Type::Identity },
{ Value::Type::IdentityCard, 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::Address },
{ Value::Type::UtilityBill, Scope::Type::Address }, { Value::Type::UtilityBill, Scope::Type::Address },
{ Value::Type::BankStatement, Scope::Type::Address }, { Value::Type::BankStatement, Scope::Type::Address },
{ Value::Type::RentalAgreement, 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::Phone, Scope::Type::Phone },
{ Value::Type::Email, Scope::Type::Email }, { 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 &fields = scope.fields->data.parsed.fields;
const auto document = [&]() -> const Value* { const auto document = [&]() -> const Value* {
for (const auto &document : scope.documents) { for (const auto &document : scope.documents) {
if (!document->scans.empty()) { if (document->scansAreFilled(scope.selfieRequired)) {
return document; return document;
} }
} }
@ -119,25 +122,31 @@ QString ComputeScopeRowReadyString(const Scope &scope) {
}(); }();
if (document && scope.documents.size() > 1) { if (document && scope.documents.size() > 1) {
pushListValue([&] { pushListValue([&] {
using Type = Value::Type;
switch (document->type) { switch (document->type) {
case Value::Type::Passport: case Type::Passport:
return lang(lng_passport_identity_passport); return lang(lng_passport_identity_passport);
case Value::Type::DriverLicense: case Type::DriverLicense:
return lang(lng_passport_identity_license); return lang(lng_passport_identity_license);
case Value::Type::IdentityCard: case Type::IdentityCard:
return lang(lng_passport_identity_card); 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); return lang(lng_passport_address_statement);
case Value::Type::UtilityBill: case Type::UtilityBill:
return lang(lng_passport_address_bill); return lang(lng_passport_address_bill);
case Value::Type::RentalAgreement: case Type::RentalAgreement:
return lang(lng_passport_address_agreement); 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."); default: Unexpected("Files type in ComputeScopeRowReadyString.");
} }
}()); }());
} }
if (!scope.documents.empty() if (!scope.documents.empty() && !document) {
&& (!document || (scope.selfieRequired && !document->selfie))) {
return QString(); return QString();
} }
const auto scheme = GetDocumentScheme(scope.type); const auto scheme = GetDocumentScheme(scope.type);
@ -155,8 +164,6 @@ QString ComputeScopeRowReadyString(const Scope &scope) {
pushListValue(format ? format(text) : text); pushListValue(format ? format(text) : text);
} else if (scope.documents.empty()) { } else if (scope.documents.empty()) {
continue; continue;
} else if (!document) {
return QString();
} else { } else {
const auto i = document->data.parsed.fields.find(row.key); const auto i = document->data.parsed.fields.find(row.key);
if (i == end(document->data.parsed.fields)) { if (i == end(document->data.parsed.fields)) {
@ -195,8 +202,10 @@ ScopeRow ComputeScopeRow(const Scope &scope) {
errors.push_back(scan.error); errors.push_back(scan.error);
} }
} }
if (value->selfie && !value->selfie->error.isEmpty()) { for (const auto &[type, scan] : value->specialScans) {
errors.push_back(value->selfie->error); if (!scan.error.isEmpty()) {
errors.push_back(scan.error);
}
} }
if (!value->scanMissingError.isEmpty()) { if (!value->scanMissingError.isEmpty()) {
errors.push_back(value->scanMissingError); errors.push_back(value->scanMissingError);
@ -236,6 +245,11 @@ ScopeRow ComputeScopeRow(const Scope &scope) {
lang(lng_passport_identity_license), lang(lng_passport_identity_license),
lang(lng_passport_identity_license_upload), 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."); 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),
lang(lng_passport_address_agreement_upload), 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."); default: Unexpected("Address type in ComputeScopeRow.");
} }
} }

View File

@ -92,6 +92,9 @@ EditDocumentScheme GetDocumentScheme(
case Value::Type::IdentityCard: case Value::Type::IdentityCard:
result.scansHeader = lang(lng_passport_identity_card); result.scansHeader = lang(lng_passport_identity_card);
break; break;
case Value::Type::InternalPassport:
result.scansHeader = lang(lng_passport_identity_internal);
break;
default: default:
Unexpected("scansType in GetDocumentScheme:Identity."); Unexpected("scansType in GetDocumentScheme:Identity.");
} }
@ -174,6 +177,12 @@ EditDocumentScheme GetDocumentScheme(
case Value::Type::RentalAgreement: case Value::Type::RentalAgreement:
result.scansHeader = lang(lng_passport_address_agreement); result.scansHeader = lang(lng_passport_address_agreement);
break; 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: default:
Unexpected("scansType in GetDocumentScheme:Address."); Unexpected("scansType in GetDocumentScheme:Address.");
} }
@ -496,28 +505,30 @@ void PanelController::restoreScan(int fileIndex) {
_form->restoreScan(_editDocument, fileIndex); _form->restoreScan(_editDocument, fileIndex);
} }
void PanelController::uploadSelfie(QByteArray &&content) { void PanelController::uploadSpecialScan(
SpecialFile type,
QByteArray &&content) {
Expects(_editScope != nullptr); Expects(_editScope != nullptr);
Expects(_editDocument != nullptr); Expects(_editDocument != nullptr);
Expects(_editScope->selfieRequired); 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(_editScope != nullptr);
Expects(_editDocument != nullptr); Expects(_editDocument != nullptr);
Expects(_editScope->selfieRequired); Expects(_editScope->selfieRequired);
_form->deleteSelfie(_editDocument); _form->deleteSpecialScan(_editDocument, type);
} }
void PanelController::restoreSelfie() { void PanelController::restoreSpecialScan(SpecialFile type) {
Expects(_editScope != nullptr); Expects(_editScope != nullptr);
Expects(_editDocument != nullptr); Expects(_editDocument != nullptr);
Expects(_editScope->selfieRequired); Expects(_editScope->selfieRequired);
_form->restoreSelfie(_editDocument); _form->restoreSpecialScan(_editDocument, type);
} }
rpl::producer<ScanInfo> PanelController::scanUpdated() const { rpl::producer<ScanInfo> PanelController::scanUpdated() const {
@ -566,15 +577,23 @@ ScanInfo PanelController::collectScanInfo(const EditFile &file) const {
return formatDownloadText(0, file.fields.size); return formatDownloadText(0, file.fields.size);
} }
}(); }();
auto isSelfie = (file.value == _editDocument) const auto specialType = [&]() -> base::optional<SpecialFile> {
&& (_editDocument->selfieInEdit.has_value()) if (file.value != _editDocument) {
&& (&file == &*_editDocument->selfieInEdit); return base::none;
}
for (const auto &[type, scan] : _editDocument->specialScansInEdit) {
if (&file == &scan) {
return type;
}
}
return base::none;
}();
return { return {
FileKey{ file.fields.id, file.fields.dcId }, FileKey{ file.fields.id, file.fields.dcId },
!file.fields.error.isEmpty() ? file.fields.error : status, !file.fields.error.isEmpty() ? file.fields.error : status,
file.fields.image, file.fields.image,
file.deleted, file.deleted,
isSelfie, specialType,
file.fields.error }; file.fields.error };
} }
@ -593,8 +612,8 @@ std::vector<ScopeError> PanelController::collectErrors(
for (const auto &scan : value->scansInEdit) { for (const auto &scan : value->scansInEdit) {
addFileError(scan); addFileError(scan);
} }
if (value->selfieInEdit) { for (const auto &[type, scan] : value->specialScansInEdit) {
addFileError(*value->selfieInEdit); addFileError(scan);
} }
for (const auto &[key, value] : value->data.parsedInEdit.fields) { for (const auto &[key, value] : value->data.parsedInEdit.fields) {
if (!value.error.isEmpty()) { if (!value.error.isEmpty()) {
@ -635,7 +654,7 @@ bool PanelController::hasValueDocument() const {
} }
return !_editDocument->data.parsed.fields.empty() return !_editDocument->data.parsed.fields.empty()
|| !_editDocument->scans.empty() || !_editDocument->scans.empty()
|| _editDocument->selfie.has_value(); || !_editDocument->specialScans.empty();
} }
bool PanelController::hasValueFields() const { bool PanelController::hasValueFields() const {
@ -748,13 +767,15 @@ void PanelController::ensurePanelCreated() {
} }
} }
int PanelController::findNonEmptyIndex( int PanelController::findNonEmptyDocumentIndex(const Scope &scope) const {
const std::vector<not_null<const Value*>> &files) const { const auto &documents = scope.documents;
const auto i = ranges::find_if(files, [](not_null<const Value*> file) { const auto i = ranges::find_if(
return !file->scans.empty(); documents,
}); [&](not_null<const Value*> document) {
if (i != end(files)) { return document->scansAreFilled(scope.selfieRequired);
return (i - begin(files)); });
if (i != end(documents)) {
return (i - begin(documents));
} }
return -1; return -1;
} }
@ -764,14 +785,14 @@ void PanelController::editScope(int index) {
Expects(_panel != nullptr); Expects(_panel != nullptr);
Expects(index >= 0 && index < _scopes.size()); Expects(index >= 0 && index < _scopes.size());
if (_scopes[index].documents.empty()) { const auto &scope = _scopes[index];
if (scope.documents.empty()) {
editScope(index, -1); editScope(index, -1);
} else { } else {
const auto documentIndex = findNonEmptyIndex( const auto documentIndex = findNonEmptyDocumentIndex(scope);
_scopes[index].documents);
if (documentIndex >= 0) { if (documentIndex >= 0) {
editScope(index, documentIndex); editScope(index, documentIndex);
} else if (_scopes[index].documents.size() > 1) { } else if (scope.documents.size() > 1) {
requestScopeFilesType(index); requestScopeFilesType(index);
} else { } else {
editWithUpload(index, 0); editWithUpload(index, 0);
@ -802,6 +823,8 @@ void PanelController::requestScopeFilesType(int index) {
return lang(lng_passport_identity_card); return lang(lng_passport_identity_card);
case Value::Type::DriverLicense: case Value::Type::DriverLicense:
return lang(lng_passport_identity_license); return lang(lng_passport_identity_license);
case Value::Type::InternalPassport:
return lang(lng_passport_identity_internal);
default: default:
Unexpected("IdentityType in requestScopeFilesType"); Unexpected("IdentityType in requestScopeFilesType");
} }
@ -823,6 +846,10 @@ void PanelController::requestScopeFilesType(int index) {
return lang(lng_passport_address_statement); return lang(lng_passport_address_statement);
case Value::Type::RentalAgreement: case Value::Type::RentalAgreement:
return lang(lng_passport_address_agreement); 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: default:
Unexpected("AddressType in requestScopeFilesType"); Unexpected("AddressType in requestScopeFilesType");
} }
@ -842,7 +869,13 @@ void PanelController::editWithUpload(int index, int documentIndex) {
EditScans::ChooseScan(_panel.get(), [=](QByteArray &&content) { EditScans::ChooseScan(_panel.get(), [=](QByteArray &&content) {
base::take(_scopeDocumentTypeBox); base::take(_scopeDocumentTypeBox);
editScope(index, documentIndex); 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) {
readScanError(error); readScanError(error);
}); });
@ -897,9 +930,7 @@ void PanelController::editScope(int index, int documentIndex) {
_editDocument->data.parsedInEdit, _editDocument->data.parsedInEdit,
_editDocument->scanMissingError, _editDocument->scanMissingError,
valueFiles(*_editDocument), valueFiles(*_editDocument),
(_editScope->selfieRequired valueSpecialFiles(*_editDocument))
? valueSelfie(*_editDocument)
: nullptr))
: object_ptr<PanelEditDocument>( : object_ptr<PanelEditDocument>(
_panel.get(), _panel.get(),
this, this,
@ -1050,13 +1081,26 @@ std::vector<ScanInfo> PanelController::valueFiles(
return result; return result;
} }
std::unique_ptr<ScanInfo> PanelController::valueSelfie( std::map<SpecialFile, ScanInfo> PanelController::valueSpecialFiles(
const Value &value) const { const Value &value) const {
if (value.selfieInEdit) { auto result = std::map<SpecialFile, ScanInfo>();
return std::make_unique<ScanInfo>( const auto types = {
collectScanInfo(*value.selfieInEdit)); 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<ScanInfo>(); return result;
} }
void PanelController::cancelValueEdit() { void PanelController::cancelValueEdit() {

View File

@ -30,7 +30,7 @@ struct ScanInfo {
QString status; QString status;
QImage thumb; QImage thumb;
bool deleted = false; bool deleted = false;
bool selfie = false; base::optional<SpecialFile> special;
QString error; QString error;
}; };
@ -81,9 +81,9 @@ public:
void uploadScan(QByteArray &&content); void uploadScan(QByteArray &&content);
void deleteScan(int fileIndex); void deleteScan(int fileIndex);
void restoreScan(int fileIndex); void restoreScan(int fileIndex);
void uploadSelfie(QByteArray &&content); void uploadSpecialScan(SpecialFile type, QByteArray &&content);
void deleteSelfie(); void deleteSpecialScan(SpecialFile type);
void restoreSelfie(); void restoreSpecialScan(SpecialFile type);
rpl::producer<ScanInfo> scanUpdated() const; rpl::producer<ScanInfo> scanUpdated() const;
rpl::producer<ScopeError> saveErrors() const; rpl::producer<ScopeError> saveErrors() const;
void readScanError(ReadScanError error); void readScanError(ReadScanError error);
@ -133,12 +133,12 @@ private:
void editScope(int index, int documentIndex); void editScope(int index, int documentIndex);
void editWithUpload(int index, int documentIndex); void editWithUpload(int index, int documentIndex);
int findNonEmptyIndex( int findNonEmptyDocumentIndex(const Scope &scope) const;
const std::vector<not_null<const Value*>> &files) const;
void requestScopeFilesType(int index); void requestScopeFilesType(int index);
void cancelValueEdit(); void cancelValueEdit();
std::vector<ScanInfo> valueFiles(const Value &value) const; std::vector<ScanInfo> valueFiles(const Value &value) const;
std::unique_ptr<ScanInfo> valueSelfie(const Value &value) const; std::map<SpecialFile, ScanInfo> valueSpecialFiles(
const Value &value) const;
void processValueSaveFinished(not_null<const Value*> value); void processValueSaveFinished(not_null<const Value*> value);
void processVerificationNeeded(not_null<const Value*> value); void processVerificationNeeded(not_null<const Value*> value);

View File

@ -213,7 +213,7 @@ PanelEditDocument::PanelEditDocument(
const ValueMap &scanData, const ValueMap &scanData,
const QString &missingScansError, const QString &missingScansError,
std::vector<ScanInfo> &&files, std::vector<ScanInfo> &&files,
std::unique_ptr<ScanInfo> &&selfie) std::map<SpecialFile, ScanInfo> &&specialFiles)
: _controller(controller) : _controller(controller)
, _scheme(std::move(scheme)) , _scheme(std::move(scheme))
, _scroll(this, st::passportPanelScroll) , _scroll(this, st::passportPanelScroll)
@ -228,7 +228,7 @@ PanelEditDocument::PanelEditDocument(
&scanData, &scanData,
missingScansError, missingScansError,
std::move(files), std::move(files),
std::move(selfie)); std::move(specialFiles));
} }
PanelEditDocument::PanelEditDocument( PanelEditDocument::PanelEditDocument(
@ -245,7 +245,7 @@ PanelEditDocument::PanelEditDocument(
this, this,
langFactory(lng_passport_save_value), langFactory(lng_passport_save_value),
st::passportPanelSaveValue) { st::passportPanelSaveValue) {
setupControls(data, nullptr, QString(), {}, nullptr); setupControls(data, nullptr, QString(), {}, {});
} }
void PanelEditDocument::setupControls( void PanelEditDocument::setupControls(
@ -253,13 +253,13 @@ void PanelEditDocument::setupControls(
const ValueMap *scanData, const ValueMap *scanData,
const QString &missingScansError, const QString &missingScansError,
std::vector<ScanInfo> &&files, std::vector<ScanInfo> &&files,
std::unique_ptr<ScanInfo> &&selfie) { std::map<SpecialFile, ScanInfo> &&specialFiles) {
const auto inner = setupContent( const auto inner = setupContent(
data, data,
scanData, scanData,
missingScansError, missingScansError,
std::move(files), std::move(files),
std::move(selfie)); std::move(specialFiles));
using namespace rpl::mappers; using namespace rpl::mappers;
@ -277,7 +277,7 @@ not_null<Ui::RpWidget*> PanelEditDocument::setupContent(
const ValueMap *scanData, const ValueMap *scanData,
const QString &missingScansError, const QString &missingScansError,
std::vector<ScanInfo> &&files, std::vector<ScanInfo> &&files,
std::unique_ptr<ScanInfo> &&selfie) { std::map<SpecialFile, ScanInfo> &&specialFiles) {
const auto inner = _scroll->setOwnedWidget( const auto inner = _scroll->setOwnedWidget(
object_ptr<Ui::VerticalLayout>(this)); object_ptr<Ui::VerticalLayout>(this));
_scroll->widthValue( _scroll->widthValue(
@ -285,15 +285,23 @@ not_null<Ui::RpWidget*> PanelEditDocument::setupContent(
inner->resizeToWidth(width); inner->resizeToWidth(width);
}, inner->lifetime()); }, inner->lifetime());
if (scanData) { if (!specialFiles.empty()) {
_editScans = inner->add(
object_ptr<EditScans>(
inner,
_controller,
// _scheme.scansHeader,
// missingScansError,
// std::move(files),
std::move(specialFiles)));
} else if (scanData) {
_editScans = inner->add( _editScans = inner->add(
object_ptr<EditScans>( object_ptr<EditScans>(
inner, inner,
_controller, _controller,
_scheme.scansHeader, _scheme.scansHeader,
missingScansError, missingScansError,
std::move(files), std::move(files)));
std::move(selfie)));
} else { } else {
inner->add(object_ptr<BoxContentDivider>( inner->add(object_ptr<BoxContentDivider>(
inner, inner,

View File

@ -30,6 +30,7 @@ struct ValueMap;
struct ScanInfo; struct ScanInfo;
class EditScans; class EditScans;
class PanelDetailsRow; class PanelDetailsRow;
enum class SpecialFile;
enum class PanelDetailsType; enum class PanelDetailsType;
struct EditDocumentScheme { struct EditDocumentScheme {
@ -64,7 +65,7 @@ public:
const ValueMap &scanData, const ValueMap &scanData,
const QString &missingScansError, const QString &missingScansError,
std::vector<ScanInfo> &&files, std::vector<ScanInfo> &&files,
std::unique_ptr<ScanInfo> &&selfie); std::map<SpecialFile, ScanInfo> &&specialFiles);
PanelEditDocument( PanelEditDocument(
QWidget *parent, QWidget *parent,
not_null<PanelController*> controller, not_null<PanelController*> controller,
@ -84,13 +85,13 @@ private:
const ValueMap *scanData, const ValueMap *scanData,
const QString &missingScansError, const QString &missingScansError,
std::vector<ScanInfo> &&files, std::vector<ScanInfo> &&files,
std::unique_ptr<ScanInfo> &&selfie); std::map<SpecialFile, ScanInfo> &&specialFiles);
not_null<Ui::RpWidget*> setupContent( not_null<Ui::RpWidget*> setupContent(
const ValueMap &data, const ValueMap &data,
const ValueMap *scanData, const ValueMap *scanData,
const QString &missingScansError, const QString &missingScansError,
std::vector<ScanInfo> &&files, std::vector<ScanInfo> &&files,
std::unique_ptr<ScanInfo> &&selfie); std::map<SpecialFile, ScanInfo> &&specialFiles);
void updateControlsGeometry(); void updateControlsGeometry();
Result collect() const; Result collect() const;

View File

@ -106,6 +106,19 @@ private:
}; };
struct EditScans::SpecialScan {
SpecialScan(ScanInfo &&file);
ScanInfo file;
QPointer<Ui::SlideWrap<Ui::FlatLabel>> header;
QPointer<Ui::VerticalLayout> wrap;
base::unique_qptr<Ui::SlideWrap<ScanButton>> row;
QPointer<Info::Profile::Button> upload;
bool errorShown = false;
Animation errorAnimation;
};
ScanButton::ScanButton( ScanButton::ScanButton(
QWidget *parent, QWidget *parent,
const style::PassportScanRow &st, const style::PassportScanRow &st,
@ -235,21 +248,34 @@ void ScanButton::paintEvent(QPaintEvent *e) {
width()); width());
} }
EditScans::SpecialScan::SpecialScan(ScanInfo &&file)
: file(std::move(file)) {
}
EditScans::EditScans( EditScans::EditScans(
QWidget *parent, QWidget *parent,
not_null<PanelController*> controller, not_null<PanelController*> controller,
const QString &header, const QString &header,
const QString &errorMissing, const QString &errorMissing,
std::vector<ScanInfo> &&files, std::vector<ScanInfo> &&files)
std::unique_ptr<ScanInfo> &&selfie)
: RpWidget(parent) : RpWidget(parent)
, _controller(controller) , _controller(controller)
, _files(std::move(files)) , _files(std::move(files))
, _selfie(std::move(selfie))
, _initialCount(_files.size()) , _initialCount(_files.size())
, _errorMissing(errorMissing) , _errorMissing(errorMissing)
, _content(this) { , _content(this) {
setupContent(header); setupScans(header);
}
EditScans::EditScans(
QWidget *parent,
not_null<PanelController*> controller,
std::map<SpecialFile, ScanInfo> &&specialFiles)
: RpWidget(parent)
, _controller(controller)
, _initialCount(-1)
, _content(this) {
setupSpecialScans(std::move(specialFiles));
} }
bool EditScans::uploadedSomeMore() const { bool EditScans::uploadedSomeMore() const {
@ -261,6 +287,13 @@ bool EditScans::uploadedSomeMore() const {
} }
base::optional<int> EditScans::validateGetErrorTop() { base::optional<int> EditScans::validateGetErrorTop() {
auto result = base::optional<int>();
const auto suggestResult = [&](int value) {
if (!result || *result > value) {
result = value;
}
};
const auto exists = ranges::find_if( const auto exists = ranges::find_if(
_files, _files,
[](const ScanInfo &file) { return !file.deleted; }) != end(_files); [](const ScanInfo &file) { return !file.deleted; }) != end(_files);
@ -269,11 +302,10 @@ base::optional<int> EditScans::validateGetErrorTop() {
[](const ScanInfo &file) { return !file.error.isEmpty(); } [](const ScanInfo &file) { return !file.error.isEmpty(); }
) != end(_files); ) != end(_files);
auto result = base::optional<int>(); if (_upload && (!exists
if (!exists || ((errorExists || _uploadMoreError) && !uploadedSomeMore()))) {
|| ((errorExists || _uploadMoreError) && !uploadedSomeMore())) {
toggleError(true); toggleError(true);
result = (_files.size() > 5) ? _upload->y() : _header->y(); suggestResult((_files.size() > 5) ? _upload->y() : _header->y());
} }
const auto nonDeletedErrorIt = ranges::find_if( const auto nonDeletedErrorIt = ranges::find_if(
@ -283,24 +315,21 @@ base::optional<int> EditScans::validateGetErrorTop() {
}); });
if (nonDeletedErrorIt != end(_files)) { if (nonDeletedErrorIt != end(_files)) {
const auto index = (nonDeletedErrorIt - begin(_files)); const auto index = (nonDeletedErrorIt - begin(_files));
toggleError(true); // toggleError(true);
if (!result) { suggestResult(_rows[index]->y());
result = _rows[index]->y();
}
} }
if (_selfie for (const auto &[type, scan] : _specialScans) {
&& (!_selfie->key.id if (!scan.file.key.id
|| _selfie->deleted || scan.file.deleted
|| !_selfie->error.isEmpty())) { || !scan.file.error.isEmpty()) {
toggleSelfieError(true); toggleSpecialScanError(type, true);
if (!result) { suggestResult(scan.header->y());
result = _selfieHeader->y();
} }
} }
return result; return result;
} }
void EditScans::setupContent(const QString &header) { void EditScans::setupScans(const QString &header) {
const auto inner = _content.data(); const auto inner = _content.data();
inner->move(0, 0); inner->move(0, 0);
@ -356,43 +385,92 @@ void EditScans::setupContent(const QString &header) {
inner, inner,
st::passportFormDividerHeight)); st::passportFormDividerHeight));
if (_selfie) { init();
_selfieHeader = inner->add( }
void EditScans::setupSpecialScans(std::map<SpecialFile, ScanInfo> &&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<Ui::SlideWrap<Ui::FlatLabel>>( object_ptr<Ui::SlideWrap<Ui::FlatLabel>>(
inner, inner,
object_ptr<Ui::FlatLabel>( object_ptr<Ui::FlatLabel>(
inner, inner,
lang(lng_passport_selfie_title), title(type),
Ui::FlatLabel::InitType::Simple, Ui::FlatLabel::InitType::Simple,
st::passportFormHeader), st::passportFormHeader),
st::passportUploadHeaderPadding)); st::passportUploadHeaderPadding));
_selfieHeader->toggle(_selfie->key.id != 0, anim::type::instant); scan.header->toggle(scan.file.key.id != 0, anim::type::instant);
_selfieWrap = inner->add(object_ptr<Ui::VerticalLayout>(inner)); scan.wrap = inner->add(object_ptr<Ui::VerticalLayout>(inner));
if (_selfie->key.id) { if (scan.file.key.id) {
createSelfieRow(*_selfie); createSpecialScanRow(scan, scan.file);
} }
_selfieUpload = inner->add( scan.upload = inner->add(
object_ptr<Info::Profile::Button>( object_ptr<Info::Profile::Button>(
inner, inner,
Lang::Viewer( Lang::Viewer(
lng_passport_upload_selfie uploadKey(type)
) | Info::Profile::ToUpperValue(), ) | Info::Profile::ToUpperValue(),
st::passportUploadButton), st::passportUploadButton),
st::passportUploadButtonPadding); st::passportUploadButtonPadding);
_selfieUpload->addClickHandler([=] { scan.upload->addClickHandler([=] {
chooseSelfie(); chooseSpecialScan(type);
}); });
inner->add(object_ptr<PanelLabel>( inner->add(object_ptr<PanelLabel>(
inner, inner,
object_ptr<Ui::FlatLabel>( object_ptr<Ui::FlatLabel>(
_content, _content,
lang(lng_passport_selfie_description), description(type),
Ui::FlatLabel::InitType::Simple, Ui::FlatLabel::InitType::Simple,
st::passportFormLabel), st::passportFormLabel),
st::passportFormLabelPadding)); st::passportFormLabelPadding));
} }
init();
}
void EditScans::init() {
_controller->scanUpdated( _controller->scanUpdated(
) | rpl::start_with_next([=](ScanInfo &&info) { ) | rpl::start_with_next([=](ScanInfo &&info) {
updateScan(std::move(info)); updateScan(std::move(info));
@ -410,8 +488,8 @@ void EditScans::setupContent(const QString &header) {
} }
void EditScans::updateScan(ScanInfo &&info) { void EditScans::updateScan(ScanInfo &&info) {
if (info.selfie) { if (info.special) {
updateSelfie(std::move(info)); updateSpecialScan(*info.special, std::move(info));
return; return;
} }
const auto i = ranges::find(_files, info.key, [](const ScanInfo &file) { 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); Expects(info.key.id != 0);
if (!_selfie) { const auto i = _specialScans.find(type);
if (i == end(_specialScans)) {
return; return;
} }
if (_selfie->key.id) { auto &scan = i->second;
updateFileRow(_selfieRow->entity(), info); if (scan.file.key.id) {
updateFileRow(scan.row->entity(), info);
if (!info.deleted) { if (!info.deleted) {
hideSelfieError(); hideSpecialScanError(type);
} }
} else { } else {
createSelfieRow(info); createSpecialScanRow(scan, info);
_selfieWrap->resizeToWidth(width()); scan.wrap->resizeToWidth(width());
_selfieRow->show(anim::type::normal); scan.row->show(anim::type::normal);
_selfieHeader->show(anim::type::normal); scan.header->show(anim::type::normal);
} }
*_selfie = std::move(info); scan.file = std::move(info);
} }
void EditScans::updateFileRow( void EditScans::updateFileRow(
@ -468,24 +548,37 @@ void EditScans::updateFileRow(
}; };
void EditScans::createSelfieRow(const ScanInfo &info) { void EditScans::createSpecialScanRow(
_selfieRow = createScan( SpecialScan &scan,
_selfieWrap, const ScanInfo &info) {
info, Expects(scan.file.special.has_value());
lang(lng_passport_selfie_name));
const auto row = _selfieRow->entity(); 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( row->deleteClicks(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
_controller->deleteSelfie(); _controller->deleteSpecialScan(type);
}, row->lifetime()); }, row->lifetime());
row->restoreClicks( row->restoreClicks(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
_controller->restoreSelfie(); _controller->restoreSpecialScan(type);
}, row->lifetime()); }, row->lifetime());
hideSelfieError(); hideSpecialScanError(type);
} }
void EditScans::pushScan(const ScanInfo &info) { 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) { ChooseScan(this, [=](QByteArray &&content) {
_controller->uploadSelfie(std::move(content)); _controller->uploadSpecialScan(type, std::move(content));
}, [=](ReadScanError error) { }, [=](ReadScanError error) {
_controller->readScanError(error); _controller->readScanError(error);
}); });
@ -643,32 +736,42 @@ void EditScans::errorAnimationCallback() {
} }
} }
void EditScans::hideSelfieError() { void EditScans::hideSpecialScanError(SpecialFile type) {
toggleSelfieError(false); toggleSpecialScanError(type, false);
} }
void EditScans::toggleSelfieError(bool shown) { auto EditScans::findSpecialScan(SpecialFile type) -> SpecialScan& {
if (_selfieErrorShown != shown) { const auto i = _specialScans.find(type);
_selfieErrorShown = shown; Assert(i != end(_specialScans));
_selfieErrorAnimation.start( return i->second;
[=] { selfieErrorAnimationCallback(); }, }
_selfieErrorShown ? 0. : 1.,
_selfieErrorShown ? 1. : 0., 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); st::passportDetailsField.duration);
} }
} }
void EditScans::selfieErrorAnimationCallback() { void EditScans::specialScanErrorAnimationCallback(SpecialFile type) {
const auto error = _selfieErrorAnimation.current( auto &scan = findSpecialScan(type);
_selfieErrorShown ? 1. : 0.); const auto error = scan.errorAnimation.current(
scan.errorShown ? 1. : 0.);
if (error == 0.) { if (error == 0.) {
_selfieUpload->setColorOverride(base::none); scan.upload->setColorOverride(base::none);
} else { } else {
_selfieUpload->setColorOverride(anim::color( scan.upload->setColorOverride(anim::color(
st::passportUploadButton.textFg, st::passportUploadButton.textFg,
st::boxTextFgError, st::boxTextFgError,
error)); error));
} }
} }
EditScans::~EditScans() = default;
} // namespace Passport } // namespace Passport

View File

@ -26,6 +26,7 @@ class Button;
namespace Passport { namespace Passport {
enum class SpecialFile;
class PanelController; class PanelController;
class ScanButton; class ScanButton;
struct ScanInfo; struct ScanInfo;
@ -44,8 +45,11 @@ public:
not_null<PanelController*> controller, not_null<PanelController*> controller,
const QString &header, const QString &header,
const QString &errorMissing, const QString &errorMissing,
std::vector<ScanInfo> &&files, std::vector<ScanInfo> &&files);
std::unique_ptr<ScanInfo> &&selfie); EditScans(
QWidget *parent,
not_null<PanelController*> controller,
std::map<SpecialFile, ScanInfo> &&specialFiles);
base::optional<int> validateGetErrorTop(); base::optional<int> validateGetErrorTop();
@ -54,21 +58,31 @@ public:
base::lambda<void(QByteArray&&)> doneCallback, base::lambda<void(QByteArray&&)> doneCallback,
base::lambda<void(ReadScanError)> errorCallback); base::lambda<void(ReadScanError)> errorCallback);
~EditScans();
private: private:
void setupContent(const QString &header); struct SpecialScan;
void setupScans(const QString &header);
void setupSpecialScans(std::map<SpecialFile, ScanInfo> &&files);
void init();
void chooseScan(); void chooseScan();
void chooseSelfie(); void chooseSpecialScan(SpecialFile type);
void updateScan(ScanInfo &&info); void updateScan(ScanInfo &&info);
void updateSelfie(ScanInfo &&info); void updateSpecialScan(SpecialFile type, ScanInfo &&info);
void updateFileRow( void updateFileRow(
not_null<ScanButton*> button, not_null<ScanButton*> button,
const ScanInfo &info); const ScanInfo &info);
void pushScan(const ScanInfo &info); void pushScan(const ScanInfo &info);
void createSelfieRow(const ScanInfo &info); void createSpecialScanRow(
SpecialScan &scan,
const ScanInfo &info);
base::unique_qptr<Ui::SlideWrap<ScanButton>> createScan( base::unique_qptr<Ui::SlideWrap<ScanButton>> createScan(
not_null<Ui::VerticalLayout*> parent, not_null<Ui::VerticalLayout*> parent,
const ScanInfo &info, const ScanInfo &info,
const QString &name); const QString &name);
SpecialScan &findSpecialScan(SpecialFile type);
rpl::producer<QString> uploadButtonText() const; rpl::producer<QString> uploadButtonText() const;
@ -77,13 +91,12 @@ private:
void errorAnimationCallback(); void errorAnimationCallback();
bool uploadedSomeMore() const; bool uploadedSomeMore() const;
void toggleSelfieError(bool shown); void toggleSpecialScanError(SpecialFile type, bool shown);
void hideSelfieError(); void hideSpecialScanError(SpecialFile type);
void selfieErrorAnimationCallback(); void specialScanErrorAnimationCallback(SpecialFile type);
not_null<PanelController*> _controller; not_null<PanelController*> _controller;
std::vector<ScanInfo> _files; std::vector<ScanInfo> _files;
std::unique_ptr<ScanInfo> _selfie;
int _initialCount = 0; int _initialCount = 0;
QString _errorMissing; QString _errorMissing;
@ -98,12 +111,7 @@ private:
bool _errorShown = false; bool _errorShown = false;
Animation _errorAnimation; Animation _errorAnimation;
QPointer<Ui::SlideWrap<Ui::FlatLabel>> _selfieHeader; std::map<SpecialFile, SpecialScan> _specialScans;
QPointer<Ui::VerticalLayout> _selfieWrap;
base::unique_qptr<Ui::SlideWrap<ScanButton>> _selfieRow;
QPointer<Info::Profile::Button> _selfieUpload;
bool _selfieErrorShown = false;
Animation _selfieErrorAnimation;
}; };