Add translations support to passport.

This commit is contained in:
John Preston 2018-08-14 18:27:47 +03:00
parent 6558a32794
commit f76a2bc224
11 changed files with 998 additions and 792 deletions

View File

@ -1620,9 +1620,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_passport_choose_image" = "Choose scan image"; "lng_passport_choose_image" = "Choose scan image";
"lng_passport_delete_scan_undo" = "Undo"; "lng_passport_delete_scan_undo" = "Undo";
"lng_passport_scan_uploaded" = "Uploaded on {date}"; "lng_passport_scan_uploaded" = "Uploaded on {date}";
"lng_passport_first_name" = "Name"; "lng_passport_first_name" = "First name";
"lng_passport_middle_name" = "Middle name"; "lng_passport_middle_name" = "Middle name";
"lng_passport_last_name" = "Surname"; "lng_passport_last_name" = "Last name";
"lng_passport_birth_date" = "Date of birth"; "lng_passport_birth_date" = "Date of birth";
"lng_passport_gender" = "Gender"; "lng_passport_gender" = "Gender";
"lng_passport_gender_male" = "Male"; "lng_passport_gender_male" = "Male";
@ -1638,6 +1638,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_passport_city" = "City"; "lng_passport_city" = "City";
"lng_passport_state" = "State"; "lng_passport_state" = "State";
"lng_passport_postcode" = "Postcode"; "lng_passport_postcode" = "Postcode";
"lng_passport_translation" = "Translation";
"lng_passport_use_existing" = "USE {existing}"; "lng_passport_use_existing" = "USE {existing}";
"lng_passport_use_existing_phone" = "Use the same phone number as on Telegram."; "lng_passport_use_existing_phone" = "Use the same phone number as on Telegram.";
"lng_passport_new_phone" = "Or enter a new phone number"; "lng_passport_new_phone" = "Or enter a new phone number";

View File

