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_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";

View file

@ -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<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;
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<bytes> 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<string> = Vector<LangP
langpack.getDifference#b2e4d7d from_version:int = LangPackDifference;
langpack.getLanguages#800fd57d = Vector<LangPackLanguage>;
// LAYER 80
// LAYER 81

View file

@ -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

View file

@ -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);

View file

@ -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<Window::Controller*> 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<const Value*> 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<const Value*> value) {
selfieDeleteRestore(value, true);
void FormController::deleteSpecialScan(
not_null<const Value*> value,
SpecialFile type) {
specialScanDeleteRestore(value, type, true);
}
void FormController::restoreSelfie(not_null<const Value*> value) {
selfieDeleteRestore(value, false);
void FormController::restoreSpecialScan(
not_null<const Value*> 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<const Value*> 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<const Value*> 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<const Value*> 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<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) {
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*> 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*> 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*> value) {
MTP_bytes(encryptedData.bytes),
MTP_bytes(value->data.hashInEdit),
MTP_bytes(value->data.encryptedSecretInEdit)),
frontSide,
reverseSide,
selfie,
MTP_vector<MTPInputSecureFile>(inputFiles),
MTPSecurePlainData(),
selfie));
MTPSecurePlainData()));
}
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),
type,
MTPSecureData(),
MTPInputSecureFile(),
MTPInputSecureFile(),
MTPInputSecureFile(),
MTPVector<MTPInputSecureFile>(),
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 };

View file

@ -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<File> scans;
std::vector<EditFile> scansInEdit;
std::map<SpecialFile, File> specialScans;
QString scanMissingError;
base::optional<File> selfie;
base::optional<EditFile> selfieInEdit;
std::vector<EditFile> scansInEdit;
std::map<SpecialFile, EditFile> specialScansInEdit;
Verification verification;
bytes::vector submitHash;
@ -244,9 +258,16 @@ public:
void uploadScan(not_null<const Value*> value, QByteArray &&content);
void deleteScan(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 deleteSelfie(not_null<const Value*> value);
void restoreSelfie(not_null<const Value*> value);
void uploadSpecialScan(
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;
@ -349,8 +370,9 @@ private:
not_null<const Value*> value,
int fileIndex,
bool deleted);
void selfieDeleteRestore(
void specialScanDeleteRestore(
not_null<const Value*> value,
SpecialFile type,
bool deleted);
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::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.");
}
}

View file

@ -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<ScanInfo> 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<SpecialFile> {
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<ScopeError> 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<not_null<const Value*>> &files) const {
const auto i = ranges::find_if(files, [](not_null<const Value*> 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<const Value*> 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<PanelEditDocument>(
_panel.get(),
this,
@ -1050,13 +1081,26 @@ std::vector<ScanInfo> PanelController::valueFiles(
return result;
}
std::unique_ptr<ScanInfo> PanelController::valueSelfie(
std::map<SpecialFile, ScanInfo> PanelController::valueSpecialFiles(
const Value &value) const {
if (value.selfieInEdit) {
return std::make_unique<ScanInfo>(
collectScanInfo(*value.selfieInEdit));
auto result = std::map<SpecialFile, ScanInfo>();
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<ScanInfo>();
return result;
}
void PanelController::cancelValueEdit() {

View file

@ -30,7 +30,7 @@ struct ScanInfo {
QString status;
QImage thumb;
bool deleted = false;
bool selfie = false;
base::optional<SpecialFile> 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<ScanInfo> scanUpdated() const;
rpl::producer<ScopeError> 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<not_null<const Value*>> &files) const;
int findNonEmptyDocumentIndex(const Scope &scope) const;
void requestScopeFilesType(int index);
void cancelValueEdit();
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 processVerificationNeeded(not_null<const Value*> value);

View file

@ -213,7 +213,7 @@ PanelEditDocument::PanelEditDocument(
const ValueMap &scanData,
const QString &missingScansError,
std::vector<ScanInfo> &&files,
std::unique_ptr<ScanInfo> &&selfie)
std::map<SpecialFile, ScanInfo> &&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<ScanInfo> &&files,
std::unique_ptr<ScanInfo> &&selfie) {
std::map<SpecialFile, ScanInfo> &&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<Ui::RpWidget*> PanelEditDocument::setupContent(
const ValueMap *scanData,
const QString &missingScansError,
std::vector<ScanInfo> &&files,
std::unique_ptr<ScanInfo> &&selfie) {
std::map<SpecialFile, ScanInfo> &&specialFiles) {
const auto inner = _scroll->setOwnedWidget(
object_ptr<Ui::VerticalLayout>(this));
_scroll->widthValue(
@ -285,15 +285,23 @@ not_null<Ui::RpWidget*> PanelEditDocument::setupContent(
inner->resizeToWidth(width);
}, 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(
object_ptr<EditScans>(
inner,
_controller,
_scheme.scansHeader,
missingScansError,
std::move(files),
std::move(selfie)));
std::move(files)));
} else {
inner->add(object_ptr<BoxContentDivider>(
inner,

View file

@ -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<ScanInfo> &&files,
std::unique_ptr<ScanInfo> &&selfie);
std::map<SpecialFile, ScanInfo> &&specialFiles);
PanelEditDocument(
QWidget *parent,
not_null<PanelController*> controller,
@ -84,13 +85,13 @@ private:
const ValueMap *scanData,
const QString &missingScansError,
std::vector<ScanInfo> &&files,
std::unique_ptr<ScanInfo> &&selfie);
std::map<SpecialFile, ScanInfo> &&specialFiles);
not_null<Ui::RpWidget*> setupContent(
const ValueMap &data,
const ValueMap *scanData,
const QString &missingScansError,
std::vector<ScanInfo> &&files,
std::unique_ptr<ScanInfo> &&selfie);
std::map<SpecialFile, ScanInfo> &&specialFiles);
void updateControlsGeometry();
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(
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<PanelController*> controller,
const QString &header,
const QString &errorMissing,
std::vector<ScanInfo> &&files,
std::unique_ptr<ScanInfo> &&selfie)
std::vector<ScanInfo> &&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<PanelController*> controller,
std::map<SpecialFile, ScanInfo> &&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<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(
_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(); }
) != end(_files);
auto result = base::optional<int>();
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<int> 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<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>>(
inner,
object_ptr<Ui::FlatLabel>(
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<Ui::VerticalLayout>(inner));
if (_selfie->key.id) {
createSelfieRow(*_selfie);
scan.header->toggle(scan.file.key.id != 0, anim::type::instant);
scan.wrap = inner->add(object_ptr<Ui::VerticalLayout>(inner));
if (scan.file.key.id) {
createSpecialScanRow(scan, scan.file);
}
_selfieUpload = inner->add(
scan.upload = inner->add(
object_ptr<Info::Profile::Button>(
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<PanelLabel>(
inner,
object_ptr<Ui::FlatLabel>(
_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

View file

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