Start improved passport support.

This commit is contained in:
John Preston 2018-08-12 22:48:43 +03:00
parent bdab477040
commit 3c43f621ce
7 changed files with 416 additions and 195 deletions

View File

@ -1668,7 +1668,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_passport_error_cant_read" = "Can't read this file. Please choose an image.";
"lng_passport_bad_name" = "Please use latin characters only.";
"lng_passport_wait_upload" = "Please wait while upload is finished.";
"lng_passport_fix_errors" = "Please correct errors.";
"lng_passport_app_out_of_date" = "Sorry, your Telegram app is out of date and can't handle this request. Please update Telegram.";
"lng_export_title" = "Export Personal Data";

View File

@ -995,9 +995,9 @@ secureValueTypeTemporaryRegistration#ea02ec33 = SecureValueType;
secureValueTypePhone#b320aadb = SecureValueType;
secureValueTypeEmail#8e3ca7ee = SecureValueType;
secureValue#b4b4b699 flags:# type:SecureValueType data:flags.0?SecureData front_side:flags.1?SecureFile reverse_side:flags.2?SecureFile selfie:flags.3?SecureFile files:flags.4?Vector<SecureFile> plain_data:flags.5?SecurePlainData hash:bytes = SecureValue;
secureValue#187fa0ca flags:# type:SecureValueType data:flags.0?SecureData front_side:flags.1?SecureFile reverse_side:flags.2?SecureFile selfie:flags.3?SecureFile translation:flags.6?Vector<SecureFile> files:flags.4?Vector<SecureFile> plain_data:flags.5?SecurePlainData hash:bytes = SecureValue;
inputSecureValue#67872e8 flags:# type:SecureValueType data:flags.0?SecureData front_side:flags.1?InputSecureFile reverse_side:flags.2?InputSecureFile selfie:flags.3?InputSecureFile files:flags.4?Vector<InputSecureFile> plain_data:flags.5?SecurePlainData = InputSecureValue;
inputSecureValue#db21d0a7 flags:# type:SecureValueType data:flags.0?SecureData front_side:flags.1?InputSecureFile reverse_side:flags.2?InputSecureFile selfie:flags.3?InputSecureFile translation:flags.6?Vector<InputSecureFile> files:flags.4?Vector<InputSecureFile> plain_data:flags.5?SecurePlainData = InputSecureValue;
secureValueHash#ed1ecdb0 type:SecureValueType hash:bytes = SecureValueHash;
@ -1007,10 +1007,13 @@ secureValueErrorReverseSide#868a2aa5 type:SecureValueType file_hash:bytes text:s
secureValueErrorSelfie#e537ced6 type:SecureValueType file_hash:bytes text:string = SecureValueError;
secureValueErrorFile#7a700873 type:SecureValueType file_hash:bytes text:string = SecureValueError;
secureValueErrorFiles#666220e9 type:SecureValueType file_hash:Vector<bytes> text:string = SecureValueError;
secureValueError#869d758f type:SecureValueType hash:bytes text:string = SecureValueError;
secureValueErrorTranslationFile#a1144770 type:SecureValueType file_hash:bytes text:string = SecureValueError;
secureValueErrorTranslationFiles#34636dd8 type:SecureValueType file_hash:Vector<bytes> text:string = SecureValueError;
secureCredentialsEncrypted#33f0ea47 data:bytes hash:bytes secret:bytes = SecureCredentialsEncrypted;
account.authorizationForm#cb976d53 flags:# selfie_required:flags.1?true required_types:Vector<SecureValueType> values:Vector<SecureValue> errors:Vector<SecureValueError> users:Vector<User> privacy_policy_url:flags.0?string = account.AuthorizationForm;
account.authorizationForm#ad2e1cd8 flags:# required_types:Vector<SecureRequiredType> values:Vector<SecureValue> errors:Vector<SecureValueError> users:Vector<User> privacy_policy_url:flags.0?string = account.AuthorizationForm;
account.sentEmailCode#811f854f email_pattern:string length:int = account.SentEmailCode;
@ -1033,6 +1036,9 @@ secureSecretSettings#1527bcac secure_algo:SecurePasswordKdfAlgo secure_secret:by
inputCheckPasswordEmpty#9880f658 = InputCheckPasswordSRP;
inputCheckPasswordSRP#d27ff082 srp_id:long A:bytes M1:bytes = InputCheckPasswordSRP;
secureRequiredType#829d99da flags:# native_names:flags.0?true selfie_required:flags.1?true translation_required:flags.2?true type:SecureValueType = SecureRequiredType;
secureRequiredTypeOneOf#27477b4 types:Vector<SecureRequiredType> = SecureRequiredType;
---functions---
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
@ -1321,4 +1327,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 84
// LAYER 85

View File