@ -30,6 +30,7 @@ namespace Passport {
namespace { namespace {
constexpr auto kDocumentScansLimit = 20; constexpr auto kDocumentScansLimit = 20;
constexpr auto kTranslationScansLimit = 20;
constexpr auto kShortPollTimeout = TimeMs(3000); constexpr auto kShortPollTimeout = TimeMs(3000);
constexpr auto kRememberCredentialsDelay = TimeMs(1800 * 1000); constexpr auto kRememberCredentialsDelay = TimeMs(1800 * 1000);
@ -209,11 +210,11 @@ QString ValueCredentialsKey(Value::Type type) {
Unexpected("Type in ValueCredentialsKey."); Unexpected("Type in ValueCredentialsKey.");
} }
QString SpecialScanCredentialsKey(SpecialFile type) { QString SpecialScanCredentialsKey(FileType type) {
switch (type) { switch (type) {
case SpecialFile::FrontSide: return "front_side"; case FileType::FrontSide: return "front_side";
case SpecialFile::ReverseSide: return "reverse_side"; case FileType::ReverseSide: return "reverse_side";
case SpecialFile::Selfie: return "selfie"; case FileType::Selfie: return "selfie";
} }
Unexpected("Type in SpecialScanCredentialsKey."); Unexpected("Type in SpecialScanCredentialsKey.");
} }
@ -236,7 +237,12 @@ bool ValueChanged(not_null<const Value*> value, const ValueMap &data) {
}; };
auto filesCount = 0; auto filesCount = 0;
for (const auto &scan : value->scansInEdit) { for (const auto &scan : value->filesInEdit(FileType::Scan)) {
if (FileChanged(scan)) {
return true;
}
}
for (const auto &scan : value->filesInEdit(FileType::Translation)) {
if (FileChanged(scan)) { if (FileChanged(scan)) {
return true; return true;
} }
@ -277,9 +283,11 @@ FormRequest::FormRequest(
EditFile::EditFile( EditFile::EditFile(
not_null<const Value*> value, not_null<const Value*> value,
FileType type,
const File &fields, const File &fields,
std::unique_ptr<UploadScanData> &&uploadData) std::unique_ptr<UploadScanData> &&uploadData)
: value(value) : value(value)
, type(type)
, fields(std::move(fields)) , fields(std::move(fields))
, uploadData(std::move(uploadData)) , uploadData(std::move(uploadData))
, guard(std::make_shared<bool>(true)) { , guard(std::make_shared<bool>(true)) {
@ -326,17 +334,31 @@ RequestedValue::RequestedValue(Value::Type type) : type(type) {
Value::Value(Type type) : type(type) { Value::Value(Type type) : type(type) {
} }
bool Value::requiresSpecialScan(SpecialFile type) const { bool Value::requiresScan(FileType type) const {
if (type == FileType::Scan) {
return (this->type == Type::UtilityBill)
|| (this->type == Type::BankStatement)
|| (this->type == Type::RentalAgreement)
|| (this->type == Type::PassportRegistration)
|| (this->type == Type::TemporaryRegistration);
} else if (type == FileType::Translation) {
return translationRequired;
} else {
return requiresSpecialScan(type);
}
}
bool Value::requiresSpecialScan(FileType type) const {
switch (type) { switch (type) {
case SpecialFile::FrontSide: case FileType::FrontSide:
return (this->type == Type::Passport) return (this->type == Type::Passport)
|| (this->type == Type::DriverLicense) || (this->type == Type::DriverLicense)
|| (this->type == Type::IdentityCard) || (this->type == Type::IdentityCard)
|| (this->type == Type::InternalPassport); || (this->type == Type::InternalPassport);
case SpecialFile::ReverseSide: case FileType::ReverseSide:
return (this->type == Type::DriverLicense) return (this->type == Type::DriverLicense)
|| (this->type == Type::IdentityCard); || (this->type == Type::IdentityCard);
case SpecialFile::Selfie: case FileType::Selfie:
return selfieRequired; return selfieRequired;
} }
Unexpected("Special scan type in requiresSpecialScan."); Unexpected("Special scan type in requiresSpecialScan.");
@ -357,15 +379,15 @@ void Value::fillDataFrom(Value &&other) {
} }
bool Value::scansAreFilled() const { bool Value::scansAreFilled() const {
if (!requiresSpecialScan(SpecialFile::FrontSide) && scans.empty()) { if (requiresScan(FileType::Translation) && _translations.empty()) {
return false; return false;
} else if (translationRequired && translations.empty()) { } else if (requiresScan(FileType::Scan) && _scans.empty()) {
return false; return false;
} }
const auto types = { const auto types = {
SpecialFile::FrontSide, FileType::FrontSide,
SpecialFile::ReverseSide, FileType::ReverseSide,
SpecialFile::Selfie FileType::Selfie
}; };
for (const auto type : types) { for (const auto type : types) {
if (requiresSpecialScan(type) if (requiresSpecialScan(type)
@ -376,6 +398,163 @@ bool Value::scansAreFilled() const {
return true; return true;
} }
void Value::saveInEdit() {
const auto saveList = [&](FileType type) {
filesInEdit(type) = ranges::view::all(
files(type)
) | ranges::view::transform([=](const File &file) {
return EditFile(this, type, file, nullptr);
}) | ranges::to_vector;
};
saveList(FileType::Scan);
saveList(FileType::Translation);
specialScansInEdit.clear();
for (const auto &[type, scan] : specialScans) {
specialScansInEdit.emplace(type, EditFile(
this,
type,
scan,
nullptr));
}
data.parsedInEdit = data.parsed;
}
void Value::clearEditData() {
filesInEdit(FileType::Scan).clear();
filesInEdit(FileType::Translation).clear();
specialScansInEdit.clear();
data.encryptedSecretInEdit.clear();
data.hashInEdit.clear();
data.parsedInEdit = ValueMap();
}
bool Value::uploadingScan() const {
const auto uploading = [](const EditFile &file) {
return file.uploadData
&& file.uploadData->fullId
&& !file.deleted;
};
const auto uploadingInList = [&](FileType type) {
const auto &list = filesInEdit(type);
return ranges::find_if(list, uploading) != end(list);
};
if (uploadingInList(FileType::Scan)
|| uploadingInList(FileType::Translation)) {
return true;
}
if (ranges::find_if(specialScansInEdit, [&](const auto &pair) {
return uploading(pair.second);
}) != end(specialScansInEdit)) {
return true;
}
return false;
}
bool Value::saving() const {
return (saveRequestId != 0)
|| (verification.requestId != 0)
|| (verification.codeLength != 0)
|| uploadingScan();
}
std::vector<File> &Value::files(FileType type) {
switch (type) {
case FileType::Scan: return _scans;
case FileType::Translation: return _translations;
}
Unexpected("Type in Value::files().");
}
const std::vector<File> &Value::files(FileType type) const {
switch (type) {
case FileType::Scan: return _scans;
case FileType::Translation: return _translations;
}
Unexpected("Type in Value::files() const.");
}
QString &Value::fileMissingError(FileType type) {
switch (type) {
case FileType::Scan: return _scanMissingError;
case FileType::Translation: return _translationMissingError;
}
Unexpected("Type in Value::fileMissingError().");
}
const QString &Value::fileMissingError(FileType type) const {
switch (type) {
case FileType::Scan: return _scanMissingError;
case FileType::Translation: return _translationMissingError;
}
Unexpected("Type in Value::fileMissingError() const.");
}
std::vector<EditFile> &Value::filesInEdit(FileType type) {
switch (type) {
case FileType::Scan: return _scansInEdit;
case FileType::Translation: return _translationsInEdit;
}
Unexpected("Type in Value::filesInEdit().");
}
const std::vector<EditFile> &Value::filesInEdit(FileType type) const {
switch (type) {
case FileType::Scan: return _scansInEdit;
case FileType::Translation: return _translationsInEdit;
}
Unexpected("Type in Value::filesInEdit() const.");
}
EditFile &Value::fileInEdit(FileType type, base::optional<int> fileIndex) {
switch (type) {
case FileType::Scan:
case FileType::Translation: {
auto &list = filesInEdit(type);
Assert(fileIndex.has_value());
Assert(*fileIndex >= 0 && *fileIndex < list.size());
return list[*fileIndex];
} break;
}
const auto i = specialScansInEdit.find(type);
Assert(!fileIndex.has_value());
Assert(i != end(specialScansInEdit));
return i->second;
}
const EditFile &Value::fileInEdit(
FileType type,
base::optional<int> fileIndex) const {
switch (type) {
case FileType::Scan:
case FileType::Translation: {
auto &list = filesInEdit(type);
Assert(fileIndex.has_value());
Assert(*fileIndex >= 0 && *fileIndex < list.size());
return list[*fileIndex];
} break;
}
const auto i = specialScansInEdit.find(type);
Assert(!fileIndex.has_value());
Assert(i != end(specialScansInEdit));
return i->second;
}
std::vector<EditFile> Value::takeAllFilesInEdit() {
auto result = base::take(filesInEdit(FileType::Scan));
auto &translation = filesInEdit(FileType::Translation);
auto &special = specialScansInEdit;
result.reserve(result.size() + translation.size() + special.size());
for (auto &scan : base::take(translation)) {
result.push_back(std::move(scan));
}
for (auto &[type, scan] : base::take(special)) {
result.push_back(std::move(scan));
}
return result;
}
FormController::FormController( FormController::FormController(
not_null<Window::Controller*> controller, not_null<Window::Controller*> controller,
const FormRequest &request) const FormRequest &request)
@ -417,12 +596,20 @@ auto FormController::prepareFinalData() -> FinalData {
{ "secret", value->data.secret } { "secret", value->data.secret }
})); }));
} }
if (!value->scans.empty()) { const auto addList = [&](
auto files = QJsonArray(); const QString &key,
for (const auto &scan : value->scans) { const std::vector<File> &list) {
files.append(GetJSONFromFile(scan)); if (!list.empty()) {
auto files = QJsonArray();
for (const auto &scan : list) {
files.append(GetJSONFromFile(scan));
}
object.insert(key, files);
} }
object.insert("files", files); };
addList("files", value->files(FileType::Scan));
if (value->translationRequired) {
addList("translation", value->files(FileType::Translation));
} }
for (const auto &[type, scan] : value->specialScans) { for (const auto &[type, scan] : value->specialScans) {
if (value->requiresSpecialScan(type)) { if (value->requiresSpecialScan(type)) {
@ -894,26 +1081,21 @@ void FormController::fillErrors() {
LOG(("API Error: Value not found for error type.")); LOG(("API Error: Value not found for error type."));
return nullptr; return nullptr;
}; };
using List = std::vector<File>; const auto scan = [&](
const auto findScan = [&](List &list, bytes::const_span hash) -> File* { Value &value,
FileType type,
bytes::const_span hash) -> File* {
auto &list = value.files(type);
const auto i = ranges::find_if(list, [&](const File &scan) { const auto i = ranges::find_if(list, [&](const File &scan) {
return !bytes::compare(hash, scan.hash); return !bytes::compare(hash, scan.hash);
}); });
if (i != end(list)) { if (i != end(list)) {
return &*i; return &*i;
} }
return nullptr;
};
const auto scan = [&](Value &value, bytes::const_span hash) -> File* {
if (const auto translation = findScan(value.translations, hash)) {
return translation;
} else if (const auto scan = findScan(value.scans, hash)) {
return scan;
}
LOG(("API Error: File not found for error value.")); LOG(("API Error: File not found for error value."));
return nullptr; return nullptr;
}; };
const auto setSpecialScanError = [&](SpecialFile type, auto &&data) { const auto setSpecialScanError = [&](FileType type, auto &&data) {
if (const auto value = find(data.vtype)) { if (const auto value = find(data.vtype)) {
if (value->requiresSpecialScan(type)) { if (value->requiresSpecialScan(type)) {
const auto i = value->specialScans.find(type); const auto i = value->specialScans.find(type);
@ -945,37 +1127,40 @@ void FormController::fillErrors() {
}, [&](const MTPDsecureValueErrorFile &data) { }, [&](const MTPDsecureValueErrorFile &data) {
const auto hash = bytes::make_span(data.vfile_hash.v); const auto hash = bytes::make_span(data.vfile_hash.v);
if (const auto value = find(data.vtype)) { if (const auto value = find(data.vtype)) {
if (const auto file = scan(*value, hash)) { if (const auto file = scan(*value, FileType::Scan, hash)) {
file->error = qs(data.vtext); if (value->requiresScan(FileType::Scan)) {
file->error = qs(data.vtext);
}
} }
} }
}, [&](const MTPDsecureValueErrorFiles &data) { }, [&](const MTPDsecureValueErrorFiles &data) {
if (const auto value = find(data.vtype)) { if (const auto value = find(data.vtype)) {
if (CanRequireScans(value->type)) { if (value->requiresScan(FileType::Scan)) {
value->scanMissingError = qs(data.vtext); value->fileMissingError(FileType::Scan)
= qs(data.vtext);
} }
} }
}, [&](const MTPDsecureValueErrorTranslationFile &data) { }, [&](const MTPDsecureValueErrorTranslationFile &data) {
const auto hash = bytes::make_span(data.vfile_hash.v); const auto hash = bytes::make_span(data.vfile_hash.v);
if (const auto value = find(data.vtype)) { if (const auto value = find(data.vtype)) {
if (value->translationRequired) { const auto file = scan(*value, FileType::Translation, hash);
if (const auto file = scan(*value, hash)) { if (file && value->requiresScan(FileType::Translation)) {
file->error = qs(data.vtext); file->error = qs(data.vtext);
}
} }
} }
}, [&](const MTPDsecureValueErrorTranslationFiles &data) { }, [&](const MTPDsecureValueErrorTranslationFiles &data) {
if (const auto value = find(data.vtype)) { if (const auto value = find(data.vtype)) {
if (value->translationRequired) { if (value->requiresScan(FileType::Translation)) {
value->translationMissingError = qs(data.vtext); value->fileMissingError(FileType::Translation)
= qs(data.vtext);
} }
} }
}, [&](const MTPDsecureValueErrorFrontSide &data) { }, [&](const MTPDsecureValueErrorFrontSide &data) {
setSpecialScanError(SpecialFile::FrontSide, data); setSpecialScanError(FileType::FrontSide, data);
}, [&](const MTPDsecureValueErrorReverseSide &data) { }, [&](const MTPDsecureValueErrorReverseSide &data) {
setSpecialScanError(SpecialFile::ReverseSide, data); setSpecialScanError(FileType::ReverseSide, data);
}, [&](const MTPDsecureValueErrorSelfie &data) { }, [&](const MTPDsecureValueErrorSelfie &data) {
setSpecialScanError(SpecialFile::Selfie, data); setSpecialScanError(FileType::Selfie, data);
}); });
} }
} }
@ -1027,13 +1212,18 @@ bool FormController::validateValueSecrets(Value &value) const {
} }
return true; return true;
}; };
for (auto &scan : value.scans) { for (auto &scan : value.files(FileType::Scan)) {
if (!validateFileSecret(scan)) { if (!validateFileSecret(scan)) {
return false; return false;
} }
} }
for (auto &[type, file] : value.specialScans) { for (auto &scan : value.files(FileType::Translation)) {
if (!validateFileSecret(file)) { if (!validateFileSecret(scan)) {
return false;
}
}
for (auto &[type, scan] : value.specialScans) {
if (!validateFileSecret(scan)) {
return false; return false;
} }
} }
@ -1054,73 +1244,51 @@ const PasswordSettings &FormController::passwordSettings() const {
void FormController::uploadScan( void FormController::uploadScan(
not_null<const Value*> value, not_null<const Value*> value,
FileType type,
QByteArray &&content) { QByteArray &&content) {
if (!canAddScan(value)) { if (!canAddScan(value, type)) {
_view->showToast(lang(lng_passport_scans_limit_reached)); _view->showToast(lang(lng_passport_scans_limit_reached));
return; return;
} }
const auto nonconst = findValue(value); const auto nonconst = findValue(value);
auto scanIndex = int(nonconst->scansInEdit.size()); const auto fileIndex = [&]() -> base::optional<int> {
nonconst->scansInEdit.emplace_back( auto scanInEdit = EditFile{ nonconst, type, File(), nullptr };
nonconst, if (type == FileType::Scan || type == FileType::Translation) {
File(), auto &list = nonconst->filesInEdit(type);
nullptr); auto scanIndex = int(list.size());
auto &scan = nonconst->scansInEdit.back(); list.push_back(std::move(scanInEdit));
return list.size() - 1;
}
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;
}
return base::none;
}();
auto &scan = nonconst->fileInEdit(type, fileIndex);
encryptFile(scan, std::move(content), [=](UploadScanData &&result) { encryptFile(scan, std::move(content), [=](UploadScanData &&result) {
Expects(scanIndex >= 0 && scanIndex < nonconst->scansInEdit.size());
uploadEncryptedFile( uploadEncryptedFile(
nonconst->scansInEdit[scanIndex], nonconst->fileInEdit(type, fileIndex),
std::move(result)); std::move(result));
}); });
} }
void FormController::deleteScan( void FormController::deleteScan(
not_null<const Value*> value, not_null<const Value*> value,
int scanIndex) { FileType type,
scanDeleteRestore(value, scanIndex, true); base::optional<int> fileIndex) {
scanDeleteRestore(value, type, fileIndex, true);
} }
void FormController::restoreScan( void FormController::restoreScan(
not_null<const Value*> value, not_null<const Value*> value,
int scanIndex) { FileType type,
scanDeleteRestore(value, scanIndex, false); base::optional<int> fileIndex) {
} scanDeleteRestore(value, type, fileIndex, false);
void FormController::uploadSpecialScan(
not_null<const Value*> value,
SpecialFile type,
QByteArray &&content) {
const auto nonconst = findValue(value);
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(
i->second,
std::move(result));
});
}
void FormController::deleteSpecialScan(
not_null<const Value*> value,
SpecialFile type) {
specialScanDeleteRestore(value, type, true);
}
void FormController::restoreSpecialScan(
not_null<const Value*> value,
SpecialFile type) {
specialScanDeleteRestore(value, type, false);
} }
void FormController::prepareFile( void FormController::prepareFile(
@ -1173,14 +1341,13 @@ void FormController::encryptFile(
void FormController::scanDeleteRestore( void FormController::scanDeleteRestore(
not_null<const Value*> value, not_null<const Value*> value,
int scanIndex, FileType type,
base::optional<int> fileIndex,
bool deleted) { bool deleted) {
Expects(scanIndex >= 0 && scanIndex < value->scansInEdit.size());
const auto nonconst = findValue(value); const auto nonconst = findValue(value);
auto &scan = nonconst->scansInEdit[scanIndex]; auto &scan = nonconst->fileInEdit(type, fileIndex);
if (scan.deleted && !deleted) { if (scan.deleted && !deleted) {
if (!canAddScan(value)) { if (!canAddScan(value, type)) {
_view->showToast(lang(lng_passport_scans_limit_reached)); _view->showToast(lang(lng_passport_scans_limit_reached));
return; return;
} }
@ -1189,23 +1356,21 @@ void FormController::scanDeleteRestore(
_scanUpdated.fire(&scan); _scanUpdated.fire(&scan);
} }
void FormController::specialScanDeleteRestore( bool FormController::canAddScan(
not_null<const Value*> value, not_null<const Value*> value,
SpecialFile type, FileType type) const {
bool deleted) { const auto limit = (type == FileType::Scan)
const auto nonconst = findValue(value); ? kDocumentScansLimit
const auto i = nonconst->specialScansInEdit.find(type); : (type == FileType::Translation)
Assert(i != nonconst->specialScansInEdit.end()); ? kTranslationScansLimit
auto &scan = i->second; : -1;
scan.deleted = deleted; if (limit < 0) {
_scanUpdated.fire(&scan); return true;
} }
bool FormController::canAddScan(not_null<const Value*> value) const {
const auto scansCount = ranges::count_if( const auto scansCount = ranges::count_if(
value->scansInEdit, value->filesInEdit(type),
[](const EditFile &scan) { return !scan.deleted; }); [](const EditFile &scan) { return !scan.deleted; });
return (scansCount < kDocumentScansLimit); return (scansCount < limit);
} }
void FormController::subscribeToUploader() { void FormController::subscribeToUploader() {
@ -1411,14 +1576,14 @@ not_null<Value*> FormController::findValue(not_null<const Value*> value) {
void FormController::startValueEdit(not_null<const Value*> value) { void FormController::startValueEdit(not_null<const Value*> value) {
const auto nonconst = findValue(value); const auto nonconst = findValue(value);
++nonconst->editScreens; ++nonconst->editScreens;
if (savingValue(nonconst)) { if (nonconst->saving()) {
return; return;
} }
for (auto &scan : nonconst->scans) { for (auto &scan : nonconst->files(FileType::Scan)) {
loadFile(scan); loadFile(scan);
} }
if (nonconst->translationRequired) { if (nonconst->translationRequired) {
for (auto &scan : nonconst->translations) { for (auto &scan : nonconst->files(FileType::Translation)) {
loadFile(scan); loadFile(scan);
} }
} }
@ -1427,27 +1592,7 @@ void FormController::startValueEdit(not_null<const Value*> value) {
loadFile(scan); loadFile(scan);
} }
} }
nonconst->scansInEdit = ranges::view::all( nonconst->saveInEdit();
nonconst->scans
) | ranges::view::transform([=](const File &file) {
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(
nonconst,
scan,
nullptr));
}
nonconst->data.parsedInEdit = nonconst->data.parsed;
} }
void FormController::loadFile(File &file) { void FormController::loadFile(File &file) {
@ -1531,41 +1676,6 @@ void FormController::fileLoadFail(FileKey key) {
} }
} }
bool FormController::savingValue(not_null<const Value*> value) const {
return (value->saveRequestId != 0)
|| (value->verification.requestId != 0)
|| (value->verification.codeLength != 0)
|| uploadingScan(value);
}
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 (uploading(scan)) {
return true;
}
}
for (const auto &[type, scan] : value->specialScansInEdit) {
if (uploading(scan)) {
return true;
}
}
return false;
}
void FormController::cancelValueEdit(not_null<const Value*> value) { void FormController::cancelValueEdit(not_null<const Value*> value) {
Expects(value->editScreens > 0); Expects(value->editScreens > 0);
@ -1575,7 +1685,7 @@ void FormController::cancelValueEdit(not_null<const Value*> value) {
} }
void FormController::valueEditFailed(not_null<Value*> value) { void FormController::valueEditFailed(not_null<Value*> value) {
Expects(!savingValue(value)); Expects(!value->saving());
if (value->editScreens == 0) { if (value->editScreens == 0) {
clearValueEdit(value); clearValueEdit(value);
@ -1583,20 +1693,16 @@ void FormController::valueEditFailed(not_null<Value*> value) {
} }
void FormController::clearValueEdit(not_null<Value*> value) { void FormController::clearValueEdit(not_null<Value*> value) {
if (savingValue(value)) { if (value->saving()) {
return; return;
} }
value->scansInEdit.clear(); value->clearEditData();
value->specialScansInEdit.clear();
value->data.encryptedSecretInEdit.clear();
value->data.hashInEdit.clear();
value->data.parsedInEdit = ValueMap();
} }
void FormController::cancelValueVerification(not_null<const Value*> value) { void FormController::cancelValueVerification(not_null<const Value*> value) {
const auto nonconst = findValue(value); const auto nonconst = findValue(value);
clearValueVerification(nonconst); clearValueVerification(nonconst);
if (!savingValue(nonconst)) { if (!nonconst->saving()) {
valueEditFailed(nonconst); valueEditFailed(nonconst);
} }
} }
@ -1619,7 +1725,7 @@ bool FormController::isEncryptedValue(Value::Type type) const {
void FormController::saveValueEdit( void FormController::saveValueEdit(
not_null<const Value*> value, not_null<const Value*> value,
ValueMap &&data) { ValueMap &&data) {
if (savingValue(value) || _submitRequestId) { if (value->saving() || _submitRequestId) {
return; return;
} }
@ -1630,11 +1736,7 @@ void FormController::saveValueEdit(
if (!ValueChanged(nonconst, data)) { if (!ValueChanged(nonconst, data)) {
nonconst->saveRequestId = -1; nonconst->saveRequestId = -1;
crl::on_main(this, [=] { crl::on_main(this, [=] {
base::take(nonconst->scansInEdit); nonconst->clearEditData();
base::take(nonconst->specialScansInEdit);
base::take(nonconst->data.encryptedSecretInEdit);
base::take(nonconst->data.hashInEdit);
base::take(nonconst->data.parsedInEdit);
nonconst->saveRequestId = 0; nonconst->saveRequestId = 0;
_valueSaveFinished.fire_copy(nonconst); _valueSaveFinished.fire_copy(nonconst);
}); });
@ -1650,7 +1752,7 @@ void FormController::saveValueEdit(
} }
void FormController::deleteValueEdit(not_null<const Value*> value) { void FormController::deleteValueEdit(not_null<const Value*> value) {
if (savingValue(value) || _submitRequestId) { if (value->saving() || _submitRequestId) {
return; return;
} }
@ -1689,24 +1791,21 @@ void FormController::saveEncryptedValue(not_null<Value*> value) {
MTP_long(file.fields.id), MTP_long(file.fields.id),
MTP_long(file.fields.accessHash)); MTP_long(file.fields.accessHash));
}; };
const auto wrapList = [&](not_null<const Value*> value, FileType type) {
auto files = QVector<MTPInputSecureFile>(); const auto &list = value->filesInEdit(type);
files.reserve(value->scansInEdit.size()); auto result = QVector<MTPInputSecureFile>();
for (const auto &scan : value->scansInEdit) { result.reserve(list.size());
if (scan.deleted) { for (const auto &scan : value->filesInEdit(type)) {
continue; if (scan.deleted) {
continue;
}
result.push_back(wrapFile(scan));
} }
files.push_back(wrapFile(scan)); return result;
} };
auto translations = QVector<MTPInputSecureFile>(); const auto files = wrapList(value, FileType::Scan);
translations.reserve(value->translationsInEdit.size()); const auto translations = wrapList(value, FileType::Translation);
for (const auto &scan : value->translationsInEdit) {
if (scan.deleted) {
continue;
}
translations.push_back(wrapFile(scan));
}
if (value->data.secret.empty()) { if (value->data.secret.empty()) {
value->data.secret = GenerateSecretBytes(); value->data.secret = GenerateSecretBytes();
@ -1720,37 +1819,37 @@ void FormController::saveEncryptedValue(not_null<Value*> value) {
_secret, _secret,
value->data.hashInEdit); value->data.hashInEdit);
const auto hasSpecialFile = [&](SpecialFile type) { const auto hasSpecialFile = [&](FileType type) {
const auto i = value->specialScansInEdit.find(type); const auto i = value->specialScansInEdit.find(type);
return (i != end(value->specialScansInEdit) && !i->second.deleted); return (i != end(value->specialScansInEdit) && !i->second.deleted);
}; };
const auto specialFile = [&](SpecialFile type) { const auto specialFile = [&](FileType type) {
const auto i = value->specialScansInEdit.find(type); const auto i = value->specialScansInEdit.find(type);
return (i != end(value->specialScansInEdit) && !i->second.deleted) return (i != end(value->specialScansInEdit) && !i->second.deleted)
? wrapFile(i->second) ? wrapFile(i->second)
: MTPInputSecureFile(); : MTPInputSecureFile();
}; };
const auto frontSide = specialFile(SpecialFile::FrontSide); const auto frontSide = specialFile(FileType::FrontSide);
const auto reverseSide = specialFile(SpecialFile::ReverseSide); const auto reverseSide = specialFile(FileType::ReverseSide);
const auto selfie = specialFile(SpecialFile::Selfie); const auto selfie = specialFile(FileType::Selfie);
const auto type = ConvertType(value->type); const auto type = ConvertType(value->type);
const auto flags = (value->data.parsedInEdit.fields.empty() const auto flags = (value->data.parsedInEdit.fields.empty()
? MTPDinputSecureValue::Flag(0) ? MTPDinputSecureValue::Flag(0)
: MTPDinputSecureValue::Flag::f_data) : MTPDinputSecureValue::Flag::f_data)
| (hasSpecialFile(SpecialFile::FrontSide) | (hasSpecialFile(FileType::FrontSide)
? MTPDinputSecureValue::Flag::f_front_side ? MTPDinputSecureValue::Flag::f_front_side
: MTPDinputSecureValue::Flag(0)) : MTPDinputSecureValue::Flag(0))
| (hasSpecialFile(SpecialFile::ReverseSide) | (hasSpecialFile(FileType::ReverseSide)
? MTPDinputSecureValue::Flag::f_reverse_side ? MTPDinputSecureValue::Flag::f_reverse_side
: MTPDinputSecureValue::Flag(0)) : MTPDinputSecureValue::Flag(0))
| (hasSpecialFile(SpecialFile::Selfie) | (hasSpecialFile(FileType::Selfie)
? MTPDinputSecureValue::Flag::f_selfie ? MTPDinputSecureValue::Flag::f_selfie
: MTPDinputSecureValue::Flag(0)) : MTPDinputSecureValue::Flag(0))
| (value->translationsInEdit.empty() | (translations.empty()
? MTPDinputSecureValue::Flag(0) ? MTPDinputSecureValue::Flag(0)
: MTPDinputSecureValue::Flag::f_translation) : MTPDinputSecureValue::Flag::f_translation)
| (value->scansInEdit.empty() | (files.empty()
? MTPDinputSecureValue::Flag(0) ? MTPDinputSecureValue::Flag(0)
: MTPDinputSecureValue::Flag::f_files); : MTPDinputSecureValue::Flag::f_files);
Assert(flags != MTPDinputSecureValue::Flags(0)); Assert(flags != MTPDinputSecureValue::Flags(0));
@ -1809,10 +1908,7 @@ void FormController::sendSaveRequest(
data, data,
MTP_long(_secretId) MTP_long(_secretId)
)).done([=](const MTPSecureValue &result) { )).done([=](const MTPSecureValue &result) {
auto scansInEdit = base::take(value->scansInEdit); auto scansInEdit = value->takeAllFilesInEdit();
for (auto &[type, scan] : base::take(value->specialScansInEdit)) {
scansInEdit.push_back(std::move(scan));
}
auto refreshed = parseValue(result, scansInEdit); auto refreshed = parseValue(result, scansInEdit);
decryptValue(refreshed); decryptValue(refreshed);
@ -2157,23 +2253,28 @@ auto FormController::parseValue(
result.data.encryptedSecret = bytes::make_vector(fields.vsecret.v); result.data.encryptedSecret = bytes::make_vector(fields.vsecret.v);
} }
if (data.has_files()) { if (data.has_files()) {
result.scans = parseFiles(data.vfiles.v, editData); result.files(FileType::Scan) = parseFiles(data.vfiles.v, editData);
}
if (data.has_translation()) {
result.files(FileType::Translation) = parseFiles(
data.vtranslation.v,
editData);
} }
const auto parseSpecialScan = [&]( const auto parseSpecialScan = [&](
SpecialFile type, FileType type,
const MTPSecureFile &file) { const MTPSecureFile &file) {
if (auto parsed = parseFile(file, editData)) { if (auto parsed = parseFile(file, editData)) {
result.specialScans.emplace(type, std::move(*parsed)); result.specialScans.emplace(type, std::move(*parsed));
} }
}; };
if (data.has_front_side()) { if (data.has_front_side()) {
parseSpecialScan(SpecialFile::FrontSide, data.vfront_side); parseSpecialScan(FileType::FrontSide, data.vfront_side);
} }
if (data.has_reverse_side()) { if (data.has_reverse_side()) {
parseSpecialScan(SpecialFile::ReverseSide, data.vreverse_side); parseSpecialScan(FileType::ReverseSide, data.vreverse_side);
} }
if (data.has_selfie()) { if (data.has_selfie()) {
parseSpecialScan(SpecialFile::Selfie, data.vselfie); parseSpecialScan(FileType::Selfie, data.vselfie);
} }
if (data.has_plain_data()) { if (data.has_plain_data()) {
switch (data.vplain_data.type()) { switch (data.vplain_data.type()) {
@ -2190,18 +2291,25 @@ auto FormController::parseValue(
return result; return result;
} }
auto FormController::findEditFile(const FullMsgId &fullId) -> EditFile* { template <typename Condition>
const auto found = [&](const EditFile &file) { EditFile *FormController::findEditFileByCondition(Condition &&condition) {
return (file.uploadData && file.uploadData->fullId == fullId); for (auto &pair : _form.values) {
}; auto &value = pair.second;
for (auto &[type, value] : _form.values) { const auto foundInList = [&](FileType type) -> EditFile* {
for (auto &scan : value.scansInEdit) { for (auto &scan : value.filesInEdit(type)) {
if (found(scan)) { if (condition(scan)) {
return &scan; return &scan;
}
} }
return nullptr;
};
if (const auto result = foundInList(FileType::Scan)) {
return result;
} else if (const auto other = foundInList(FileType::Translation)) {
return other;
} }
for (auto &[special, scan] : value.specialScansInEdit) { for (auto &[special, scan] : value.specialScansInEdit) {
if (found(scan)) { if (condition(scan)) {
return &scan; return &scan;
} }
} }
@ -2209,23 +2317,16 @@ auto FormController::findEditFile(const FullMsgId &fullId) -> EditFile* {
return nullptr; return nullptr;
} }
auto FormController::findEditFile(const FileKey &key) -> EditFile* { EditFile *FormController::findEditFile(const FullMsgId &fullId) {
const auto found = [&](const EditFile &file) { return findEditFileByCondition([&](const EditFile &file) {
return (file.uploadData && file.uploadData->fullId == fullId);
});
}
EditFile *FormController::findEditFile(const FileKey &key) {
return findEditFileByCondition([&](const EditFile &file) {
return (file.fields.dcId == key.dcId && file.fields.id == key.id); return (file.fields.dcId == key.dcId && file.fields.id == key.id);
}; });
for (auto &[type, value] : _form.values) {
for (auto &scan : value.scansInEdit) {
if (found(scan)) {
return &scan;
}
}
for (auto &[special, scan] : value.specialScansInEdit) {
if (found(scan)) {
return &scan;
}
}
}
return nullptr;
} }
auto FormController::findFile(const FileKey &key) auto FormController::findFile(const FileKey &key)
@ -2233,11 +2334,20 @@ auto FormController::findFile(const FileKey &key)
const auto found = [&](const File &file) { const auto found = [&](const File &file) {
return (file.dcId == key.dcId) && (file.id == key.id); return (file.dcId == key.dcId) && (file.id == key.id);
}; };
for (auto &[type, value] : _form.values) { for (auto &pair : _form.values) {
for (auto &scan : value.scans) { auto &value = pair.second;
if (found(scan)) { const auto foundInList = [&](FileType type) -> File* {
return { &value, &scan }; for (auto &scan : value.files(type)) {
if (found(scan)) {
return &scan;
}
} }
return nullptr;
};
if (const auto result = foundInList(FileType::Scan)) {
return { &value, result };
} else if (const auto other = foundInList(FileType::Translation)) {
return { &value, other };
} }
for (auto &[special, scan] : value.specialScans) { for (auto &[special, scan] : value.specialScans) {
if (found(scan)) { if (found(scan)) {

View File

@ -81,6 +81,14 @@ private:
struct Value; struct Value;
enum class FileType {
Scan,
Translation,
FrontSide,
ReverseSide,
Selfie,
};
struct File { struct File {
uint64 id = 0; uint64 id = 0;
uint64 accessHash = 0; uint64 accessHash = 0;
@ -99,10 +107,12 @@ struct File {
struct EditFile { struct EditFile {
EditFile( EditFile(
not_null<const Value*> value, not_null<const Value*> value,
FileType type,
const File &fields, const File &fields,
std::unique_ptr<UploadScanData> &&uploadData); std::unique_ptr<UploadScanData> &&uploadData);
not_null<const Value*> value; not_null<const Value*> value;
FileType type;
File fields; File fields;
UploadScanDataPointer uploadData; UploadScanDataPointer uploadData;
std::shared_ptr<bool> guard; std::shared_ptr<bool> guard;
@ -141,12 +151,6 @@ struct Verification {
struct Form; struct Form;
enum class SpecialFile {
FrontSide,
ReverseSide,
Selfie,
};
struct Value { struct Value {
enum class Type { enum class Type {
PersonalDetails, PersonalDetails,
@ -172,20 +176,32 @@ struct Value {
// It should be preserved through re-parsing (for example when saving). // It should be preserved through re-parsing (for example when saving).
// So we hide "operator=(Value&&)" in private and instead provide this. // So we hide "operator=(Value&&)" in private and instead provide this.
void fillDataFrom(Value &&other); void fillDataFrom(Value &&other);
bool requiresSpecialScan(SpecialFile type) const; bool requiresSpecialScan(FileType type) const;
bool requiresScan(FileType type) const;
bool scansAreFilled() const; bool scansAreFilled() const;
void saveInEdit();
void clearEditData();
bool uploadingScan() const;
bool saving() const;
std::vector<File> &files(FileType type);
const std::vector<File> &files(FileType type) const;
QString &fileMissingError(FileType type);
const QString &fileMissingError(FileType type) const;
std::vector<EditFile> &filesInEdit(FileType type);
const std::vector<EditFile> &filesInEdit(FileType type) const;
EditFile &fileInEdit(FileType type, base::optional<int> fileIndex);
const EditFile &fileInEdit(
FileType type,
base::optional<int> fileIndex) const;
std::vector<EditFile> takeAllFilesInEdit();
Type type; Type type;
ValueData data; ValueData data;
std::vector<File> scans; std::map<FileType, File> specialScans;
std::vector<File> translations;
std::map<SpecialFile, File> specialScans;
QString error; QString error;
QString scanMissingError; std::map<FileType, EditFile> specialScansInEdit;
QString translationMissingError;
std::vector<EditFile> scansInEdit;
std::vector<EditFile> translationsInEdit;
std::map<SpecialFile, EditFile> specialScansInEdit;
Verification verification; Verification verification;
bytes::vector submitHash; bytes::vector submitHash;
@ -199,6 +215,13 @@ struct Value {
private: private:
Value &operator=(Value &&other) = default; Value &operator=(Value &&other) = default;
std::vector<File> _scans;
std::vector<File> _translations;
std::vector<EditFile> _scansInEdit;
std::vector<EditFile> _translationsInEdit;
QString _scanMissingError;
QString _translationMissingError;
}; };
bool ValueChanged(not_null<const Value*> value, const ValueMap &data); bool ValueChanged(not_null<const Value*> value, const ValueMap &data);
@ -299,20 +322,19 @@ public:
void reloadAndSubmitPassword(const QByteArray &password); void reloadAndSubmitPassword(const QByteArray &password);
void cancelPassword(); void cancelPassword();
bool canAddScan(not_null<const Value*> value) const; bool canAddScan(not_null<const Value*> value, FileType type) const;
void uploadScan(not_null<const Value*> value, QByteArray &&content); void uploadScan(
void deleteScan(not_null<const Value*> value, int fileIndex);
void restoreScan(not_null<const Value*> value, int fileIndex);
void uploadSpecialScan(
not_null<const Value*> value, not_null<const Value*> value,
SpecialFile type, FileType type,
QByteArray &&content); QByteArray &&content);
void deleteSpecialScan( void deleteScan(
not_null<const Value*> value, not_null<const Value*> value,
SpecialFile type); FileType type,
void restoreSpecialScan( base::optional<int> fileIndex);
void restoreScan(
not_null<const Value*> value, not_null<const Value*> value,
SpecialFile type); FileType type,
base::optional<int> fileIndex);
rpl::producer<> secretReadyEvents() const; rpl::producer<> secretReadyEvents() const;
@ -331,8 +353,6 @@ public:
void cancelValueVerification(not_null<const Value*> value); void cancelValueVerification(not_null<const Value*> value);
void saveValueEdit(not_null<const Value*> value, ValueMap &&data); void saveValueEdit(not_null<const Value*> value, ValueMap &&data);
void deleteValueEdit(not_null<const Value*> value); void deleteValueEdit(not_null<const Value*> value);
bool savingValue(not_null<const Value*> value) const;
bool uploadingScan(not_null<const Value*> value) const;
void cancel(); void cancel();
void cancelSure(); void cancelSure();
@ -350,6 +370,9 @@ private:
QByteArray credentials; QByteArray credentials;
std::vector<not_null<const Value*>> errors; std::vector<not_null<const Value*>> errors;
}; };
template <typename Condition>
EditFile *findEditFileByCondition(Condition &&condition);
EditFile *findEditFile(const FullMsgId &fullId); EditFile *findEditFile(const FullMsgId &fullId);
EditFile *findEditFile(const FileKey &key); EditFile *findEditFile(const FileKey &key);
std::pair<Value*, File*> findFile(const FileKey &key); std::pair<Value*, File*> findFile(const FileKey &key);
@ -432,11 +455,8 @@ private:
void scanUploadFail(const FullMsgId &fullId); void scanUploadFail(const FullMsgId &fullId);
void scanDeleteRestore( void scanDeleteRestore(
not_null<const Value*> value, not_null<const Value*> value,
int fileIndex, FileType type,
bool deleted); base::optional<int> fileIndex,
void specialScanDeleteRestore(
not_null<const Value*> value,
SpecialFile type,
bool deleted); bool deleted);
QString getPhoneFromValue(not_null<const Value*> value) const; QString getPhoneFromValue(not_null<const Value*> value) const;

View File

@ -159,14 +159,17 @@ bool ValidateForm(const Form &form) {
LOG(("API Error: Bad value requiring native names.")); LOG(("API Error: Bad value requiring native names."));
return false; return false;
} }
if (!CanRequireScans(value.type)) { if (!value.requiresScan(FileType::Scan)) {
Assert(value.scanMissingError.isEmpty()); for (const auto &scan : value.files(FileType::Scan)) {
}
if (!value.translationRequired) {
for (const auto &scan : value.translations) {
Assert(scan.error.isEmpty()); Assert(scan.error.isEmpty());
} }
Assert(value.translationMissingError.isEmpty()); Assert(value.fileMissingError(FileType::Scan).isEmpty());
}
if (!value.requiresScan(FileType::Translation)) {
for (const auto &scan : value.files(FileType::Translation)) {
Assert(scan.error.isEmpty());
}
Assert(value.fileMissingError(FileType::Translation).isEmpty());
} }
for (const auto &[type, specialScan] : value.specialScans) { for (const auto &[type, specialScan] : value.specialScans) {
if (!value.requiresSpecialScan(type)) { if (!value.requiresSpecialScan(type)) {
@ -278,6 +281,9 @@ QString ComputeScopeRowReadyString(const Scope &scope) {
} }
return nullptr; return nullptr;
}(); }();
if (!scope.documents.empty() && !document) {
return QString();
}
if ((document && scope.documents.size() > 1) if ((document && scope.documents.size() > 1)
|| (!scope.details || (!scope.details
&& (ScopeTypeForValueType(document->type) && (ScopeTypeForValueType(document->type)
@ -307,9 +313,6 @@ QString ComputeScopeRowReadyString(const Scope &scope) {
} }
}()); }());
} }
if (!scope.documents.empty() && !document) {
return QString();
}
const auto scheme = GetDocumentScheme( const auto scheme = GetDocumentScheme(
scope.type, scope.type,
document ? base::make_optional(document->type) : base::none, document ? base::make_optional(document->type) : base::none,
@ -372,22 +375,18 @@ ScopeRow ComputeScopeRow(const Scope &scope) {
if (!value->error.isEmpty()) { if (!value->error.isEmpty()) {
errors.push_back(value->error); errors.push_back(value->error);
} }
if (!value->scanMissingError.isEmpty()) { const auto addTypeErrors = [&](FileType type) {
errors.push_back(value->scanMissingError); if (!value->fileMissingError(type).isEmpty()) {
} errors.push_back(value->fileMissingError(type));
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->files(type)) {
for (const auto &scan : value->translations) { if (!scan.error.isEmpty()) {
if (!scan.error.isEmpty()) { errors.push_back(scan.error);
errors.push_back(scan.error); }
} }
} };
addTypeErrors(FileType::Scan);
addTypeErrors(FileType::Translation);
for (const auto &[type, scan] : value->specialScans) { for (const auto &[type, scan] : value->specialScans) {
if (!scan.error.isEmpty()) { if (!scan.error.isEmpty()) {
errors.push_back(scan.error); errors.push_back(scan.error);

View File

@ -34,7 +34,6 @@ struct ScopeRow {
QString error; QString error;
}; };
bool CanRequireScans(Value::Type type);
bool CanHaveErrors(Value::Type type); bool CanHaveErrors(Value::Type type);
bool ValidateForm(const Form &form); bool ValidateForm(const Form &form);
std::vector<Scope> ComputeScopes(const Form &form); std::vector<Scope> ComputeScopes(const Form &form);

View File

@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_boxes.h" #include "styles/style_boxes.h"
namespace Passport { namespace Passport {
namespace {
constexpr auto kMaxNameSize = 255; constexpr auto kMaxNameSize = 255;
constexpr auto kMaxDocumentSize = 24; constexpr auto kMaxDocumentSize = 24;
@ -32,6 +33,76 @@ constexpr auto kMinCitySize = 2;
constexpr auto kMaxCitySize = 64; constexpr auto kMaxCitySize = 64;
constexpr auto kMaxPostcodeSize = 10; constexpr auto kMaxPostcodeSize = 10;
ScanInfo CollectScanInfo(const EditFile &file) {
const auto status = [&] {
if (file.fields.accessHash) {
if (file.fields.downloadOffset < 0) {
return lang(lng_attach_failed);
} else if (file.fields.downloadOffset < file.fields.size) {
return formatDownloadText(
file.fields.downloadOffset,
file.fields.size);
} else {
return lng_passport_scan_uploaded(
lt_date,
langDateTimeFull(ParseDateTime(file.fields.date)));
}
} else if (file.uploadData) {
if (file.uploadData->offset < 0) {
return lang(lng_attach_failed);
} else if (file.uploadData->fullId) {
return formatDownloadText(
file.uploadData->offset,
file.uploadData->bytes.size());
} else {
return lng_passport_scan_uploaded(
lt_date,
langDateTimeFull(ParseDateTime(file.fields.date)));
}
} else {
return formatDownloadText(0, file.fields.size);
}
}();
return {
FileKey{ file.fields.id, file.fields.dcId },
!file.fields.error.isEmpty() ? file.fields.error : status,
file.fields.image,
file.deleted,
file.type,
file.fields.error };
}
ScanListData PrepareScanListData(const Value &value, FileType type) {
auto result = ScanListData();
for (const auto &scan : value.filesInEdit(type)) {
result.files.push_back(CollectScanInfo(scan));
}
result.errorMissing = value.fileMissingError(type);
return result;
}
std::map<FileType, ScanInfo> PrepareSpecialFiles(const Value &value) {
auto result = std::map<FileType, ScanInfo>();
const auto types = {
FileType::FrontSide,
FileType::ReverseSide,
FileType::Selfie
};
for (const auto type : types) {
if (value.requiresSpecialScan(type)) {
const auto i = value.specialScansInEdit.find(type);
const auto j = result.emplace(
type,
(i != end(value.specialScansInEdit)
? CollectScanInfo(i->second)
: ScanInfo())).first;
}
}
return result;
}
} // namespace
EditDocumentScheme GetDocumentScheme( EditDocumentScheme GetDocumentScheme(
Scope::Type type, Scope::Type type,
base::optional<Value::Type> scansType, base::optional<Value::Type> scansType,
@ -576,66 +647,47 @@ void PanelController::cancelPasswordSubmit() {
[=] { if (*box) (*box)->closeBox(); _form->cancelPassword(); })); [=] { if (*box) (*box)->closeBox(); _form->cancelPassword(); }));
} }
bool PanelController::canAddScan() const { bool PanelController::canAddScan(FileType type) const {
Expects(_editScope != nullptr); Expects(_editScope != nullptr);
Expects(_editDocument != nullptr); Expects(_editDocument != nullptr);
return _form->canAddScan(_editDocument); return _form->canAddScan(_editDocument, type);
} }
void PanelController::uploadScan(QByteArray &&content) { void PanelController::uploadScan(FileType type, QByteArray &&content) {
Expects(_editScope != nullptr); Expects(_editScope != nullptr);
Expects(_editDocument != nullptr); Expects(_editDocument != nullptr);
Expects(_editDocument->requiresScan(type));
_form->uploadScan(_editDocument, std::move(content)); _form->uploadScan(_editDocument, type, std::move(content));
} }
void PanelController::deleteScan(int fileIndex) { void PanelController::deleteScan(
FileType type,
base::optional<int> fileIndex) {
Expects(_editScope != nullptr); Expects(_editScope != nullptr);
Expects(_editDocument != nullptr); Expects(_editDocument != nullptr);
Expects(_editDocument->requiresScan(type));
_form->deleteScan(_editDocument, fileIndex); _form->deleteScan(_editDocument, type, fileIndex);
} }
void PanelController::restoreScan(int fileIndex) { void PanelController::restoreScan(
FileType type,
base::optional<int> fileIndex) {
Expects(_editScope != nullptr); Expects(_editScope != nullptr);
Expects(_editDocument != nullptr); Expects(_editDocument != nullptr);
Expects(_editDocument->requiresScan(type));
_form->restoreScan(_editDocument, fileIndex); _form->restoreScan(_editDocument, type, fileIndex);
}
void PanelController::uploadSpecialScan(
SpecialFile type,
QByteArray &&content) {
Expects(_editScope != nullptr);
Expects(_editDocument != nullptr);
Expects(_editDocument->requiresSpecialScan(type));
_form->uploadSpecialScan(_editDocument, type, std::move(content));
}
void PanelController::deleteSpecialScan(SpecialFile type) {
Expects(_editScope != nullptr);
Expects(_editDocument != nullptr);
Expects(_editDocument->requiresSpecialScan(type));
_form->deleteSpecialScan(_editDocument, type);
}
void PanelController::restoreSpecialScan(SpecialFile type) {
Expects(_editScope != nullptr);
Expects(_editDocument != nullptr);
Expects(_editDocument->requiresSpecialScan(type));
_form->restoreSpecialScan(_editDocument, type);
} }
rpl::producer<ScanInfo> PanelController::scanUpdated() const { rpl::producer<ScanInfo> PanelController::scanUpdated() const {
return _form->scanUpdated( return _form->scanUpdated(
) | rpl::filter([=](not_null<const EditFile*> file) { ) | rpl::filter([=](not_null<const EditFile*> file) {
return (file->value == _editDocument); return (file->value == _editDocument);
}) | rpl::map([=](not_null<const EditFile*> file) { }) | rpl::map([](not_null<const EditFile*> file) {
return collectScanInfo(*file); return CollectScanInfo(*file);
}); });
} }
@ -643,63 +695,8 @@ rpl::producer<ScopeError> PanelController::saveErrors() const {
return _saveErrors.events(); return _saveErrors.events();
} }
ScanInfo PanelController::collectScanInfo(const EditFile &file) const {
Expects(_editScope != nullptr);
Expects(_editDocument != nullptr);
const auto status = [&] {
if (file.fields.accessHash) {
if (file.fields.downloadOffset < 0) {
return lang(lng_attach_failed);
} else if (file.fields.downloadOffset < file.fields.size) {
return formatDownloadText(
file.fields.downloadOffset,
file.fields.size);
} else {
return lng_passport_scan_uploaded(
lt_date,
langDateTimeFull(ParseDateTime(file.fields.date)));
}
} else if (file.uploadData) {
if (file.uploadData->offset < 0) {
return lang(lng_attach_failed);
} else if (file.uploadData->fullId) {
return formatDownloadText(
file.uploadData->offset,
file.uploadData->bytes.size());
} else {
return lng_passport_scan_uploaded(
lt_date,
langDateTimeFull(ParseDateTime(file.fields.date)));
}
} else {
return formatDownloadText(0, file.fields.size);
}
}();
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,
specialType,
file.fields.error };
}
std::vector<ScopeError> PanelController::collectSaveErrors( std::vector<ScopeError> PanelController::collectSaveErrors(
not_null<const Value*> value) const { not_null<const Value*> value) const {
using General = ScopeError::General;
auto result = std::vector<ScopeError>(); auto result = std::vector<ScopeError>();
for (const auto &[key, value] : value->data.parsedInEdit.fields) { for (const auto &[key, value] : value->data.parsedInEdit.fields) {
if (!value.error.isEmpty()) { if (!value.error.isEmpty()) {
@ -740,7 +737,8 @@ bool PanelController::hasValueDocument() const {
return false; return false;
} }
return !_editDocument->data.parsed.fields.empty() return !_editDocument->data.parsed.fields.empty()
|| !_editDocument->scans.empty() || !_editDocument->files(FileType::Scan).empty()
|| !_editDocument->files(FileType::Translation).empty()
|| !_editDocument->specialScans.empty(); || !_editDocument->specialScans.empty();
} }
@ -978,25 +976,22 @@ void PanelController::editWithUpload(int index, int documentIndex) {
&& documentIndex < _scopes[index].documents.size()); && documentIndex < _scopes[index].documents.size());
const auto document = _scopes[index].documents[documentIndex]; const auto document = _scopes[index].documents[documentIndex];
const auto requiresSpecialScan = document->requiresSpecialScan( const auto type = document->requiresSpecialScan(FileType::FrontSide)
SpecialFile::FrontSide); ? FileType::FrontSide
const auto allowMany = !requiresSpecialScan; : FileType::Scan;
const auto allowMany = (type == FileType::Scan);
const auto widget = _panel->widget(); const auto widget = _panel->widget();
EditScans::ChooseScan(widget.get(), [=](QByteArray &&content) { EditScans::ChooseScan(widget.get(), type, [=](QByteArray &&content) {
if (_scopeDocumentTypeBox) { if (_scopeDocumentTypeBox) {
_scopeDocumentTypeBox = BoxPointer(); _scopeDocumentTypeBox = BoxPointer();
} }
if (!_editScope || !_editDocument) { if (!_editScope || !_editDocument) {
startScopeEdit(index, documentIndex); startScopeEdit(index, documentIndex);
} }
if (requiresSpecialScan) { uploadScan(type, std::move(content));
uploadSpecialScan(SpecialFile::FrontSide, std::move(content));
} else {
uploadScan(std::move(content));
}
}, [=](ReadScanError error) { }, [=](ReadScanError error) {
readScanError(error); readScanError(error);
}, allowMany); });
} }
void PanelController::readScanError(ReadScanError error) { void PanelController::readScanError(ReadScanError error) {
@ -1027,11 +1022,11 @@ bool PanelController::editRequiresScanUpload(
return false; return false;
} }
const auto document = _scopes[index].documents[documentIndex]; const auto document = _scopes[index].documents[documentIndex];
if (document->requiresSpecialScan(SpecialFile::FrontSide)) { if (document->requiresSpecialScan(FileType::FrontSide)) {
const auto &scans = document->specialScans; const auto &scans = document->specialScans;
return (scans.find(SpecialFile::FrontSide) == end(scans)); return (scans.find(FileType::FrontSide) == end(scans));
} }
return document->scans.empty(); return document->files(FileType::Scan).empty();
} }
void PanelController::editScope(int index, int documentIndex) { void PanelController::editScope(int index, int documentIndex) {
@ -1068,6 +1063,14 @@ void PanelController::startScopeEdit(int index, int documentIndex) {
case Scope::Type::Identity: case Scope::Type::Identity:
case Scope::Type::Address: { case Scope::Type::Address: {
Assert(_editDocument != nullptr); Assert(_editDocument != nullptr);
auto scans = PrepareScanListData(
*_editDocument,
FileType::Scan);
auto translations = _editDocument->translationRequired
? base::make_optional(PrepareScanListData(
*_editDocument,
FileType::Translation))
: base::none;
auto result = _editValue auto result = _editValue
? object_ptr<PanelEditDocument>( ? object_ptr<PanelEditDocument>(
_panel->widget(), _panel->widget(),
@ -1080,9 +1083,9 @@ void PanelController::startScopeEdit(int index, int documentIndex) {
_editValue->data.parsedInEdit, _editValue->data.parsedInEdit,
_editDocument->error, _editDocument->error,
_editDocument->data.parsedInEdit, _editDocument->data.parsedInEdit,
_editDocument->scanMissingError, std::move(scans),
valueFiles(*_editDocument), std::move(translations),
valueSpecialFiles(*_editDocument)) PrepareSpecialFiles(*_editDocument))
: object_ptr<PanelEditDocument>( : object_ptr<PanelEditDocument>(
_panel->widget(), _panel->widget(),
this, this,
@ -1092,9 +1095,9 @@ void PanelController::startScopeEdit(int index, int documentIndex) {
false), false),
_editDocument->error, _editDocument->error,
_editDocument->data.parsedInEdit, _editDocument->data.parsedInEdit,
_editDocument->scanMissingError, std::move(scans),
valueFiles(*_editDocument), std::move(translations),
valueSpecialFiles(*_editDocument)); PrepareSpecialFiles(*_editDocument));
const auto weak = make_weak(result.data()); const auto weak = make_weak(result.data());
_panelHasUnsavedChanges = [=] { _panelHasUnsavedChanges = [=] {
return weak ? weak->hasUnsavedChanges() : false; return weak ? weak->hasUnsavedChanges() : false;
@ -1183,13 +1186,13 @@ void PanelController::processValueSaveFinished(
} }
bool PanelController::uploadingScopeScan() const { bool PanelController::uploadingScopeScan() const {
return (_editValue && _form->uploadingScan(_editValue)) return (_editValue && _editValue->uploadingScan())
|| (_editDocument && _form->uploadingScan(_editDocument)); || (_editDocument && _editDocument->uploadingScan());
} }
bool PanelController::savingScope() const { bool PanelController::savingScope() const {
return (_editValue && _form->savingValue(_editValue)) return (_editValue && _editValue->saving())
|| (_editDocument && _form->savingValue(_editDocument)); || (_editDocument && _editDocument->saving());
} }
void PanelController::processVerificationNeeded( void PanelController::processVerificationNeeded(
@ -1253,37 +1256,6 @@ void PanelController::processVerificationNeeded(
_verificationBoxes.emplace(value, box); _verificationBoxes.emplace(value, box);
} }
std::vector<ScanInfo> PanelController::valueFiles(
const Value &value) const {
auto result = std::vector<ScanInfo>();
for (const auto &scan : value.scansInEdit) {
result.push_back(collectScanInfo(scan));
}
return result;
}
std::map<SpecialFile, ScanInfo> PanelController::valueSpecialFiles(
const Value &value) const {
auto result = std::map<SpecialFile, ScanInfo>();
const auto types = {
SpecialFile::FrontSide,
SpecialFile::ReverseSide,
SpecialFile::Selfie
};
for (const auto type : types) {
if (value.requiresSpecialScan(type)) {
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 result;
}
void PanelController::cancelValueEdit() { void PanelController::cancelValueEdit() {
Expects(_editScope != nullptr); Expects(_editScope != nullptr);

View File

@ -36,9 +36,8 @@ struct ScanInfo {
QString status; QString status;
QImage thumb; QImage thumb;
bool deleted = false; bool deleted = false;
base::optional<SpecialFile> special; FileType type = FileType();
QString error; QString error;
}; };
struct ScopeError { struct ScopeError {
@ -88,13 +87,10 @@ public:
void setupPassword(); void setupPassword();
void cancelPasswordSubmit(); void cancelPasswordSubmit();
bool canAddScan() const; bool canAddScan(FileType type) const;
void uploadScan(QByteArray &&content); void uploadScan(FileType type, QByteArray &&content);
void deleteScan(int fileIndex); void deleteScan(FileType type, base::optional<int> fileIndex);
void restoreScan(int fileIndex); void restoreScan(FileType type, base::optional<int> fileIndex);
void uploadSpecialScan(SpecialFile type, QByteArray &&content);
void deleteSpecialScan(SpecialFile type);
void restoreSpecialScan(SpecialFile type);
rpl::producer<ScanInfo> scanUpdated() const; rpl::producer<ScanInfo> scanUpdated() const;
rpl::producer<ScopeError> saveErrors() const; rpl::producer<ScopeError> saveErrors() const;
void readScanError(ReadScanError error); void readScanError(ReadScanError error);
@ -151,8 +147,7 @@ private:
int findNonEmptyDocumentIndex(const Scope &scope) const; int findNonEmptyDocumentIndex(const Scope &scope) const;
void requestScopeFilesType(int index); void requestScopeFilesType(int index);
void cancelValueEdit(); void cancelValueEdit();
std::vector<ScanInfo> valueFiles(const Value &value) const; std::map<FileType, ScanInfo> valueSpecialFiles(
std::map<SpecialFile, ScanInfo> valueSpecialFiles(
const Value &value) const; const Value &value) const;
void processValueSaveFinished(not_null<const Value*> value); void processValueSaveFinished(not_null<const Value*> value);
void processVerificationNeeded(not_null<const Value*> value); void processVerificationNeeded(not_null<const Value*> value);
@ -161,7 +156,6 @@ private:
bool uploadingScopeScan() const; bool uploadingScopeScan() const;
bool hasValueDocument() const; bool hasValueDocument() const;
bool hasValueFields() const; bool hasValueFields() const;
ScanInfo collectScanInfo(const EditFile &file) const;
std::vector<ScopeError> collectSaveErrors( std::vector<ScopeError> collectSaveErrors(
not_null<const Value*> value) const; not_null<const Value*> value) const;
QString getDefaultContactValue(Scope::Type type) const; QString getDefaultContactValue(Scope::Type type) const;

View File

@ -214,9 +214,9 @@ PanelEditDocument::PanelEditDocument(
const ValueMap &data, const ValueMap &data,
const QString &scansError, const QString &scansError,
const ValueMap &scansData, const ValueMap &scansData,
const QString &missingScansError, ScanListData &&scans,
std::vector<ScanInfo> &&files, base::optional<ScanListData> &&translations,
std::map<SpecialFile, ScanInfo> &&specialFiles) std::map<FileType, ScanInfo> &&specialFiles)
: _controller(controller) : _controller(controller)
, _scheme(std::move(scheme)) , _scheme(std::move(scheme))
, _scroll(this, st::passportPanelScroll) , _scroll(this, st::passportPanelScroll)
@ -231,8 +231,8 @@ PanelEditDocument::PanelEditDocument(
&data, &data,
&scansError, &scansError,
&scansData, &scansData,
missingScansError, std::move(scans),
std::move(files), std::move(translations),
std::move(specialFiles)); std::move(specialFiles));
} }
@ -242,9 +242,9 @@ PanelEditDocument::PanelEditDocument(
Scheme scheme, Scheme scheme,
const QString &scansError, const QString &scansError,
const ValueMap &scansData, const ValueMap &scansData,
const QString &missingScansError, ScanListData &&scans,
std::vector<ScanInfo> &&files, base::optional<ScanListData> &&translations,
std::map<SpecialFile, ScanInfo> &&specialFiles) std::map<FileType, ScanInfo> &&specialFiles)
: _controller(controller) : _controller(controller)
, _scheme(std::move(scheme)) , _scheme(std::move(scheme))
, _scroll(this, st::passportPanelScroll) , _scroll(this, st::passportPanelScroll)
@ -259,8 +259,8 @@ PanelEditDocument::PanelEditDocument(
nullptr, nullptr,
&scansError, &scansError,
&scansData, &scansData,
missingScansError, std::move(scans),
std::move(files), std::move(translations),
std::move(specialFiles)); std::move(specialFiles));
} }
@ -279,7 +279,7 @@ PanelEditDocument::PanelEditDocument(
this, this,
langFactory(lng_passport_save_value), langFactory(lng_passport_save_value),
st::passportPanelSaveValue) { st::passportPanelSaveValue) {
setupControls(&error, &data, nullptr, nullptr, QString(), {}, {}); setupControls(&error, &data, nullptr, nullptr, {}, {}, {});
} }
void PanelEditDocument::setupControls( void PanelEditDocument::setupControls(
@ -287,16 +287,16 @@ void PanelEditDocument::setupControls(
const ValueMap *data, const ValueMap *data,
const QString *scansError, const QString *scansError,
const ValueMap *scansData, const ValueMap *scansData,
const QString &missingScansError, ScanListData &&scans,
std::vector<ScanInfo> &&files, base::optional<ScanListData> &&translations,
std::map<SpecialFile, ScanInfo> &&specialFiles) { std::map<FileType, ScanInfo> &&specialFiles) {
const auto inner = setupContent( const auto inner = setupContent(
error, error,
data, data,
scansError, scansError,
scansData, scansData,
missingScansError, std::move(scans),
std::move(files), std::move(translations),
std::move(specialFiles)); std::move(specialFiles));
using namespace rpl::mappers; using namespace rpl::mappers;
@ -315,9 +315,9 @@ not_null<Ui::RpWidget*> PanelEditDocument::setupContent(
const ValueMap *data, const ValueMap *data,
const QString *scansError, const QString *scansError,
const ValueMap *scansData, const ValueMap *scansData,
const QString &missingScansError, ScanListData &&scans,
std::vector<ScanInfo> &&files, base::optional<ScanListData> &&translations,
std::map<SpecialFile, ScanInfo> &&specialFiles) { std::map<FileType, ScanInfo> &&specialFiles) {
const auto inner = _scroll->setOwnedWidget( const auto inner = _scroll->setOwnedWidget(
object_ptr<Ui::VerticalLayout>(this)); object_ptr<Ui::VerticalLayout>(this));
_scroll->widthValue( _scroll->widthValue(
@ -331,7 +331,8 @@ not_null<Ui::RpWidget*> PanelEditDocument::setupContent(
inner, inner,
_controller, _controller,
*scansError, *scansError,
std::move(specialFiles))); std::move(specialFiles),
std::move(translations)));
} else if (scansData) { } else if (scansData) {
_editScans = inner->add( _editScans = inner->add(
object_ptr<EditScans>( object_ptr<EditScans>(
@ -339,8 +340,8 @@ not_null<Ui::RpWidget*> PanelEditDocument::setupContent(
_controller, _controller,
_scheme.scansHeader, _scheme.scansHeader,
*scansError, *scansError,
missingScansError, std::move(scans),
std::move(files))); std::move(translations)));
} }
const auto valueOrEmpty = [&]( const auto valueOrEmpty = [&](

View File

@ -33,8 +33,9 @@ struct ValueMap;
struct ScanInfo; struct ScanInfo;
class EditScans; class EditScans;
class PanelDetailsRow; class PanelDetailsRow;
enum class SpecialFile; enum class FileType;
enum class PanelDetailsType; enum class PanelDetailsType;
struct ScanListData;
struct EditDocumentScheme { struct EditDocumentScheme {
enum class ValueClass { enum class ValueClass {
@ -72,18 +73,18 @@ public:
const ValueMap &data, const ValueMap &data,
const QString &scansError, const QString &scansError,
const ValueMap &scansData, const ValueMap &scansData,
const QString &missingScansError, ScanListData &&scans,
std::vector<ScanInfo> &&files, base::optional<ScanListData> &&translations,
std::map<SpecialFile, ScanInfo> &&specialFiles); std::map<FileType, ScanInfo> &&specialFiles);
PanelEditDocument( PanelEditDocument(
QWidget *parent, QWidget *parent,
not_null<PanelController*> controller, not_null<PanelController*> controller,
Scheme scheme, Scheme scheme,
const QString &scansError, const QString &scansError,
const ValueMap &scansData, const ValueMap &scansData,
const QString &missingScansError, ScanListData &&scans,
std::vector<ScanInfo> &&files, base::optional<ScanListData> &&translations,
std::map<SpecialFile, ScanInfo> &&specialFiles); std::map<FileType, ScanInfo> &&specialFiles);
PanelEditDocument( PanelEditDocument(
QWidget *parent, QWidget *parent,
not_null<PanelController*> controller, not_null<PanelController*> controller,
@ -104,17 +105,17 @@ private:
const ValueMap *data, const ValueMap *data,
const QString *scansError, const QString *scansError,
const ValueMap *scansData, const ValueMap *scansData,
const QString &missingScansError, ScanListData &&scans,
std::vector<ScanInfo> &&files, base::optional<ScanListData> &&translations,
std::map<SpecialFile, ScanInfo> &&specialFiles); std::map<FileType, ScanInfo> &&specialFiles);
not_null<Ui::RpWidget*> setupContent( not_null<Ui::RpWidget*> setupContent(
const QString *error, const QString *error,
const ValueMap *data, const ValueMap *data,
const QString *scansError, const QString *scansError,
const ValueMap *scansData, const ValueMap *scansData,
const QString &missingScansError, ScanListData &&scans,
std::vector<ScanInfo> &&files, base::optional<ScanListData> &&translations,
std::map<SpecialFile, ScanInfo> &&specialFiles); std::map<FileType, ScanInfo> &&specialFiles);
void updateControlsGeometry(); void updateControlsGeometry();
void updateCommonError(); void updateCommonError();

View File

@ -120,6 +120,173 @@ struct EditScans::SpecialScan {
rpl::variable<bool> rowCreated; rpl::variable<bool> rowCreated;
}; };
void UpdateFileRow(
not_null<ScanButton*> button,
const ScanInfo &info) {
button->setStatus(info.status);
button->setImage(info.thumb);
button->setDeleted(info.deleted);
button->setError(!info.error.isEmpty());
}
base::unique_qptr<Ui::SlideWrap<ScanButton>> CreateScan(
not_null<Ui::VerticalLayout*> parent,
const ScanInfo &info,
const QString &name) {
auto result = base::unique_qptr<Ui::SlideWrap<ScanButton>>(
parent->add(object_ptr<Ui::SlideWrap<ScanButton>>(
parent,
object_ptr<ScanButton>(
parent,
st::passportScanRow,
name,
info.status,
info.deleted,
!info.error.isEmpty()))));
result->entity()->setImage(info.thumb);
return result;
}
EditScans::List::List(
not_null<PanelController*> controller,
ScanListData &&data)
: controller(controller)
, files(std::move(data.files))
, initialCount(int(files.size()))
, errorMissing(data.errorMissing) {
}
EditScans::List::List(
not_null<PanelController*> controller,
base::optional<ScanListData> &&data)
: controller(controller)
, files(data ? std::move(data->files) : std::vector<ScanInfo>())
, initialCount(data ? base::make_optional(int(files.size())) : base::none)
, errorMissing(data ? std::move(data->errorMissing) : QString()) {
}
bool EditScans::List::uploadedSomeMore() const {
if (!initialCount) {
return false;
}
const auto from = begin(files) + *initialCount;
const auto till = end(files);
return std::find_if(from, till, [](const ScanInfo &file) {
return !file.deleted;
}) != till;
}
bool EditScans::List::uploadMoreRequired() const {
if (!upload) {
return false;
}
const auto exists = ranges::find_if(
files,
[](const ScanInfo &file) { return !file.deleted; }) != end(files);
if (!exists) {
return false;
}
const auto errorExists = ranges::find_if(
files,
[](const ScanInfo &file) { return !file.error.isEmpty(); }
) != end(files);
return (errorExists || uploadMoreError) && !uploadedSomeMore();
}
Ui::SlideWrap<ScanButton> *EditScans::List::nonDeletedErrorRow() const {
const auto nonDeletedErrorIt = ranges::find_if(
files,
[](const ScanInfo &file) {
return !file.error.isEmpty() && !file.deleted;
});
if (nonDeletedErrorIt == end(files)) {
return nullptr;
}
const auto index = (nonDeletedErrorIt - begin(files));
return rows[index].get();
}
rpl::producer<QString> EditScans::List::uploadButtonText() const {
return Lang::Viewer(files.empty()
? lng_passport_upload_scans
: lng_passport_upload_more) | Info::Profile::ToUpperValue();
}
void EditScans::List::hideError() {
toggleError(false);
}
void EditScans::List::toggleError(bool shown) {
if (errorShown != shown) {
errorShown = shown;
errorAnimation.start(
[=] { errorAnimationCallback(); },
errorShown ? 0. : 1.,
errorShown ? 1. : 0.,
st::passportDetailsField.duration);
}
}
void EditScans::List::errorAnimationCallback() {
const auto error = errorAnimation.current(errorShown ? 1. : 0.);
if (error == 0.) {
upload->setColorOverride(base::none);
} else {
upload->setColorOverride(anim::color(
st::passportUploadButton.textFg,
st::boxTextFgError,
error));
}
}
void EditScans::List::updateScan(ScanInfo &&info, int width) {
const auto i = ranges::find(files, info.key, [](const ScanInfo &file) {
return file.key;
});
if (i != files.end()) {
*i = std::move(info);
const auto scan = rows[i - files.begin()]->entity();
UpdateFileRow(scan, *i);
if (!i->deleted) {
hideError();
}
} else {
files.push_back(std::move(info));
pushScan(files.back());
wrap->resizeToWidth(width);
rows.back()->show(anim::type::normal);
if (divider) {
divider->hide(anim::type::normal);
}
header->show(anim::type::normal);
uploadTexts.fire(uploadButtonText());
}
}
void EditScans::List::pushScan(const ScanInfo &info) {
const auto index = rows.size();
const auto type = info.type;
rows.push_back(CreateScan(
wrap,
info,
lng_passport_scan_index(lt_index, QString::number(index + 1))));
rows.back()->hide(anim::type::instant);
const auto scan = rows.back()->entity();
scan->deleteClicks(
) | rpl::start_with_next([=] {
controller->deleteScan(type, index);
}, scan->lifetime());
scan->restoreClicks(
) | rpl::start_with_next([=] {
controller->restoreScan(type, index);
}, scan->lifetime());
hideError();
}
ScanButton::ScanButton( ScanButton::ScanButton(
QWidget *parent, QWidget *parent,
const style::PassportScanRow &st, const style::PassportScanRow &st,
@ -258,15 +425,14 @@ EditScans::EditScans(
not_null<PanelController*> controller, not_null<PanelController*> controller,
const QString &header, const QString &header,
const QString &error, const QString &error,
const QString &errorMissing, ScanListData &&scans,
std::vector<ScanInfo> &&files) base::optional<ScanListData> &&translations)
: RpWidget(parent) : RpWidget(parent)
, _controller(controller) , _controller(controller)
, _files(std::move(files))
, _initialCount(_files.size())
, _error(error) , _error(error)
, _errorMissing(errorMissing) , _content(this)
, _content(this) { , _scansList(_controller, std::move(scans))
, _translationsList(_controller, std::move(translations)) {
setupScans(header); setupScans(header);
} }
@ -274,26 +440,17 @@ EditScans::EditScans(
QWidget *parent, QWidget *parent,
not_null<PanelController*> controller, not_null<PanelController*> controller,
const QString &error, const QString &error,
std::map<SpecialFile, ScanInfo> &&specialFiles) std::map<FileType, ScanInfo> &&specialFiles,
base::optional<ScanListData> &&translations)
: RpWidget(parent) : RpWidget(parent)
, _controller(controller) , _controller(controller)
, _initialCount(-1)
, _error(error) , _error(error)
, _content(this) { , _content(this)
, _scansList(_controller)
, _translationsList(_controller, std::move(translations)) {
setupSpecialScans(std::move(specialFiles)); setupSpecialScans(std::move(specialFiles));
} }
bool EditScans::uploadedSomeMore() const {
if (_initialCount < 0) {
return false;
}
const auto from = begin(_files) + _initialCount;
const auto till = end(_files);
return std::find_if(from, till, [](const ScanInfo &file) {
return !file.deleted;
}) != till;
}
base::optional<int> EditScans::validateGetErrorTop() { base::optional<int> EditScans::validateGetErrorTop() {
auto result = base::optional<int>(); auto result = base::optional<int>();
const auto suggestResult = [&](int value) { const auto suggestResult = [&](int value) {
@ -302,33 +459,23 @@ base::optional<int> EditScans::validateGetErrorTop() {
} }
}; };
const auto exists = ranges::find_if(
_files,
[](const ScanInfo &file) { return !file.deleted; }) != end(_files);
const auto errorExists = ranges::find_if(
_files,
[](const ScanInfo &file) { return !file.error.isEmpty(); }
) != end(_files);
if (_commonError && !somethingChanged()) { if (_commonError && !somethingChanged()) {
suggestResult(_commonError->y()); suggestResult(_commonError->y());
} }
if (_upload && (!exists const auto suggestList = [&](FileType type) {
|| ((errorExists || _uploadMoreError) && !uploadedSomeMore()))) { auto &list = this->list(type);
toggleError(true); if (list.uploadMoreRequired()) {
suggestResult((_files.size() > 5) ? _upload->y() : _header->y()); list.toggleError(true);
} suggestResult((list.files.size() > 5)
? list.upload->y()
const auto nonDeletedErrorIt = ranges::find_if( : list.header->y());
_files, }
[](const ScanInfo &file) { if (const auto row = list.nonDeletedErrorRow()) {
return !file.error.isEmpty() && !file.deleted; //toggleError(true);
}); suggestResult(row->y());
if (nonDeletedErrorIt != end(_files)) { }
const auto index = (nonDeletedErrorIt - begin(_files)); };
// toggleError(true); suggestList(FileType::Scan);
suggestResult(_rows[index]->y());
}
for (const auto &[type, scan] : _specialScans) { for (const auto &[type, scan] : _specialScans) {
if (!scan.file.key.id if (!scan.file.key.id
|| scan.file.deleted || scan.file.deleted
@ -337,9 +484,26 @@ base::optional<int> EditScans::validateGetErrorTop() {
suggestResult(scan.header->y()); suggestResult(scan.header->y());
} }
} }
suggestList(FileType::Translation);
return result; return result;
} }
EditScans::List &EditScans::list(FileType type) {
switch (type) {
case FileType::Scan: return _scansList;
case FileType::Translation: return _translationsList;
}
Unexpected("Type in EditScans::list().");
}
const EditScans::List &EditScans::list(FileType type) const {
switch (type) {
case FileType::Scan: return _scansList;
case FileType::Translation: return _translationsList;
}
Unexpected("Type in EditScans::list() const.");
}
void EditScans::setupScans(const QString &header) { void EditScans::setupScans(const QString &header) {
const auto inner = _content.data(); const auto inner = _content.data();
inner->move(0, 0); inner->move(0, 0);
@ -357,80 +521,96 @@ void EditScans::setupScans(const QString &header) {
_commonError->toggle(true, anim::type::instant); _commonError->toggle(true, anim::type::instant);
} }
_divider = inner->add( setupList(inner, FileType::Scan, header);
object_ptr<Ui::SlideWrap<BoxContentDivider>>( setupList(inner, FileType::Translation, "Translations");
inner,
object_ptr<BoxContentDivider>(
inner,
st::passportFormDividerHeight)));
_divider->toggle(_files.empty(), anim::type::instant);
_header = inner->add(
object_ptr<Ui::SlideWrap<Ui::FlatLabel>>(
inner,
object_ptr<Ui::FlatLabel>(
inner,
header,
Ui::FlatLabel::InitType::Simple,
st::passportFormHeader),
st::passportUploadHeaderPadding));
_header->toggle(!_files.empty(), anim::type::instant);
if (!_errorMissing.isEmpty()) {
_uploadMoreError = inner->add(
object_ptr<Ui::SlideWrap<Ui::FlatLabel>>(
inner,
object_ptr<Ui::FlatLabel>(
inner,
_errorMissing,
Ui::FlatLabel::InitType::Simple,
st::passportVerifyErrorLabel),
st::passportUploadErrorPadding));
_uploadMoreError->toggle(true, anim::type::instant);
}
_wrap = inner->add(object_ptr<Ui::VerticalLayout>(inner));
for (const auto &scan : _files) {
pushScan(scan);
_rows.back()->show(anim::type::instant);
}
_upload = inner->add(
object_ptr<Info::Profile::Button>(
inner,
_uploadTexts.events_starting_with(
uploadButtonText()
) | rpl::flatten_latest(),
st::passportUploadButton),
st::passportUploadButtonPadding);
_upload->addClickHandler([=] {
chooseScan();
});
inner->add(object_ptr<BoxContentDivider>(
inner,
st::passportFormDividerHeight));
init(); init();
} }
void EditScans::setupSpecialScans(std::map<SpecialFile, ScanInfo> &&files) { void EditScans::setupList(
const auto requiresBothSides = files.find(SpecialFile::ReverseSide) not_null<Ui::VerticalLayout*> container,
FileType type,
const QString &header) {
auto &list = this->list(type);
if (!list.initialCount) {
return;
}
if (type == FileType::Scan) {
list.divider = container->add(
object_ptr<Ui::SlideWrap<BoxContentDivider>>(
container,
object_ptr<BoxContentDivider>(
container,
st::passportFormDividerHeight)));
list.divider->toggle(list.files.empty(), anim::type::instant);
}
list.header = container->add(
object_ptr<Ui::SlideWrap<Ui::FlatLabel>>(
container,
object_ptr<Ui::FlatLabel>(
container,
header,
Ui::FlatLabel::InitType::Simple,
st::passportFormHeader),
st::passportUploadHeaderPadding));
list.header->toggle(
!list.divider || !list.files.empty(),
anim::type::instant);
if (!list.errorMissing.isEmpty()) {
list.uploadMoreError = container->add(
object_ptr<Ui::SlideWrap<Ui::FlatLabel>>(
container,
object_ptr<Ui::FlatLabel>(
container,
list.errorMissing,
Ui::FlatLabel::InitType::Simple,
st::passportVerifyErrorLabel),
st::passportUploadErrorPadding));
list.uploadMoreError->toggle(true, anim::type::instant);
}
list.wrap = container->add(object_ptr<Ui::VerticalLayout>(container));
for (const auto &scan : list.files) {
list.pushScan(scan);
list.rows.back()->show(anim::type::instant);
}
list.upload = container->add(
object_ptr<Info::Profile::Button>(
container,
list.uploadTexts.events_starting_with(
list.uploadButtonText()
) | rpl::flatten_latest(),
st::passportUploadButton),
st::passportUploadButtonPadding);
list.upload->addClickHandler([=] {
chooseScan(type);
});
container->add(object_ptr<BoxContentDivider>(
container,
st::passportFormDividerHeight));
}
void EditScans::setupSpecialScans(std::map<FileType, ScanInfo> &&files) {
const auto requiresBothSides = files.find(FileType::ReverseSide)
!= end(files); != end(files);
const auto title = [&](SpecialFile type) { const auto title = [&](FileType type) {
switch (type) { switch (type) {
case SpecialFile::FrontSide: case FileType::FrontSide:
return lang(requiresBothSides return lang(requiresBothSides
? lng_passport_front_side_title ? lng_passport_front_side_title
: lng_passport_main_page_title); : lng_passport_main_page_title);
case SpecialFile::ReverseSide: case FileType::ReverseSide:
return lang(lng_passport_reverse_side_title); return lang(lng_passport_reverse_side_title);
case SpecialFile::Selfie: case FileType::Selfie:
return lang(lng_passport_selfie_title); return lang(lng_passport_selfie_title);
} }
Unexpected("Type in special row title."); Unexpected("Type in special row title.");
}; };
const auto uploadKey = [=](SpecialFile type, bool hasScan) { const auto uploadKey = [=](FileType type, bool hasScan) {
switch (type) { switch (type) {
case SpecialFile::FrontSide: case FileType::FrontSide:
return requiresBothSides return requiresBothSides
? (hasScan ? (hasScan
? lng_passport_reupload_front_side ? lng_passport_reupload_front_side
@ -438,26 +618,26 @@ void EditScans::setupSpecialScans(std::map<SpecialFile, ScanInfo> &&files) {
: (hasScan : (hasScan
? lng_passport_reupload_main_page ? lng_passport_reupload_main_page
: lng_passport_upload_main_page); : lng_passport_upload_main_page);
case SpecialFile::ReverseSide: case FileType::ReverseSide:
return hasScan return hasScan
? lng_passport_reupload_reverse_side ? lng_passport_reupload_reverse_side
: lng_passport_upload_reverse_side; : lng_passport_upload_reverse_side;
case SpecialFile::Selfie: case FileType::Selfie:
return hasScan return hasScan
? lng_passport_reupload_selfie ? lng_passport_reupload_selfie
: lng_passport_upload_selfie; : lng_passport_upload_selfie;
} }
Unexpected("Type in special row upload key."); Unexpected("Type in special row upload key.");
}; };
const auto description = [&](SpecialFile type) { const auto description = [&](FileType type) {
switch (type) { switch (type) {
case SpecialFile::FrontSide: case FileType::FrontSide:
return lang(requiresBothSides return lang(requiresBothSides
? lng_passport_front_side_description ? lng_passport_front_side_description
: lng_passport_main_page_description); : lng_passport_main_page_description);
case SpecialFile::ReverseSide: case FileType::ReverseSide:
return lang(lng_passport_reverse_side_description); return lang(lng_passport_reverse_side_description);
case SpecialFile::Selfie: case FileType::Selfie:
return lang(lng_passport_selfie_description); return lang(lng_passport_selfie_description);
} }
Unexpected("Type in special row upload key."); Unexpected("Type in special row upload key.");
@ -511,7 +691,7 @@ void EditScans::setupSpecialScans(std::map<SpecialFile, ScanInfo> &&files) {
st::passportUploadButton), st::passportUploadButton),
st::passportUploadButtonPadding); st::passportUploadButtonPadding);
scan.upload->addClickHandler([=, type = type] { scan.upload->addClickHandler([=, type = type] {
chooseSpecialScan(type); chooseScan(type);
}); });
inner->add(object_ptr<Ui::DividerLabel>( inner->add(object_ptr<Ui::DividerLabel>(
@ -524,6 +704,8 @@ void EditScans::setupSpecialScans(std::map<SpecialFile, ScanInfo> &&files) {
st::passportFormLabelPadding)); st::passportFormLabelPadding));
} }
setupList(inner, FileType::Translation, lang(lng_passport_translation));
init(); init();
} }
@ -545,29 +727,11 @@ void EditScans::init() {
} }
void EditScans::updateScan(ScanInfo &&info) { void EditScans::updateScan(ScanInfo &&info) {
if (info.special) { if (info.type != FileType::Scan && info.type != FileType::Translation) {
updateSpecialScan(*info.special, std::move(info)); updateSpecialScan(std::move(info));
return; return;
} }
const auto i = ranges::find(_files, info.key, [](const ScanInfo &file) { list(info.type).updateScan(std::move(info), width());
return file.key;
});
if (i != _files.end()) {
*i = std::move(info);
const auto scan = _rows[i - _files.begin()]->entity();
updateFileRow(scan, *i);
if (!i->deleted) {
hideError();
}
} else {
_files.push_back(std::move(info));
pushScan(_files.back());
_wrap->resizeToWidth(width());
_rows.back()->show(anim::type::normal);
_divider->hide(anim::type::normal);
_header->show(anim::type::normal);
_uploadTexts.fire(uploadButtonText());
}
updateErrorLabels(); updateErrorLabels();
} }
@ -579,35 +743,46 @@ void EditScans::scanFieldsChanged(bool changed) {
} }
void EditScans::updateErrorLabels() { void EditScans::updateErrorLabels() {
if (_uploadMoreError) { const auto updateList = [&](FileType type) {
_uploadMoreError->toggle(!uploadedSomeMore(), anim::type::normal); auto &list = this->list(type);
} if (list.uploadMoreError) {
list.uploadMoreError->toggle(
!list.uploadedSomeMore(),
anim::type::normal);
}
};
updateList(FileType::Scan);
updateList(FileType::Translation);
if (_commonError) { if (_commonError) {
_commonError->toggle(!somethingChanged(), anim::type::normal); _commonError->toggle(!somethingChanged(), anim::type::normal);
} }
} }
bool EditScans::somethingChanged() const { bool EditScans::somethingChanged() const {
return uploadedSomeMore() || _scanFieldsChanged || _specialScanChanged; return list(FileType::Scan).uploadedSomeMore()
|| list(FileType::Translation).uploadedSomeMore()
|| _scanFieldsChanged
|| _specialScanChanged;
} }
void EditScans::updateSpecialScan(SpecialFile type, ScanInfo &&info) { void EditScans::updateSpecialScan(ScanInfo &&info) {
Expects(info.key.id != 0); Expects(info.key.id != 0);
const auto type = info.type;
const auto i = _specialScans.find(type); const auto i = _specialScans.find(type);
if (i == end(_specialScans)) { if (i == end(_specialScans)) {
return; return;
} }
auto &scan = i->second; auto &scan = i->second;
if (scan.file.key.id) { if (scan.file.key.id) {
updateFileRow(scan.row->entity(), info); UpdateFileRow(scan.row->entity(), info);
scan.rowCreated = !info.deleted; scan.rowCreated = !info.deleted;
if (scan.file.key.id != info.key.id) { if (scan.file.key.id != info.key.id) {
specialScanChanged(type, true); specialScanChanged(type, true);
} }
} else { } else {
const auto requiresBothSides const auto requiresBothSides
= (_specialScans.find(SpecialFile::ReverseSide) = (_specialScans.find(FileType::ReverseSide)
!= end(_specialScans)); != end(_specialScans));
createSpecialScanRow(scan, info, requiresBothSides); createSpecialScanRow(scan, info, requiresBothSides);
scan.wrap->resizeToWidth(width()); scan.wrap->resizeToWidth(width());
@ -618,117 +793,60 @@ void EditScans::updateSpecialScan(SpecialFile type, ScanInfo &&info) {
scan.file = std::move(info); scan.file = std::move(info);
} }
void EditScans::updateFileRow(
not_null<ScanButton*> button,
const ScanInfo &info) {
button->setStatus(info.status);
button->setImage(info.thumb);
button->setDeleted(info.deleted);
button->setError(!info.error.isEmpty());
}
void EditScans::createSpecialScanRow( void EditScans::createSpecialScanRow(
SpecialScan &scan, SpecialScan &scan,
const ScanInfo &info, const ScanInfo &info,
bool requiresBothSides) { bool requiresBothSides) {
Expects(scan.file.special.has_value()); Expects(scan.file.type != FileType::Scan
&& scan.file.type != FileType::Translation);
const auto type = *scan.file.special; const auto type = scan.file.type;
const auto name = [&] { const auto name = [&] {
switch (type) { switch (type) {
case SpecialFile::FrontSide: case FileType::FrontSide:
return lang(requiresBothSides return lang(requiresBothSides
? lng_passport_front_side_name ? lng_passport_front_side_name
: lng_passport_main_page_name); : lng_passport_main_page_name);
case SpecialFile::ReverseSide: case FileType::ReverseSide:
return lang(lng_passport_reverse_side_name); return lang(lng_passport_reverse_side_name);
case SpecialFile::Selfie: case FileType::Selfie:
return lang(lng_passport_selfie_name); return lang(lng_passport_selfie_name);
} }
Unexpected("Type in special file name."); Unexpected("Type in special file name.");
}(); }();
scan.row = createScan(scan.wrap, info, name); scan.row = CreateScan(scan.wrap, info, name);
const auto row = scan.row->entity(); const auto row = scan.row->entity();
row->deleteClicks( row->deleteClicks(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
_controller->deleteSpecialScan(type); _controller->deleteScan(type, base::none);
}, row->lifetime()); }, row->lifetime());
row->restoreClicks( row->restoreClicks(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
_controller->restoreSpecialScan(type); _controller->restoreScan(type, base::none);
}, row->lifetime()); }, row->lifetime());
scan.rowCreated = !info.deleted; scan.rowCreated = !info.deleted;
} }
void EditScans::pushScan(const ScanInfo &info) { void EditScans::chooseScan(FileType type) {
const auto index = _rows.size(); if (!_controller->canAddScan(type)) {
_rows.push_back(createScan(
_wrap,
info,
lng_passport_scan_index(lt_index, QString::number(index + 1))));
_rows.back()->hide(anim::type::instant);
const auto scan = _rows.back()->entity();
scan->deleteClicks(
) | rpl::start_with_next([=] {
_controller->deleteScan(index);
}, scan->lifetime());
scan->restoreClicks(
) | rpl::start_with_next([=] {
_controller->restoreScan(index);
}, scan->lifetime());
hideError();
}
base::unique_qptr<Ui::SlideWrap<ScanButton>> EditScans::createScan(
not_null<Ui::VerticalLayout*> parent,
const ScanInfo &info,
const QString &name) {
auto result = base::unique_qptr<Ui::SlideWrap<ScanButton>>(
parent->add(object_ptr<Ui::SlideWrap<ScanButton>>(
parent,
object_ptr<ScanButton>(
parent,
st::passportScanRow,
name,
info.status,
info.deleted,
!info.error.isEmpty()))));
result->entity()->setImage(info.thumb);
return result;
}
void EditScans::chooseScan() {
if (!_controller->canAddScan()) {
_controller->showToast(lang(lng_passport_scans_limit_reached)); _controller->showToast(lang(lng_passport_scans_limit_reached));
return; return;
} }
ChooseScan(this, [=](QByteArray &&content) { ChooseScan(this, type, [=](QByteArray &&content) {
_controller->uploadScan(std::move(content)); _controller->uploadScan(type, std::move(content));
}, [=](ReadScanError error) { }, [=](ReadScanError error) {
_controller->readScanError(error); _controller->readScanError(error);
}, true); });
}
void EditScans::chooseSpecialScan(SpecialFile type) {
ChooseScan(this, [=](QByteArray &&content) {
_controller->uploadSpecialScan(type, std::move(content));
}, [=](ReadScanError error) {
_controller->readScanError(error);
}, false);
} }
void EditScans::ChooseScan( void EditScans::ChooseScan(
QPointer<QWidget> parent, QPointer<QWidget> parent,
FileType type,
Fn<void(QByteArray&&)> doneCallback, Fn<void(QByteArray&&)> doneCallback,
Fn<void(ReadScanError)> errorCallback, Fn<void(ReadScanError)> errorCallback) {
bool allowMany) {
Expects(parent != nullptr); Expects(parent != nullptr);
const auto processFiles = std::make_shared<Fn<void(QStringList&&)>>(); const auto processFiles = std::make_shared<Fn<void(QStringList&&)>>();
@ -802,6 +920,8 @@ void EditScans::ChooseScan(
} }
} }
}; };
const auto allowMany = (type == FileType::Scan)
|| (type == FileType::Translation);
(allowMany ? FileDialog::GetOpenPaths : FileDialog::GetOpenPath)( (allowMany ? FileDialog::GetOpenPaths : FileDialog::GetOpenPath)(
parent, parent,
lang(lng_passport_choose_image), lang(lng_passport_choose_image),
@ -810,44 +930,11 @@ void EditScans::ChooseScan(
nullptr); nullptr);
} }
rpl::producer<QString> EditScans::uploadButtonText() const { void EditScans::hideSpecialScanError(FileType type) {
return Lang::Viewer(_files.empty()
? lng_passport_upload_scans
: lng_passport_upload_more) | Info::Profile::ToUpperValue();
}
void EditScans::hideError() {
toggleError(false);
}
void EditScans::toggleError(bool shown) {
if (_errorShown != shown) {
_errorShown = shown;
_errorAnimation.start(
[=] { errorAnimationCallback(); },
_errorShown ? 0. : 1.,
_errorShown ? 1. : 0.,
st::passportDetailsField.duration);
}
}
void EditScans::errorAnimationCallback() {
const auto error = _errorAnimation.current(_errorShown ? 1. : 0.);
if (error == 0.) {
_upload->setColorOverride(base::none);
} else {
_upload->setColorOverride(anim::color(
st::passportUploadButton.textFg,
st::boxTextFgError,
error));
}
}
void EditScans::hideSpecialScanError(SpecialFile type) {
toggleSpecialScanError(type, false); toggleSpecialScanError(type, false);
} }
void EditScans::specialScanChanged(SpecialFile type, bool changed) { void EditScans::specialScanChanged(FileType type, bool changed) {
hideSpecialScanError(type); hideSpecialScanError(type);
if (_specialScanChanged != changed) { if (_specialScanChanged != changed) {
_specialScanChanged = changed; _specialScanChanged = changed;
@ -855,13 +942,13 @@ void EditScans::specialScanChanged(SpecialFile type, bool changed) {
} }
} }
auto EditScans::findSpecialScan(SpecialFile type) -> SpecialScan& { auto EditScans::findSpecialScan(FileType type) -> SpecialScan& {
const auto i = _specialScans.find(type); const auto i = _specialScans.find(type);
Assert(i != end(_specialScans)); Assert(i != end(_specialScans));
return i->second; return i->second;
} }
void EditScans::toggleSpecialScanError(SpecialFile type, bool shown) { void EditScans::toggleSpecialScanError(FileType type, bool shown) {
auto &scan = findSpecialScan(type); auto &scan = findSpecialScan(type);
if (scan.errorShown != shown) { if (scan.errorShown != shown) {
scan.errorShown = shown; scan.errorShown = shown;
@ -873,7 +960,7 @@ void EditScans::toggleSpecialScanError(SpecialFile type, bool shown) {
} }
} }
void EditScans::specialScanErrorAnimationCallback(SpecialFile type) { void EditScans::specialScanErrorAnimationCallback(FileType type) {
auto &scan = findSpecialScan(type); auto &scan = findSpecialScan(type);
const auto error = scan.errorAnimation.current( const auto error = scan.errorAnimation.current(
scan.errorShown ? 1. : 0.); scan.errorShown ? 1. : 0.);

View File

@ -26,7 +26,7 @@ class Button;
namespace Passport { namespace Passport {
enum class SpecialFile; enum class FileType;
class PanelController; class PanelController;
class ScanButton; class ScanButton;
struct ScanInfo; struct ScanInfo;
@ -38,6 +38,11 @@ enum class ReadScanError {
Unknown, Unknown,
}; };
struct ScanListData {
std::vector<ScanInfo> files;
QString errorMissing;
};
class EditScans : public Ui::RpWidget { class EditScans : public Ui::RpWidget {
public: public:
EditScans( EditScans(
@ -45,13 +50,14 @@ public:
not_null<PanelController*> controller, not_null<PanelController*> controller,
const QString &header, const QString &header,
const QString &error, const QString &error,
const QString &errorMissing, ScanListData &&scans,
std::vector<ScanInfo> &&files); base::optional<ScanListData> &&translations);
EditScans( EditScans(
QWidget *parent, QWidget *parent,
not_null<PanelController*> controller, not_null<PanelController*> controller,
const QString &error, const QString &error,
std::map<SpecialFile, ScanInfo> &&specialFiles); std::map<FileType, ScanInfo> &&specialFiles,
base::optional<ScanListData> &&translations);
base::optional<int> validateGetErrorTop(); base::optional<int> validateGetErrorTop();
@ -59,27 +65,59 @@ public:
static void ChooseScan( static void ChooseScan(
QPointer<QWidget> parent, QPointer<QWidget> parent,
FileType type,
Fn<void(QByteArray&&)> doneCallback, Fn<void(QByteArray&&)> doneCallback,
Fn<void(ReadScanError)> errorCallback, Fn<void(ReadScanError)> errorCallback);
bool allowMany);
~EditScans(); ~EditScans();
private: private:
struct SpecialScan; struct SpecialScan;
struct List {
List(not_null<PanelController*> controller, ScanListData &&data);
List(
not_null<PanelController*> controller,
base::optional<ScanListData> &&data = base::none);
bool uploadedSomeMore() const;
bool uploadMoreRequired() const;
Ui::SlideWrap<ScanButton> *nonDeletedErrorRow() const;
rpl::producer<QString> uploadButtonText() const;
void toggleError(bool shown);
void hideError();
void errorAnimationCallback();
void updateScan(ScanInfo &&info, int width);
void pushScan(const ScanInfo &info);
not_null<PanelController*> controller;
std::vector<ScanInfo> files;
base::optional<int> initialCount;
QString errorMissing;
QPointer<Ui::SlideWrap<BoxContentDivider>> divider;
QPointer<Ui::SlideWrap<Ui::FlatLabel>> header;
QPointer<Ui::SlideWrap<Ui::FlatLabel>> uploadMoreError;
QPointer<Ui::VerticalLayout> wrap;
std::vector<base::unique_qptr<Ui::SlideWrap<ScanButton>>> rows;
QPointer<Info::Profile::Button> upload;
rpl::event_stream<rpl::producer<QString>> uploadTexts;
bool errorShown = false;
Animation errorAnimation;
};
List &list(FileType type);
const List &list(FileType type) const;
void setupScans(const QString &header); void setupScans(const QString &header);
void setupSpecialScans(std::map<SpecialFile, ScanInfo> &&files); void setupList(
not_null<Ui::VerticalLayout*> container,
FileType type,
const QString &header);
void setupSpecialScans(std::map<FileType, ScanInfo> &&files);
void init(); void init();
void chooseScan(); void chooseScan(FileType type);
void chooseSpecialScan(SpecialFile type);
void updateScan(ScanInfo &&info); void updateScan(ScanInfo &&info);
void updateSpecialScan(SpecialFile type, ScanInfo &&info); void updateSpecialScan(ScanInfo &&info);
void updateFileRow(
not_null<ScanButton*> button,
const ScanInfo &info);
void pushScan(const ScanInfo &info);
void createSpecialScanRow( void createSpecialScanRow(
SpecialScan &scan, SpecialScan &scan,
const ScanInfo &info, const ScanInfo &info,
@ -88,43 +126,27 @@ private:
not_null<Ui::VerticalLayout*> parent, not_null<Ui::VerticalLayout*> parent,
const ScanInfo &info, const ScanInfo &info,
const QString &name); const QString &name);
SpecialScan &findSpecialScan(SpecialFile type); SpecialScan &findSpecialScan(FileType type);
rpl::producer<QString> uploadButtonText() const;
void updateErrorLabels(); void updateErrorLabels();
void toggleError(bool shown);
void hideError();
void errorAnimationCallback();
bool uploadedSomeMore() const;
bool somethingChanged() const; bool somethingChanged() const;
void toggleSpecialScanError(SpecialFile type, bool shown); void toggleSpecialScanError(FileType type, bool shown);
void hideSpecialScanError(SpecialFile type); void hideSpecialScanError(FileType type);
void specialScanErrorAnimationCallback(SpecialFile type); void specialScanErrorAnimationCallback(FileType type);
void specialScanChanged(SpecialFile type, bool changed); void specialScanChanged(FileType type, bool changed);
not_null<PanelController*> _controller; not_null<PanelController*> _controller;
std::vector<ScanInfo> _files;
int _initialCount = 0;
QString _error; QString _error;
QString _errorMissing;
object_ptr<Ui::VerticalLayout> _content; object_ptr<Ui::VerticalLayout> _content;
QPointer<Ui::SlideWrap<BoxContentDivider>> _divider;
QPointer<Ui::SlideWrap<Ui::FlatLabel>> _header;
QPointer<Ui::SlideWrap<Ui::FlatLabel>> _commonError; QPointer<Ui::SlideWrap<Ui::FlatLabel>> _commonError;
QPointer<Ui::SlideWrap<Ui::FlatLabel>> _uploadMoreError;
QPointer<Ui::VerticalLayout> _wrap;
std::vector<base::unique_qptr<Ui::SlideWrap<ScanButton>>> _rows;
QPointer<Info::Profile::Button> _upload;
rpl::event_stream<rpl::producer<QString>> _uploadTexts;
bool _scanFieldsChanged = false; bool _scanFieldsChanged = false;
bool _specialScanChanged = false; bool _specialScanChanged = false;
bool _errorShown = false;
Animation _errorAnimation;
std::map<SpecialFile, SpecialScan> _specialScans; List _scansList;
std::map<FileType, SpecialScan> _specialScans;
List _translationsList;
}; };