@ -132,7 +132,30 @@ MTPSecureValueType ConvertType(Value::Type type) {
return MTP_secureValueTypeEmail();
}
Unexpected("Type in FormController::submit.");
};
}
void CollectToRequestedRow(
RequestedRow &row,
const MTPSecureRequiredType &data) {
data.match([&](const MTPDsecureRequiredType &data) {
row.values.emplace_back(ConvertType(data.vtype));
auto &value = row.values.back();
value.selfieRequired = data.is_selfie_required();
value.translationRequired = data.is_translation_required();
value.nativeNames = data.is_native_names();
}, [&](const MTPDsecureRequiredTypeOneOf &data) {
row.values.reserve(row.values.size() + data.vtypes.v.size());
for (const auto &one : data.vtypes.v) {
CollectToRequestedRow(row, one);
}
});
}
RequestedRow CollectRequestedRow(const MTPSecureRequiredType &data) {
auto result = RequestedRow();
CollectToRequestedRow(result, data);
return result;
}
QJsonObject GetJSONFromMap(
const std::map<QString, bytes::const_span> &map) {
@ -257,12 +280,13 @@ UploadScanData *UploadScanDataPointer::operator->() const {
return _value.get();
}
RequestedValue::RequestedValue(Value::Type type) : type(type) {
}
Value::Value(Type type) : type(type) {
}
bool Value::requiresSpecialScan(
SpecialFile type,
bool selfieRequired) const {
bool Value::requiresSpecialScan(SpecialFile type) const {
switch (type) {
case SpecialFile::FrontSide:
return (this->type == Type::Passport)
@ -278,9 +302,11 @@ bool Value::requiresSpecialScan(
Unexpected("Special scan type in requiresSpecialScan.");
}
bool Value::scansAreFilled(bool selfieRequired) const {
if (!requiresSpecialScan(SpecialFile::FrontSide, selfieRequired)) {
return !scans.empty();
bool Value::scansAreFilled() const {
if (!requiresSpecialScan(SpecialFile::FrontSide) && scans.empty()) {
return false;
} else if (translationRequired && translations.empty()) {
return false;
}
const auto types = {
SpecialFile::FrontSide,
@ -288,14 +314,13 @@ bool Value::scansAreFilled(bool selfieRequired) const {
SpecialFile::Selfie
};
for (const auto type : types) {
if (requiresSpecialScan(type, selfieRequired)
if (requiresSpecialScan(type)
&& (specialScans.find(type) == end(specialScans))) {
return false;
}
}
return true;
};
}
FormController::FormController(
not_null<Window::Controller*> controller,
@ -346,8 +371,7 @@ auto FormController::prepareFinalData() -> FinalData {
object.insert("files", files);
}
for (const auto &[type, scan] : value->specialScans) {
const auto selfieRequired = _form.identitySelfieRequired;
if (value->requiresSpecialScan(type, selfieRequired)) {
if (value->requiresSpecialScan(type)) {
object.insert(
SpecialScanCredentialsKey(type),
GetJSONFromFile(scan));
@ -364,17 +388,21 @@ auto FormController::prepareFinalData() -> FinalData {
addValueToJSON(key, value);
}
};
const auto scopes = ComputeScopes(this);
const auto scopes = ComputeScopes(_form);
for (const auto &scope : scopes) {
const auto row = ComputeScopeRow(scope);
if (row.ready.isEmpty() || !row.error.isEmpty()) {
errors.push_back(scope.fields);
errors.push_back(scope.details
? scope.details
: scope.documents[0].get());
continue;
}
addValue(scope.fields);
if (scope.details) {
addValue(scope.details);
}
if (!scope.documents.empty()) {
for (const auto &document : scope.documents) {
if (document->scansAreFilled(scope.selfieRequired)) {
if (document->scansAreFilled()) {
addValue(document);
break;
}
@ -803,6 +831,7 @@ void FormController::decryptValues() {
}
void FormController::fillErrors() {
// #TODO passport filter by flags
const auto find = [&](const MTPSecureValueType &type) -> Value* {
const auto converted = ConvertType(type);
const auto i = _form.values.find(ConvertType(type));
@ -835,43 +864,44 @@ void FormController::fillErrors() {
}
};
for (const auto &error : _form.pendingErrors) {
switch (error.type()) {
case mtpc_secureValueErrorData: {
const auto &data = error.c_secureValueErrorData();
error.match([&](const MTPDsecureValueError &data) {
if (const auto value = find(data.vtype)) {
value->error = qs(data.vtext);
}
}, [&](const MTPDsecureValueErrorData &data) {
if (const auto value = find(data.vtype)) {
const auto key = qs(data.vfield);
value->data.parsed.fields[key].error = qs(data.vtext);
}
} break;
case mtpc_secureValueErrorFile: {
const auto &data = error.c_secureValueErrorFile();
}, [&](const MTPDsecureValueErrorFile &data) {
const auto hash = bytes::make_span(data.vfile_hash.v);
if (const auto value = find(data.vtype)) {
if (const auto file = scan(*value, hash)) {
file->error = qs(data.vtext);
}
}
} break;
case mtpc_secureValueErrorFiles: {
const auto &data = error.c_secureValueErrorFiles();
}, [&](const MTPDsecureValueErrorFiles &data) {
if (const auto value = find(data.vtype)) {
value->scanMissingError = qs(data.vtext);
}
} break;
case mtpc_secureValueErrorFrontSide: {
const auto &data = error.c_secureValueErrorFrontSide();
}, [&](const MTPDsecureValueErrorTranslationFile &data) {
const auto hash = bytes::make_span(data.vfile_hash.v);
if (const auto value = find(data.vtype)) {
if (const auto file = scan(*value, hash)) { // #TODO passport
file->error = qs(data.vtext);
}
}
}, [&](const MTPDsecureValueErrorTranslationFiles &data) {
if (const auto value = find(data.vtype)) {
value->translationMissingError = qs(data.vtext);
}
}, [&](const MTPDsecureValueErrorFrontSide &data) {
setSpecialScanError(SpecialFile::FrontSide, data);
} break;
case mtpc_secureValueErrorReverseSide: {
const auto &data = error.c_secureValueErrorReverseSide();
}, [&](const MTPDsecureValueErrorReverseSide &data) {
setSpecialScanError(SpecialFile::ReverseSide, data);
} break;
case mtpc_secureValueErrorSelfie: {
const auto &data = error.c_secureValueErrorSelfie();
}, [&](const MTPDsecureValueErrorSelfie &data) {
setSpecialScanError(SpecialFile::Selfie, data);
} break;
default: Unexpected("Error type in FormController::fillErrors.");
}
});
}
}
@ -1312,9 +1342,13 @@ void FormController::startValueEdit(not_null<const Value*> value) {
for (auto &scan : nonconst->scans) {
loadFile(scan);
}
if (nonconst->translationRequired) {
for (auto &scan : nonconst->translations) {
loadFile(scan);
}
}
for (auto &[type, scan] : nonconst->specialScans) {
const auto selfieRequired = _form.identitySelfieRequired;
if (nonconst->requiresSpecialScan(type, selfieRequired)) {
if (nonconst->requiresSpecialScan(type)) {
loadFile(scan);
}
}
@ -1324,6 +1358,12 @@ void FormController::startValueEdit(not_null<const Value*> value) {
return EditFile(nonconst, file, nullptr);
}) | ranges::to_vector;
nonconst->translationsInEdit = ranges::view::all(
nonconst->translations
) | ranges::view::transform([=](const File &file) {
return EditFile(nonconst, file, nullptr);
}) | ranges::to_vector;
nonconst->specialScansInEdit.clear();
for (const auto &[type, scan] : nonconst->specialScans) {
nonconst->specialScansInEdit.emplace(type, EditFile(
@ -1597,7 +1637,7 @@ void FormController::saveEncryptedValue(not_null<Value*> value) {
return;
}
const auto inputFile = [](const EditFile &file) {
const auto wrapFile = [](const EditFile &file) {
if (const auto uploadData = file.uploadData.get()) {
return MTP_inputSecureFileUploaded(
MTP_long(file.fields.id),
@ -1611,13 +1651,22 @@ void FormController::saveEncryptedValue(not_null<Value*> value) {
MTP_long(file.fields.accessHash));
};
auto inputFiles = QVector<MTPInputSecureFile>();
inputFiles.reserve(value->scansInEdit.size());
auto files = QVector<MTPInputSecureFile>();
files.reserve(value->scansInEdit.size());
for (const auto &scan : value->scansInEdit) {
if (scan.deleted) {
continue;
}
inputFiles.push_back(inputFile(scan));
files.push_back(wrapFile(scan));
}
auto translations = QVector<MTPInputSecureFile>();
translations.reserve(value->translationsInEdit.size());
for (const auto &scan : value->translationsInEdit) {
if (scan.deleted) {
continue;
}
translations.push_back(wrapFile(scan));
}
if (value->data.secret.empty()) {
@ -1639,7 +1688,7 @@ void FormController::saveEncryptedValue(not_null<Value*> value) {
const auto specialFile = [&](SpecialFile type) {
const auto i = value->specialScansInEdit.find(type);
return (i != end(value->specialScansInEdit) && !i->second.deleted)
? inputFile(i->second)
? wrapFile(i->second)
: MTPInputSecureFile();
};
const auto frontSide = specialFile(SpecialFile::FrontSide);
@ -1659,6 +1708,9 @@ void FormController::saveEncryptedValue(not_null<Value*> value) {
| (hasSpecialFile(SpecialFile::Selfie)
? MTPDinputSecureValue::Flag::f_selfie
: MTPDinputSecureValue::Flag(0))
| (value->translationsInEdit.empty()
? MTPDinputSecureValue::Flag(0)
: MTPDinputSecureValue::Flag::f_translation)
| (value->scansInEdit.empty()
? MTPDinputSecureValue::Flag(0)
: MTPDinputSecureValue::Flag::f_files);
@ -1674,7 +1726,8 @@ void FormController::saveEncryptedValue(not_null<Value*> value) {
frontSide,
reverseSide,
selfie,
MTP_vector<MTPInputSecureFile>(inputFiles),
MTP_vector<MTPInputSecureFile>(translations),
MTP_vector<MTPInputSecureFile>(files),
MTPSecurePlainData()));
}
@ -1704,6 +1757,7 @@ void FormController::savePlainTextValue(not_null<Value*> value) {
MTPInputSecureFile(),
MTPInputSecureFile(),
MTPVector<MTPInputSecureFile>(),
MTPVector<MTPInputSecureFile>(),
plain(MTP_string(text))));
}
@ -2157,13 +2211,14 @@ auto FormController::findFile(const FileKey &key)
}
void FormController::formDone(const MTPaccount_AuthorizationForm &result) {
parseForm(result);
if (!_passwordRequestId) {
if (!parseForm(result)) {
_view->showCriticalError(lang(lng_passport_form_error));
} else if (!_passwordRequestId) {
showForm();
}
}
void FormController::parseForm(const MTPaccount_AuthorizationForm &result) {
bool FormController::parseForm(const MTPaccount_AuthorizationForm &result) {
Expects(result.type() == mtpc_account_authorizationForm);
const auto &data = result.c_account_authorizationForm();
@ -2177,21 +2232,34 @@ void FormController::parseForm(const MTPaccount_AuthorizationForm &result) {
if (alreadyIt != _form.values.end()) {
LOG(("API Error: Two values for type %1 in authorization form"
"%1").arg(int(type)));
continue;
return false;
}
_form.values.emplace(type, std::move(parsed));
}
_form.identitySelfieRequired = data.is_selfie_required();
if (data.has_privacy_policy_url()) {
_form.privacyPolicyUrl = qs(data.vprivacy_policy_url);
}
for (const auto &required : data.vrequired_types.v) {
const auto type = ConvertType(required);
_form.request.push_back(type);
_form.values.emplace(type, Value(type));
const auto row = CollectRequestedRow(required);
for (const auto value : row.values) {
const auto [i, ok] = _form.values.emplace(
value.type,
Value(value.type));
i->second.selfieRequired = value.selfieRequired;
i->second.translationRequired = value.translationRequired;
i->second.nativeNames = value.nativeNames;
}
_form.request.push_back(row.values
| ranges::view::transform([](const RequestedValue &value) {
return value.type;
}) | ranges::to_vector);
}
if (!ValidateForm(_form)) {
return false;
}
_bot = App::userLoaded(_request.botId);
_form.pendingErrors = data.verrors.v;
return true;
}
void FormController::formFail(const QString &error) {

View File

@ -168,31 +168,52 @@ struct Value {
Value(Value &&other) = default;
Value &operator=(Value &&other) = default;
bool requiresSpecialScan(SpecialFile type, bool selfieRequired) const;
bool scansAreFilled(bool selfieRequired) const;
bool requiresSpecialScan(SpecialFile type) const;
bool scansAreFilled() const;
Type type;
ValueData data;
std::vector<File> scans;
std::vector<File> translations;
std::map<SpecialFile, File> specialScans;
QString error;
QString scanMissingError;
QString translationMissingError;
std::vector<EditFile> scansInEdit;
std::vector<EditFile> translationsInEdit;
std::map<SpecialFile, EditFile> specialScansInEdit;
Verification verification;
bytes::vector submitHash;
bool selfieRequired = false;
bool translationRequired = false;
bool nativeNames = false;
int editScreens = 0;
mtpRequestId saveRequestId = 0;
};
struct RequestedValue {
explicit RequestedValue(Value::Type type);
Value::Type type;
bool selfieRequired = false;
bool translationRequired = false;
bool nativeNames = false;
};
struct RequestedRow {
std::vector<RequestedValue> values;
};
struct Form {
using Request = std::vector<std::vector<Value::Type>>;
std::map<Value::Type, Value> values;
std::vector<Value::Type> request;
bool identitySelfieRequired = false;
Request request;
QString privacyPolicyUrl;
QVector<MTPSecureValueError> pendingErrors;
};
struct PasswordSettings {
@ -333,7 +354,7 @@ private:
void formDone(const MTPaccount_AuthorizationForm &result);
void formFail(const QString &error);
void parseForm(const MTPaccount_AuthorizationForm &result);
bool parseForm(const MTPaccount_AuthorizationForm &result);
void showForm();
Value parseValue(
const MTPSecureValue &value,

View File

@ -18,12 +18,12 @@ namespace {
std::map<Value::Type, Scope::Type> ScopeTypesMap() {
return {
{ Value::Type::PersonalDetails, Scope::Type::Identity },
{ Value::Type::PersonalDetails, Scope::Type::PersonalDetails },
{ Value::Type::Passport, Scope::Type::Identity },
{ Value::Type::DriverLicense, Scope::Type::Identity },
{ Value::Type::IdentityCard, Scope::Type::Identity },
{ Value::Type::InternalPassport, Scope::Type::Identity },
{ Value::Type::Address, Scope::Type::Address },
{ Value::Type::Address, Scope::Type::AddressDetails },
{ Value::Type::UtilityBill, Scope::Type::Address },
{ Value::Type::BankStatement, Scope::Type::Address },
{ Value::Type::RentalAgreement, Scope::Type::Address },
@ -41,62 +41,141 @@ Scope::Type ScopeTypeForValueType(Value::Type type) {
return i->second;
}
std::map<Scope::Type, Value::Type> ScopeFieldsMap() {
std::map<Scope::Type, Value::Type> ScopeDetailsMap() {
return {
{ Scope::Type::PersonalDetails, Value::Type::PersonalDetails },
{ Scope::Type::Identity, Value::Type::PersonalDetails },
{ Scope::Type::AddressDetails, Value::Type::Address },
{ Scope::Type::Address, Value::Type::Address },
{ Scope::Type::Phone, Value::Type::Phone },
{ Scope::Type::Email, Value::Type::Email },
};
}
Value::Type FieldsTypeForScopeType(Scope::Type type) {
static const auto map = ScopeFieldsMap();
Value::Type DetailsTypeForScopeType(Scope::Type type) {
static const auto map = ScopeDetailsMap();
const auto i = map.find(type);
Assert(i != map.end());
return i->second;
}
} // namespace
bool InlineDetails(
const Form::Request &request,
Scope::Type into,
Value::Type details) {
const auto count = ranges::count_if(
request,
[&](const std::vector<Value::Type> &types) {
Expects(!types.empty());
Scope::Scope(Type type, not_null<const Value*> fields)
: type(type)
, fields(fields) {
return ScopeTypeForValueType(types[0]) == into;
});
if (count != 1) {
return false;
}
const auto has = ranges::find_if(
request,
[&](const std::vector<Value::Type> &types) {
Expects(!types.empty());
return (types[0] == details);
}
) != end(request);
return has;
}
std::vector<Scope> ComputeScopes(
not_null<const FormController*> controller) {
auto scopes = std::map<Scope::Type, Scope>();
const auto &form = controller->form();
bool InlineDetails(const Form::Request &request, Value::Type details) {
if (details == Value::Type::PersonalDetails) {
return InlineDetails(request, Scope::Type::Identity, details);
} else if (details == Value::Type::Address) {
return InlineDetails(request, Scope::Type::Address, details);
}
return false;
}
} // namespace
Scope::Scope(Type type) : type(type) {
}
bool ValidateForm(const Form &form) {
base::flat_set<Value::Type> values;
for (const auto &requested : form.request) {
if (requested.empty()) {
LOG(("API Error: Empty types list in authorization form row."));
return false;
}
const auto scopeType = ScopeTypeForValueType(requested[0]);
const auto ownsDetails = (scopeType != Scope::Type::Identity
&& scopeType != Scope::Type::Address);
if (ownsDetails && requested.size() != 1) {
LOG(("API Error: Large types list in authorization form row."));
return false;
}
for (const auto type : requested) {
if (values.contains(type)) {
LOG(("API Error: Value twice in authorization form row."));
return false;
}
values.emplace(type);
}
}
for (const auto &[type, value] : form.values) {
if (!value.translationRequired) {
for (const auto &scan : value.translations) {
if (!scan.error.isEmpty()) {
LOG(("API Error: "
"Translation error in authorization form value."));
return false;
}
}
if (!value.translationMissingError.isEmpty()) {
LOG(("API Error: "
"Translations error in authorization form value."));
return false;
}
}
for (const auto &[type, specialScan] : value.specialScans) {
if (!value.requiresSpecialScan(type)
&& !specialScan.error.isEmpty()) {
LOG(("API Error: "
"Special scan error in authorization form value."));
return false;
}
}
}
return true;
}
std::vector<Scope> ComputeScopes(const Form &form) {
auto result = std::vector<Scope>();
const auto findValue = [&](const Value::Type type) {
const auto i = form.values.find(type);
Assert(i != form.values.end());
return &i->second;
};
for (const auto type : form.request) {
const auto scopeType = ScopeTypeForValueType(type);
const auto fieldsType = FieldsTypeForScopeType(scopeType);
const auto [i, ok] = scopes.emplace(
scopeType,
Scope(scopeType, findValue(fieldsType)));
i->second.selfieRequired = (scopeType == Scope::Type::Identity)
&& form.identitySelfieRequired;
const auto alreadyIt = ranges::find(
i->second.documents,
type,
[](not_null<const Value*> value) { return value->type; });
if (alreadyIt != end(i->second.documents)) {
LOG(("API Error: Value type %1 multiple times in request."
).arg(int(type)));
for (const auto &requested : form.request) {
Assert(!requested.empty());
const auto scopeType = ScopeTypeForValueType(requested[0]);
const auto detailsType = DetailsTypeForScopeType(scopeType);
const auto ownsDetails = (scopeType != Scope::Type::Identity
&& scopeType != Scope::Type::Address);
const auto inlineDetails = InlineDetails(form.request, detailsType);
if (ownsDetails && inlineDetails) {
continue;
} else if (type != fieldsType) {
i->second.documents.push_back(findValue(type));
}
}
auto result = std::vector<Scope>();
result.reserve(scopes.size());
for (auto &[type, scope] : scopes) {
result.push_back(std::move(scope));
result.push_back(Scope(scopeType));
auto &scope = result.back();
scope.details = (ownsDetails || inlineDetails)
? findValue(detailsType)
: nullptr;
if (ownsDetails) {
Assert(requested.size() == 1);
} else {
for (const auto type : requested) {
scope.documents.push_back(findValue(type));
}
}
}
return result;
}
@ -129,7 +208,9 @@ QString JoinScopeRowReadyString(
QString ComputeScopeRowReadyString(const Scope &scope) {
switch (scope.type) {
case Scope::Type::PersonalDetails:
case Scope::Type::Identity:
case Scope::Type::AddressDetails:
case Scope::Type::Address: {
auto list = std::vector<std::pair<QString, QString>>();
const auto pushListValue = [&](
@ -153,10 +234,12 @@ QString ComputeScopeRowReadyString(const Scope &scope) {
}
}
};
const auto &fields = scope.fields->data.parsed.fields;
const auto fields = scope.details
? &scope.details->data.parsed.fields
: nullptr;
const auto document = [&]() -> const Value* {
for (const auto &document : scope.documents) {
if (document->scansAreFilled(scope.selfieRequired)) {
if (document->scansAreFilled()) {
return document;
}
}
@ -195,8 +278,11 @@ QString ComputeScopeRowReadyString(const Scope &scope) {
for (const auto &row : scheme.rows) {
const auto format = row.format;
if (row.valueClass == EditDocumentScheme::ValueClass::Fields) {
const auto i = fields.find(row.key);
if (i == end(fields)) {
if (!fields) {
continue;
}
const auto i = fields->find(row.key);
if (i == end(*fields)) {
return QString();
}
const auto text = i->second.text;
@ -225,8 +311,9 @@ QString ComputeScopeRowReadyString(const Scope &scope) {
} break;
case Scope::Type::Phone:
case Scope::Type::Email: {
Assert(scope.details != nullptr);
const auto format = GetContactScheme(scope.type).format;
const auto &fields = scope.fields->data.parsed.fields;
const auto &fields = scope.details->data.parsed.fields;
const auto i = fields.find("value");
return (i != end(fields))
? (format ? format(i->second.text) : i->second.text)
@ -242,19 +329,30 @@ ScopeRow ComputeScopeRow(const Scope &scope) {
row.ready = ready;
auto errors = QStringList();
const auto addValueErrors = [&](not_null<const Value*> value) {
if (!value->error.isEmpty()) {
errors.push_back(value->error);
}
if (!value->scanMissingError.isEmpty()) {
errors.push_back(value->scanMissingError);
}
if (!value->translationMissingError.isEmpty()) {
errors.push_back(value->translationMissingError);
}
for (const auto &scan : value->scans) {
if (!scan.error.isEmpty()) {
errors.push_back(scan.error);
}
}
for (const auto &scan : value->translations) {
if (!scan.error.isEmpty()) {
errors.push_back(scan.error);
}
}
for (const auto &[type, scan] : value->specialScans) {
if (!scan.error.isEmpty()) {
errors.push_back(scan.error);
}
}
if (!value->scanMissingError.isEmpty()) {
errors.push_back(value->scanMissingError);
}
for (const auto &[key, value] : value->data.parsed.fields) {
if (!value.error.isEmpty()) {
errors.push_back(value.error);
@ -263,7 +361,7 @@ ScopeRow ComputeScopeRow(const Scope &scope) {
};
const auto document = [&]() -> const Value* {
for (const auto &document : scope.documents) {
if (document->scansAreFilled(scope.selfieRequired)) {
if (document->scansAreFilled()) {
return document;
}
}
@ -272,31 +370,35 @@ ScopeRow ComputeScopeRow(const Scope &scope) {
if (document) {
addValueErrors(document);
}
addValueErrors(scope.fields);
if (scope.details) {
addValueErrors(scope.details);
}
if (!errors.isEmpty()) {
row.error = lang(lng_passport_fix_errors);// errors.join('\n');
}
if (row.error.isEmpty()
&& row.ready.isEmpty()
&& scope.type == Scope::Type::Identity
&& scope.selfieRequired) {
auto noSelfieScope = scope;
noSelfieScope.selfieRequired = false;
if (!ComputeScopeRowReadyString(noSelfieScope).isEmpty()) {
// Only selfie is missing.
row.description = lang(lng_passport_identity_selfie);
}
row.error = errors[0];// errors.join('\n');
}
// #TODO passport half-full value
//if (row.error.isEmpty()
// && row.ready.isEmpty()
// && scope.type == Scope::Type::Identity
// && scope.selfieRequired) {
// auto noSelfieScope = scope;
// noSelfieScope.selfieRequired = false;
// if (!ComputeScopeRowReadyString(noSelfieScope).isEmpty()) {
// // Only selfie is missing.
// row.description = lang(lng_passport_identity_selfie);
// }
//}
return row;
};
switch (scope.type) {
case Scope::Type::PersonalDetails:
return addReadyError({
lang(lng_passport_personal_details),
lang(lng_passport_personal_details_enter),
});
case Scope::Type::Identity:
if (scope.documents.empty()) {
return addReadyError({
lang(lng_passport_personal_details),
lang(lng_passport_personal_details_enter),
});
} else if (scope.documents.size() == 1) {
Assert(!scope.documents.empty());
if (scope.documents.size() == 1) {
switch (scope.documents.front()->type) {
case Value::Type::Passport:
return addReadyError({
@ -325,13 +427,14 @@ ScopeRow ComputeScopeRow(const Scope &scope) {
lang(lng_passport_identity_title),
lang(lng_passport_identity_description),
});
case Scope::Type::Address:
if (scope.documents.empty()) {
return addReadyError({
lang(lng_passport_address),
lang(lng_passport_address_enter),
case Scope::Type::AddressDetails:
return addReadyError({
lang(lng_passport_address),
lang(lng_passport_address_enter),
});
} else if (scope.documents.size() == 1) {
case Scope::Type::Address:
Assert(!scope.documents.empty());
if (scope.documents.size() == 1) {
switch (scope.documents.front()->type) {
case Value::Type::BankStatement:
return addReadyError({

View File

@ -13,17 +13,18 @@ namespace Passport {
struct Scope {
enum class Type {
PersonalDetails,
Identity,
AddressDetails,
Address,
Phone,
Email,
};
Scope(Type type, not_null<const Value*> fields);
explicit Scope(Type type);
Type type;
not_null<const Value*> fields;
const Value *details = nullptr;
std::vector<not_null<const Value*>> documents;
bool selfieRequired = false;
};
struct ScopeRow {
@ -33,8 +34,8 @@ struct ScopeRow {
QString error;
};
std::vector<Scope> ComputeScopes(
not_null<const FormController*> controller);
bool ValidateForm(const Form &form);
std::vector<Scope> ComputeScopes(const Form &form);
QString ComputeScopeRowReadyString(const Scope &scope);
ScopeRow ComputeScopeRow(const Scope &scope);

View File

@ -99,6 +99,7 @@ EditDocumentScheme GetDocumentScheme(
return !CountryFormat(value).isEmpty();
});
// #TODO passport scheme
switch (type) {
case Scope::Type::Identity: {
auto result = Scheme();
@ -360,7 +361,7 @@ BoxContent *BoxPointer::operator->() const {
PanelController::PanelController(not_null<FormController*> form)
: _form(form)
, _scopes(ComputeScopes(_form)) {
, _scopes(ComputeScopes(_form->form())) {
_form->secretReadyEvents(
) | rpl::start_with_next([=] {
ensurePanelCreated();
@ -378,8 +379,6 @@ PanelController::PanelController(not_null<FormController*> form)
}) | rpl::start_with_next([=](not_null<const Value*> field) {
_verificationBoxes.erase(field);
}, lifetime());
_scopes = ComputeScopes(_form);
}
not_null<UserData*> PanelController::bot() const {
@ -397,12 +396,14 @@ void PanelController::fillRows(
bool ready,
bool error)> callback) {
if (_scopes.empty()) {
_scopes = ComputeScopes(_form);
_scopes = ComputeScopes(_form->form());
}
for (const auto &scope : _scopes) {
const auto row = ComputeScopeRow(scope);
const auto main = scope.fields;
if (!row.ready.isEmpty()) {
const auto main = scope.details
? not_null<const Value*>(scope.details)
: scope.documents[0];
if (main && !row.ready.isEmpty()) {
_submitErrors.erase(
ranges::remove(_submitErrors, main),
_submitErrors.end());
@ -546,9 +547,7 @@ void PanelController::uploadSpecialScan(
QByteArray &&content) {
Expects(_editScope != nullptr);
Expects(_editDocument != nullptr);
Expects(_editDocument->requiresSpecialScan(
type,
_editScope->selfieRequired));
Expects(_editDocument->requiresSpecialScan(type));
_form->uploadSpecialScan(_editDocument, type, std::move(content));
}
@ -556,9 +555,7 @@ void PanelController::uploadSpecialScan(
void PanelController::deleteSpecialScan(SpecialFile type) {
Expects(_editScope != nullptr);
Expects(_editDocument != nullptr);
Expects(_editDocument->requiresSpecialScan(
type,
_editScope->selfieRequired));
Expects(_editDocument->requiresSpecialScan(type));
_form->deleteSpecialScan(_editDocument, type);
}
@ -566,9 +563,7 @@ void PanelController::deleteSpecialScan(SpecialFile type) {
void PanelController::restoreSpecialScan(SpecialFile type) {
Expects(_editScope != nullptr);
Expects(_editDocument != nullptr);
Expects(_editDocument->requiresSpecialScan(
type,
_editScope->selfieRequired));
Expects(_editDocument->requiresSpecialScan(type));
_form->restoreSpecialScan(_editDocument, type);
}
@ -642,9 +637,15 @@ ScanInfo PanelController::collectScanInfo(const EditFile &file) const {
std::vector<ScopeError> PanelController::collectErrors(
not_null<const Value*> value) const {
auto result = std::vector<ScopeError>();
if (!value->error.isEmpty()) {
result.push_back({ FileKey(), value->error });
}
if (!value->scanMissingError.isEmpty()) {
result.push_back({ FileKey(), value->scanMissingError });
}
if (!value->translationMissingError.isEmpty()) {
result.push_back({ FileKey(), value->translationMissingError });
}
const auto addFileError = [&](const EditFile &file) {
if (!file.fields.error.isEmpty()) {
const auto key = FileKey{ file.fields.id, file.fields.dcId };
@ -654,6 +655,9 @@ std::vector<ScopeError> PanelController::collectErrors(
for (const auto &scan : value->scansInEdit) {
addFileError(scan);
}
for (const auto &scan : value->translationsInEdit) {
addFileError(scan);
}
for (const auto &[type, scan] : value->specialScansInEdit) {
addFileError(scan);
}
@ -671,13 +675,14 @@ auto PanelController::deleteValueLabel() const
if (hasValueDocument()) {
return Lang::Viewer(lng_passport_delete_document);
}
if (!hasValueFields()) {
} else if (!hasValueFields()) {
return base::none;
}
switch (_editScope->type) {
case Scope::Type::PersonalDetails:
case Scope::Type::Identity:
return Lang::Viewer(lng_passport_delete_details);
case Scope::Type::AddressDetails:
case Scope::Type::Address:
return Lang::Viewer(lng_passport_delete_address);
case Scope::Type::Email:
@ -700,27 +705,26 @@ bool PanelController::hasValueDocument() const {
}
bool PanelController::hasValueFields() const {
Expects(_editValue != nullptr);
return !_editValue->data.parsed.fields.empty();
return _editValue && !_editValue->data.parsed.fields.empty();
}
void PanelController::deleteValue() {
Expects(_editScope != nullptr);
Expects(hasValueDocument() || hasValueFields());
if (savingScope()) {
return;
}
const auto text = [&] {
switch (_editScope->type) {
case Scope::Type::PersonalDetails:
return lang(lng_passport_delete_details_sure);
case Scope::Type::Identity:
return lang(hasValueDocument()
? lng_passport_delete_document_sure
: lng_passport_delete_details_sure);
return lang(lng_passport_delete_document_sure);
case Scope::Type::AddressDetails:
return lang(lng_passport_delete_address_sure);
case Scope::Type::Address:
return lang(hasValueDocument()
? lng_passport_delete_document_sure
: lng_passport_delete_address_sure);
return lang(lng_passport_delete_document_sure);
case Scope::Type::Phone:
return lang(lng_passport_delete_phone_sure);
case Scope::Type::Email:
@ -745,7 +749,7 @@ void PanelController::deleteValue() {
}
void PanelController::deleteValueSure(bool withDetails) {
Expects(_editValue != nullptr);
Expects(!withDetails || _editValue != nullptr);
if (hasValueDocument()) {
_form->deleteValueEdit(_editDocument);
@ -830,21 +834,22 @@ int PanelController::findNonEmptyDocumentIndex(const Scope &scope) const {
const auto &documents = scope.documents;
const auto i = ranges::find_if(
documents,
[&](not_null<const Value*> document) {
return document->scansAreFilled(scope.selfieRequired);
[](not_null<const Value*> document) {
return document->scansAreFilled();
});
if (i != end(documents)) {
return (i - begin(documents));
}
// If we have a document where only selfie is not filled - return it.
const auto j = ranges::find_if(
documents,
[&](not_null<const Value*> document) {
return document->scansAreFilled(false);
});
if (j != end(documents)) {
return (j - begin(documents));
}
// #TODO passport half-full value
//const auto j = ranges::find_if(
// documents,
// [&](not_null<const Value*> document) {
// return document->scansAreFilled(false);
// });
//if (j != end(documents)) {
// return (j - begin(documents));
//}
return -1;
}
@ -936,8 +941,7 @@ void PanelController::editWithUpload(int index, int documentIndex) {
const auto &document = _scopes[index].documents[documentIndex];
const auto requiresSpecialScan = document->requiresSpecialScan(
SpecialFile::FrontSide,
false);
SpecialFile::FrontSide);
const auto allowMany = !requiresSpecialScan;
const auto widget = _panel->widget();
EditScans::ChooseScan(widget.get(), [=](QByteArray &&content) {
@ -981,21 +985,26 @@ void PanelController::editScope(int index, int documentIndex) {
&& documentIndex < _scopes[index].documents.size()));
_editScope = &_scopes[index];
_editValue = _editScope->fields;
_editValue = _editScope->details;
_editDocument = (documentIndex >= 0)
? _scopes[index].documents[documentIndex].get()
: nullptr;
Assert(_editValue || _editDocument);
_form->startValueEdit(_editValue);
if (_editValue) {
_form->startValueEdit(_editValue);
}
if (_editDocument) {
_form->startValueEdit(_editDocument);
}
auto content = [&]() -> object_ptr<Ui::RpWidget> {
// #TODO passport pass and display value->error
switch (_editScope->type) {
case Scope::Type::Identity:
case Scope::Type::Address: {
auto result = _editDocument
Assert(_editDocument != nullptr);
auto result = _editValue
? object_ptr<PanelEditDocument>(
_panel->widget(),
this,
@ -1007,6 +1016,7 @@ void PanelController::editScope(int index, int documentIndex) {
_editDocument->scanMissingError,
valueFiles(*_editDocument),
valueSpecialFiles(*_editDocument))
// #TODO passport document without details
: object_ptr<PanelEditDocument>(
_panel->widget(),
this,
@ -1018,8 +1028,23 @@ void PanelController::editScope(int index, int documentIndex) {
};
return std::move(result);
} break;
case Scope::Type::PersonalDetails:
case Scope::Type::AddressDetails: {
Assert(_editValue != nullptr);
auto result = object_ptr<PanelEditDocument>(
_panel->widget(),
this,
GetDocumentScheme(_editScope->type),
_editValue->data.parsedInEdit);
const auto weak = make_weak(result.data());
_panelHasUnsavedChanges = [=] {
return weak ? weak->hasUnsavedChanges() : false;
};
return std::move(result);
} break;
case Scope::Type::Phone:
case Scope::Type::Email: {
Assert(_editValue != nullptr);
const auto &parsed = _editValue->data.parsedInEdit;
const auto valueIt = parsed.fields.find("value");
const auto value = (valueIt == end(parsed.fields)
@ -1081,16 +1106,12 @@ void PanelController::processValueSaveFinished(
}
bool PanelController::uploadingScopeScan() const {
Expects(_editValue != nullptr);
return _form->uploadingScan(_editValue)
return (_editValue && _form->uploadingScan(_editValue))
|| (_editDocument && _form->uploadingScan(_editDocument));
}
bool PanelController::savingScope() const {
Expects(_editValue != nullptr);
return _form->savingValue(_editValue)
return (_editValue && _form->savingValue(_editValue))
|| (_editDocument && _form->savingValue(_editDocument));
}
@ -1173,7 +1194,7 @@ std::map<SpecialFile, ScanInfo> PanelController::valueSpecialFiles(
SpecialFile::Selfie
};
for (const auto type : types) {
if (value.requiresSpecialScan(type, _editScope->selfieRequired)) {
if (value.requiresSpecialScan(type)) {
const auto i = value.specialScansInEdit.find(type);
const auto j = result.emplace(
type,
@ -1190,7 +1211,9 @@ void PanelController::cancelValueEdit() {
Expects(_editScope != nullptr);
_editScopeBoxes.clear();
_form->cancelValueEdit(base::take(_editValue));
if (const auto value = base::take(_editValue)) {
_form->cancelValueEdit(value);
}
if (const auto document = base::take(_editDocument)) {
_form->cancelValueEdit(document);
}
@ -1199,7 +1222,6 @@ void PanelController::cancelValueEdit() {
void PanelController::saveScope(ValueMap &&data, ValueMap &&filesData) {
Expects(_panel != nullptr);
Expects(_editValue != nullptr);
if (uploadingScopeScan()) {
showToast(lang(lng_passport_wait_upload));
@ -1208,7 +1230,9 @@ void PanelController::saveScope(ValueMap &&data, ValueMap &&filesData) {
return;
}
_form->saveValueEdit(_editValue, std::move(data));
if (_editValue) {
_form->saveValueEdit(_editValue, std::move(data));
}
if (_editDocument) {
_form->saveValueEdit(_editDocument, std::move(filesData));
} else {
@ -1219,12 +1243,11 @@ void PanelController::saveScope(ValueMap &&data, ValueMap &&filesData) {
bool PanelController::editScopeChanged(
const ValueMap &data,
const ValueMap &filesData) const {
Expects(_editValue != nullptr);
if (_form->editValueChanged(_editValue, data)) {
if (_editValue && _form->editValueChanged(_editValue, data)) {
return true;
} else if (_editDocument
&& _form->editValueChanged(_editDocument, filesData)) {
return true;
} else if (_editDocument) {
return _form->editValueChanged(_editDocument, filesData);
}
return false;
}