From f76a2bc224cf780328b4f74266ea0ce9516dc773 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 14 Aug 2018 18:27:47 +0300 Subject: [PATCH] Add translations support to passport. --- Telegram/Resources/langs/lang.strings | 5 +- .../passport/passport_form_controller.cpp | 616 +++++++++++------- .../passport/passport_form_controller.h | 84 ++- .../passport_form_view_controller.cpp | 45 +- .../passport/passport_form_view_controller.h | 1 - .../passport/passport_panel_controller.cpp | 264 ++++---- .../passport/passport_panel_controller.h | 18 +- .../passport/passport_panel_edit_document.cpp | 45 +- .../passport/passport_panel_edit_document.h | 27 +- .../passport/passport_panel_edit_scans.cpp | 585 ++++++++++------- .../passport/passport_panel_edit_scans.h | 100 +-- 11 files changed, 998 insertions(+), 792 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index b26f93277..ea7ed1d32 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1620,9 +1620,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_passport_choose_image" = "Choose scan image"; "lng_passport_delete_scan_undo" = "Undo"; "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_last_name" = "Surname"; +"lng_passport_last_name" = "Last name"; "lng_passport_birth_date" = "Date of birth"; "lng_passport_gender" = "Gender"; "lng_passport_gender_male" = "Male"; @@ -1638,6 +1638,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_passport_city" = "City"; "lng_passport_state" = "State"; "lng_passport_postcode" = "Postcode"; +"lng_passport_translation" = "Translation"; "lng_passport_use_existing" = "USE {existing}"; "lng_passport_use_existing_phone" = "Use the same phone number as on Telegram."; "lng_passport_new_phone" = "Or enter a new phone number"; diff --git a/Telegram/SourceFiles/passport/passport_form_controller.cpp b/Telegram/SourceFiles/passport/passport_form_controller.cpp index 37ed0835b..d1c49dc90 100644 --- a/Telegram/SourceFiles/passport/passport_form_controller.cpp +++ b/Telegram/SourceFiles/passport/passport_form_controller.cpp @@ -30,6 +30,7 @@ namespace Passport { namespace { constexpr auto kDocumentScansLimit = 20; +constexpr auto kTranslationScansLimit = 20; constexpr auto kShortPollTimeout = TimeMs(3000); constexpr auto kRememberCredentialsDelay = TimeMs(1800 * 1000); @@ -209,11 +210,11 @@ QString ValueCredentialsKey(Value::Type type) { Unexpected("Type in ValueCredentialsKey."); } -QString SpecialScanCredentialsKey(SpecialFile type) { +QString SpecialScanCredentialsKey(FileType type) { switch (type) { - case SpecialFile::FrontSide: return "front_side"; - case SpecialFile::ReverseSide: return "reverse_side"; - case SpecialFile::Selfie: return "selfie"; + case FileType::FrontSide: return "front_side"; + case FileType::ReverseSide: return "reverse_side"; + case FileType::Selfie: return "selfie"; } Unexpected("Type in SpecialScanCredentialsKey."); } @@ -236,7 +237,12 @@ bool ValueChanged(not_null value, const ValueMap &data) { }; 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)) { return true; } @@ -277,9 +283,11 @@ FormRequest::FormRequest( EditFile::EditFile( not_null value, + FileType type, const File &fields, std::unique_ptr &&uploadData) : value(value) +, type(type) , fields(std::move(fields)) , uploadData(std::move(uploadData)) , guard(std::make_shared(true)) { @@ -326,17 +334,31 @@ RequestedValue::RequestedValue(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) { - case SpecialFile::FrontSide: + case FileType::FrontSide: return (this->type == Type::Passport) || (this->type == Type::DriverLicense) || (this->type == Type::IdentityCard) || (this->type == Type::InternalPassport); - case SpecialFile::ReverseSide: + case FileType::ReverseSide: return (this->type == Type::DriverLicense) || (this->type == Type::IdentityCard); - case SpecialFile::Selfie: + case FileType::Selfie: return selfieRequired; } Unexpected("Special scan type in requiresSpecialScan."); @@ -357,15 +379,15 @@ void Value::fillDataFrom(Value &&other) { } bool Value::scansAreFilled() const { - if (!requiresSpecialScan(SpecialFile::FrontSide) && scans.empty()) { + if (requiresScan(FileType::Translation) && _translations.empty()) { return false; - } else if (translationRequired && translations.empty()) { + } else if (requiresScan(FileType::Scan) && _scans.empty()) { return false; } const auto types = { - SpecialFile::FrontSide, - SpecialFile::ReverseSide, - SpecialFile::Selfie + FileType::FrontSide, + FileType::ReverseSide, + FileType::Selfie }; for (const auto type : types) { if (requiresSpecialScan(type) @@ -376,6 +398,163 @@ bool Value::scansAreFilled() const { 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 &Value::files(FileType type) { + switch (type) { + case FileType::Scan: return _scans; + case FileType::Translation: return _translations; + } + Unexpected("Type in Value::files()."); +} + +const std::vector &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 &Value::filesInEdit(FileType type) { + switch (type) { + case FileType::Scan: return _scansInEdit; + case FileType::Translation: return _translationsInEdit; + } + Unexpected("Type in Value::filesInEdit()."); +} + +const std::vector &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 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 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 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( not_null controller, const FormRequest &request) @@ -417,12 +596,20 @@ auto FormController::prepareFinalData() -> FinalData { { "secret", value->data.secret } })); } - if (!value->scans.empty()) { - auto files = QJsonArray(); - for (const auto &scan : value->scans) { - files.append(GetJSONFromFile(scan)); + const auto addList = [&]( + const QString &key, + const std::vector &list) { + 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) { if (value->requiresSpecialScan(type)) { @@ -894,26 +1081,21 @@ void FormController::fillErrors() { LOG(("API Error: Value not found for error type.")); return nullptr; }; - using List = std::vector; - const auto findScan = [&](List &list, bytes::const_span hash) -> File* { + const auto scan = [&]( + Value &value, + FileType type, + bytes::const_span hash) -> File* { + auto &list = value.files(type); const auto i = ranges::find_if(list, [&](const File &scan) { return !bytes::compare(hash, scan.hash); }); if (i != end(list)) { 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.")); return nullptr; }; - const auto setSpecialScanError = [&](SpecialFile type, auto &&data) { + const auto setSpecialScanError = [&](FileType type, auto &&data) { if (const auto value = find(data.vtype)) { if (value->requiresSpecialScan(type)) { const auto i = value->specialScans.find(type); @@ -945,37 +1127,40 @@ void FormController::fillErrors() { }, [&](const MTPDsecureValueErrorFile &data) { const auto hash = bytes::make_span(data.vfile_hash.v); if (const auto value = find(data.vtype)) { - if (const auto file = scan(*value, hash)) { - file->error = qs(data.vtext); + if (const auto file = scan(*value, FileType::Scan, hash)) { + if (value->requiresScan(FileType::Scan)) { + file->error = qs(data.vtext); + } } } }, [&](const MTPDsecureValueErrorFiles &data) { if (const auto value = find(data.vtype)) { - if (CanRequireScans(value->type)) { - value->scanMissingError = qs(data.vtext); + if (value->requiresScan(FileType::Scan)) { + value->fileMissingError(FileType::Scan) + = qs(data.vtext); } } }, [&](const MTPDsecureValueErrorTranslationFile &data) { const auto hash = bytes::make_span(data.vfile_hash.v); if (const auto value = find(data.vtype)) { - if (value->translationRequired) { - if (const auto file = scan(*value, hash)) { - file->error = qs(data.vtext); - } + const auto file = scan(*value, FileType::Translation, hash); + if (file && value->requiresScan(FileType::Translation)) { + file->error = qs(data.vtext); } } }, [&](const MTPDsecureValueErrorTranslationFiles &data) { if (const auto value = find(data.vtype)) { - if (value->translationRequired) { - value->translationMissingError = qs(data.vtext); + if (value->requiresScan(FileType::Translation)) { + value->fileMissingError(FileType::Translation) + = qs(data.vtext); } } }, [&](const MTPDsecureValueErrorFrontSide &data) { - setSpecialScanError(SpecialFile::FrontSide, data); + setSpecialScanError(FileType::FrontSide, data); }, [&](const MTPDsecureValueErrorReverseSide &data) { - setSpecialScanError(SpecialFile::ReverseSide, data); + setSpecialScanError(FileType::ReverseSide, data); }, [&](const MTPDsecureValueErrorSelfie &data) { - setSpecialScanError(SpecialFile::Selfie, data); + setSpecialScanError(FileType::Selfie, data); }); } } @@ -1027,13 +1212,18 @@ bool FormController::validateValueSecrets(Value &value) const { } return true; }; - for (auto &scan : value.scans) { + for (auto &scan : value.files(FileType::Scan)) { if (!validateFileSecret(scan)) { return false; } } - for (auto &[type, file] : value.specialScans) { - if (!validateFileSecret(file)) { + for (auto &scan : value.files(FileType::Translation)) { + if (!validateFileSecret(scan)) { + return false; + } + } + for (auto &[type, scan] : value.specialScans) { + if (!validateFileSecret(scan)) { return false; } } @@ -1054,73 +1244,51 @@ const PasswordSettings &FormController::passwordSettings() const { void FormController::uploadScan( not_null value, + FileType type, QByteArray &&content) { - if (!canAddScan(value)) { + if (!canAddScan(value, type)) { _view->showToast(lang(lng_passport_scans_limit_reached)); return; } const auto nonconst = findValue(value); - auto scanIndex = int(nonconst->scansInEdit.size()); - nonconst->scansInEdit.emplace_back( - nonconst, - File(), - nullptr); - auto &scan = nonconst->scansInEdit.back(); + const auto fileIndex = [&]() -> base::optional { + auto scanInEdit = EditFile{ nonconst, type, File(), nullptr }; + if (type == FileType::Scan || type == FileType::Translation) { + auto &list = nonconst->filesInEdit(type); + auto scanIndex = int(list.size()); + 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) { - Expects(scanIndex >= 0 && scanIndex < nonconst->scansInEdit.size()); - uploadEncryptedFile( - nonconst->scansInEdit[scanIndex], + nonconst->fileInEdit(type, fileIndex), std::move(result)); }); } void FormController::deleteScan( not_null value, - int scanIndex) { - scanDeleteRestore(value, scanIndex, true); + FileType type, + base::optional fileIndex) { + scanDeleteRestore(value, type, fileIndex, true); } void FormController::restoreScan( not_null value, - int scanIndex) { - scanDeleteRestore(value, scanIndex, false); -} - -void FormController::uploadSpecialScan( - not_null 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 value, - SpecialFile type) { - specialScanDeleteRestore(value, type, true); -} - -void FormController::restoreSpecialScan( - not_null value, - SpecialFile type) { - specialScanDeleteRestore(value, type, false); + FileType type, + base::optional fileIndex) { + scanDeleteRestore(value, type, fileIndex, false); } void FormController::prepareFile( @@ -1173,14 +1341,13 @@ void FormController::encryptFile( void FormController::scanDeleteRestore( not_null value, - int scanIndex, + FileType type, + base::optional fileIndex, bool deleted) { - Expects(scanIndex >= 0 && scanIndex < value->scansInEdit.size()); - const auto nonconst = findValue(value); - auto &scan = nonconst->scansInEdit[scanIndex]; + auto &scan = nonconst->fileInEdit(type, fileIndex); if (scan.deleted && !deleted) { - if (!canAddScan(value)) { + if (!canAddScan(value, type)) { _view->showToast(lang(lng_passport_scans_limit_reached)); return; } @@ -1189,23 +1356,21 @@ void FormController::scanDeleteRestore( _scanUpdated.fire(&scan); } -void FormController::specialScanDeleteRestore( +bool FormController::canAddScan( not_null value, - SpecialFile type, - bool deleted) { - const auto nonconst = findValue(value); - const auto i = nonconst->specialScansInEdit.find(type); - Assert(i != nonconst->specialScansInEdit.end()); - auto &scan = i->second; - scan.deleted = deleted; - _scanUpdated.fire(&scan); -} - -bool FormController::canAddScan(not_null value) const { + FileType type) const { + const auto limit = (type == FileType::Scan) + ? kDocumentScansLimit + : (type == FileType::Translation) + ? kTranslationScansLimit + : -1; + if (limit < 0) { + return true; + } const auto scansCount = ranges::count_if( - value->scansInEdit, + value->filesInEdit(type), [](const EditFile &scan) { return !scan.deleted; }); - return (scansCount < kDocumentScansLimit); + return (scansCount < limit); } void FormController::subscribeToUploader() { @@ -1411,14 +1576,14 @@ not_null FormController::findValue(not_null value) { void FormController::startValueEdit(not_null value) { const auto nonconst = findValue(value); ++nonconst->editScreens; - if (savingValue(nonconst)) { + if (nonconst->saving()) { return; } - for (auto &scan : nonconst->scans) { + for (auto &scan : nonconst->files(FileType::Scan)) { loadFile(scan); } if (nonconst->translationRequired) { - for (auto &scan : nonconst->translations) { + for (auto &scan : nonconst->files(FileType::Translation)) { loadFile(scan); } } @@ -1427,27 +1592,7 @@ void FormController::startValueEdit(not_null value) { loadFile(scan); } } - nonconst->scansInEdit = ranges::view::all( - 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; + nonconst->saveInEdit(); } void FormController::loadFile(File &file) { @@ -1531,41 +1676,6 @@ void FormController::fileLoadFail(FileKey key) { } } -bool FormController::savingValue(not_null value) const { - return (value->saveRequestId != 0) - || (value->verification.requestId != 0) - || (value->verification.codeLength != 0) - || uploadingScan(value); -} - -bool FormController::uploadingScan(not_null value) const { - const auto uploading = [](const EditFile &file) { - return file.uploadData - && file.uploadData->fullId - && !file.deleted; - }; - if (ranges::find_if(value->scansInEdit, uploading) - != end(value->scansInEdit)) { - return true; - } - if (ranges::find_if(value->specialScansInEdit, [&](const auto &pair) { - return uploading(pair.second); - }) != end(value->specialScansInEdit)) { - return true; - } - for (const auto &scan : value->scansInEdit) { - if (uploading(scan)) { - return true; - } - } - for (const auto &[type, scan] : value->specialScansInEdit) { - if (uploading(scan)) { - return true; - } - } - return false; -} - void FormController::cancelValueEdit(not_null value) { Expects(value->editScreens > 0); @@ -1575,7 +1685,7 @@ void FormController::cancelValueEdit(not_null value) { } void FormController::valueEditFailed(not_null value) { - Expects(!savingValue(value)); + Expects(!value->saving()); if (value->editScreens == 0) { clearValueEdit(value); @@ -1583,20 +1693,16 @@ void FormController::valueEditFailed(not_null value) { } void FormController::clearValueEdit(not_null value) { - if (savingValue(value)) { + if (value->saving()) { return; } - value->scansInEdit.clear(); - value->specialScansInEdit.clear(); - value->data.encryptedSecretInEdit.clear(); - value->data.hashInEdit.clear(); - value->data.parsedInEdit = ValueMap(); + value->clearEditData(); } void FormController::cancelValueVerification(not_null value) { const auto nonconst = findValue(value); clearValueVerification(nonconst); - if (!savingValue(nonconst)) { + if (!nonconst->saving()) { valueEditFailed(nonconst); } } @@ -1619,7 +1725,7 @@ bool FormController::isEncryptedValue(Value::Type type) const { void FormController::saveValueEdit( not_null value, ValueMap &&data) { - if (savingValue(value) || _submitRequestId) { + if (value->saving() || _submitRequestId) { return; } @@ -1630,11 +1736,7 @@ void FormController::saveValueEdit( if (!ValueChanged(nonconst, data)) { nonconst->saveRequestId = -1; crl::on_main(this, [=] { - base::take(nonconst->scansInEdit); - base::take(nonconst->specialScansInEdit); - base::take(nonconst->data.encryptedSecretInEdit); - base::take(nonconst->data.hashInEdit); - base::take(nonconst->data.parsedInEdit); + nonconst->clearEditData(); nonconst->saveRequestId = 0; _valueSaveFinished.fire_copy(nonconst); }); @@ -1650,7 +1752,7 @@ void FormController::saveValueEdit( } void FormController::deleteValueEdit(not_null value) { - if (savingValue(value) || _submitRequestId) { + if (value->saving() || _submitRequestId) { return; } @@ -1689,24 +1791,21 @@ void FormController::saveEncryptedValue(not_null value) { MTP_long(file.fields.id), MTP_long(file.fields.accessHash)); }; - - auto files = QVector(); - files.reserve(value->scansInEdit.size()); - for (const auto &scan : value->scansInEdit) { - if (scan.deleted) { - continue; + const auto wrapList = [&](not_null value, FileType type) { + const auto &list = value->filesInEdit(type); + auto result = QVector(); + result.reserve(list.size()); + for (const auto &scan : value->filesInEdit(type)) { + if (scan.deleted) { + continue; + } + result.push_back(wrapFile(scan)); } - files.push_back(wrapFile(scan)); - } + return result; + }; - auto translations = QVector(); - translations.reserve(value->translationsInEdit.size()); - for (const auto &scan : value->translationsInEdit) { - if (scan.deleted) { - continue; - } - translations.push_back(wrapFile(scan)); - } + const auto files = wrapList(value, FileType::Scan); + const auto translations = wrapList(value, FileType::Translation); if (value->data.secret.empty()) { value->data.secret = GenerateSecretBytes(); @@ -1720,37 +1819,37 @@ void FormController::saveEncryptedValue(not_null value) { _secret, value->data.hashInEdit); - const auto hasSpecialFile = [&](SpecialFile type) { + const auto hasSpecialFile = [&](FileType type) { const auto i = value->specialScansInEdit.find(type); 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); return (i != end(value->specialScansInEdit) && !i->second.deleted) ? wrapFile(i->second) : MTPInputSecureFile(); }; - const auto frontSide = specialFile(SpecialFile::FrontSide); - const auto reverseSide = specialFile(SpecialFile::ReverseSide); - const auto selfie = specialFile(SpecialFile::Selfie); + const auto frontSide = specialFile(FileType::FrontSide); + const auto reverseSide = specialFile(FileType::ReverseSide); + const auto selfie = specialFile(FileType::Selfie); const auto type = ConvertType(value->type); const auto flags = (value->data.parsedInEdit.fields.empty() ? MTPDinputSecureValue::Flag(0) : MTPDinputSecureValue::Flag::f_data) - | (hasSpecialFile(SpecialFile::FrontSide) + | (hasSpecialFile(FileType::FrontSide) ? MTPDinputSecureValue::Flag::f_front_side : MTPDinputSecureValue::Flag(0)) - | (hasSpecialFile(SpecialFile::ReverseSide) + | (hasSpecialFile(FileType::ReverseSide) ? MTPDinputSecureValue::Flag::f_reverse_side : MTPDinputSecureValue::Flag(0)) - | (hasSpecialFile(SpecialFile::Selfie) + | (hasSpecialFile(FileType::Selfie) ? MTPDinputSecureValue::Flag::f_selfie : MTPDinputSecureValue::Flag(0)) - | (value->translationsInEdit.empty() + | (translations.empty() ? MTPDinputSecureValue::Flag(0) : MTPDinputSecureValue::Flag::f_translation) - | (value->scansInEdit.empty() + | (files.empty() ? MTPDinputSecureValue::Flag(0) : MTPDinputSecureValue::Flag::f_files); Assert(flags != MTPDinputSecureValue::Flags(0)); @@ -1809,10 +1908,7 @@ void FormController::sendSaveRequest( data, MTP_long(_secretId) )).done([=](const MTPSecureValue &result) { - auto scansInEdit = base::take(value->scansInEdit); - for (auto &[type, scan] : base::take(value->specialScansInEdit)) { - scansInEdit.push_back(std::move(scan)); - } + auto scansInEdit = value->takeAllFilesInEdit(); auto refreshed = parseValue(result, scansInEdit); decryptValue(refreshed); @@ -2157,23 +2253,28 @@ auto FormController::parseValue( result.data.encryptedSecret = bytes::make_vector(fields.vsecret.v); } 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 = [&]( - SpecialFile type, + FileType type, const MTPSecureFile &file) { if (auto parsed = parseFile(file, editData)) { result.specialScans.emplace(type, std::move(*parsed)); } }; if (data.has_front_side()) { - parseSpecialScan(SpecialFile::FrontSide, data.vfront_side); + parseSpecialScan(FileType::FrontSide, data.vfront_side); } if (data.has_reverse_side()) { - parseSpecialScan(SpecialFile::ReverseSide, data.vreverse_side); + parseSpecialScan(FileType::ReverseSide, data.vreverse_side); } if (data.has_selfie()) { - parseSpecialScan(SpecialFile::Selfie, data.vselfie); + parseSpecialScan(FileType::Selfie, data.vselfie); } if (data.has_plain_data()) { switch (data.vplain_data.type()) { @@ -2190,18 +2291,25 @@ auto FormController::parseValue( return result; } -auto FormController::findEditFile(const FullMsgId &fullId) -> EditFile* { - const auto found = [&](const EditFile &file) { - return (file.uploadData && file.uploadData->fullId == fullId); - }; - for (auto &[type, value] : _form.values) { - for (auto &scan : value.scansInEdit) { - if (found(scan)) { - return &scan; +template +EditFile *FormController::findEditFileByCondition(Condition &&condition) { + for (auto &pair : _form.values) { + auto &value = pair.second; + const auto foundInList = [&](FileType type) -> EditFile* { + for (auto &scan : value.filesInEdit(type)) { + if (condition(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) { - if (found(scan)) { + if (condition(scan)) { return &scan; } } @@ -2209,23 +2317,16 @@ auto FormController::findEditFile(const FullMsgId &fullId) -> EditFile* { return nullptr; } -auto FormController::findEditFile(const FileKey &key) -> EditFile* { - const auto found = [&](const EditFile &file) { +EditFile *FormController::findEditFile(const FullMsgId &fullId) { + 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); - }; - 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) @@ -2233,11 +2334,20 @@ auto FormController::findFile(const FileKey &key) const auto found = [&](const File &file) { return (file.dcId == key.dcId) && (file.id == key.id); }; - for (auto &[type, value] : _form.values) { - for (auto &scan : value.scans) { - if (found(scan)) { - return { &value, &scan }; + for (auto &pair : _form.values) { + auto &value = pair.second; + const auto foundInList = [&](FileType type) -> File* { + 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) { if (found(scan)) { diff --git a/Telegram/SourceFiles/passport/passport_form_controller.h b/Telegram/SourceFiles/passport/passport_form_controller.h index cb63b8184..60cfd8dc9 100644 --- a/Telegram/SourceFiles/passport/passport_form_controller.h +++ b/Telegram/SourceFiles/passport/passport_form_controller.h @@ -81,6 +81,14 @@ private: struct Value; +enum class FileType { + Scan, + Translation, + FrontSide, + ReverseSide, + Selfie, +}; + struct File { uint64 id = 0; uint64 accessHash = 0; @@ -99,10 +107,12 @@ struct File { struct EditFile { EditFile( not_null value, + FileType type, const File &fields, std::unique_ptr &&uploadData); not_null value; + FileType type; File fields; UploadScanDataPointer uploadData; std::shared_ptr guard; @@ -141,12 +151,6 @@ struct Verification { struct Form; -enum class SpecialFile { - FrontSide, - ReverseSide, - Selfie, -}; - struct Value { enum class Type { PersonalDetails, @@ -172,20 +176,32 @@ struct Value { // It should be preserved through re-parsing (for example when saving). // So we hide "operator=(Value&&)" in private and instead provide this. void fillDataFrom(Value &&other); - bool requiresSpecialScan(SpecialFile type) const; + bool requiresSpecialScan(FileType type) const; + bool requiresScan(FileType type) const; bool scansAreFilled() const; + void saveInEdit(); + void clearEditData(); + bool uploadingScan() const; + bool saving() const; + + std::vector &files(FileType type); + const std::vector &files(FileType type) const; + QString &fileMissingError(FileType type); + const QString &fileMissingError(FileType type) const; + std::vector &filesInEdit(FileType type); + const std::vector &filesInEdit(FileType type) const; + EditFile &fileInEdit(FileType type, base::optional fileIndex); + const EditFile &fileInEdit( + FileType type, + base::optional fileIndex) const; + + std::vector takeAllFilesInEdit(); Type type; ValueData data; - std::vector scans; - std::vector translations; - std::map specialScans; + std::map specialScans; QString error; - QString scanMissingError; - QString translationMissingError; - std::vector scansInEdit; - std::vector translationsInEdit; - std::map specialScansInEdit; + std::map specialScansInEdit; Verification verification; bytes::vector submitHash; @@ -199,6 +215,13 @@ struct Value { private: Value &operator=(Value &&other) = default; + std::vector _scans; + std::vector _translations; + std::vector _scansInEdit; + std::vector _translationsInEdit; + QString _scanMissingError; + QString _translationMissingError; + }; bool ValueChanged(not_null value, const ValueMap &data); @@ -299,20 +322,19 @@ public: void reloadAndSubmitPassword(const QByteArray &password); void cancelPassword(); - bool canAddScan(not_null value) const; - void uploadScan(not_null value, QByteArray &&content); - void deleteScan(not_null value, int fileIndex); - void restoreScan(not_null value, int fileIndex); - void uploadSpecialScan( + bool canAddScan(not_null value, FileType type) const; + void uploadScan( not_null value, - SpecialFile type, + FileType type, QByteArray &&content); - void deleteSpecialScan( + void deleteScan( not_null value, - SpecialFile type); - void restoreSpecialScan( + FileType type, + base::optional fileIndex); + void restoreScan( not_null value, - SpecialFile type); + FileType type, + base::optional fileIndex); rpl::producer<> secretReadyEvents() const; @@ -331,8 +353,6 @@ public: void cancelValueVerification(not_null value); void saveValueEdit(not_null value, ValueMap &&data); void deleteValueEdit(not_null value); - bool savingValue(not_null value) const; - bool uploadingScan(not_null value) const; void cancel(); void cancelSure(); @@ -350,6 +370,9 @@ private: QByteArray credentials; std::vector> errors; }; + + template + EditFile *findEditFileByCondition(Condition &&condition); EditFile *findEditFile(const FullMsgId &fullId); EditFile *findEditFile(const FileKey &key); std::pair findFile(const FileKey &key); @@ -432,11 +455,8 @@ private: void scanUploadFail(const FullMsgId &fullId); void scanDeleteRestore( not_null value, - int fileIndex, - bool deleted); - void specialScanDeleteRestore( - not_null value, - SpecialFile type, + FileType type, + base::optional fileIndex, bool deleted); QString getPhoneFromValue(not_null value) const; diff --git a/Telegram/SourceFiles/passport/passport_form_view_controller.cpp b/Telegram/SourceFiles/passport/passport_form_view_controller.cpp index 40c2839e1..4142e33b8 100644 --- a/Telegram/SourceFiles/passport/passport_form_view_controller.cpp +++ b/Telegram/SourceFiles/passport/passport_form_view_controller.cpp @@ -159,14 +159,17 @@ bool ValidateForm(const Form &form) { LOG(("API Error: Bad value requiring native names.")); return false; } - if (!CanRequireScans(value.type)) { - Assert(value.scanMissingError.isEmpty()); - } - if (!value.translationRequired) { - for (const auto &scan : value.translations) { + if (!value.requiresScan(FileType::Scan)) { + for (const auto &scan : value.files(FileType::Scan)) { 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) { if (!value.requiresSpecialScan(type)) { @@ -278,6 +281,9 @@ QString ComputeScopeRowReadyString(const Scope &scope) { } return nullptr; }(); + if (!scope.documents.empty() && !document) { + return QString(); + } if ((document && scope.documents.size() > 1) || (!scope.details && (ScopeTypeForValueType(document->type) @@ -307,9 +313,6 @@ QString ComputeScopeRowReadyString(const Scope &scope) { } }()); } - if (!scope.documents.empty() && !document) { - return QString(); - } const auto scheme = GetDocumentScheme( scope.type, document ? base::make_optional(document->type) : base::none, @@ -372,22 +375,18 @@ ScopeRow ComputeScopeRow(const Scope &scope) { if (!value->error.isEmpty()) { errors.push_back(value->error); } - if (!value->scanMissingError.isEmpty()) { - errors.push_back(value->scanMissingError); - } - if (!value->translationMissingError.isEmpty()) { - errors.push_back(value->translationMissingError); - } - for (const auto &scan : value->scans) { - if (!scan.error.isEmpty()) { - errors.push_back(scan.error); + const auto addTypeErrors = [&](FileType type) { + if (!value->fileMissingError(type).isEmpty()) { + errors.push_back(value->fileMissingError(type)); } - } - for (const auto &scan : value->translations) { - if (!scan.error.isEmpty()) { - errors.push_back(scan.error); + for (const auto &scan : value->files(type)) { + if (!scan.error.isEmpty()) { + errors.push_back(scan.error); + } } - } + }; + addTypeErrors(FileType::Scan); + addTypeErrors(FileType::Translation); for (const auto &[type, scan] : value->specialScans) { if (!scan.error.isEmpty()) { errors.push_back(scan.error); diff --git a/Telegram/SourceFiles/passport/passport_form_view_controller.h b/Telegram/SourceFiles/passport/passport_form_view_controller.h index cf04347a7..85efd5d3c 100644 --- a/Telegram/SourceFiles/passport/passport_form_view_controller.h +++ b/Telegram/SourceFiles/passport/passport_form_view_controller.h @@ -34,7 +34,6 @@ struct ScopeRow { QString error; }; -bool CanRequireScans(Value::Type type); bool CanHaveErrors(Value::Type type); bool ValidateForm(const Form &form); std::vector ComputeScopes(const Form &form); diff --git a/Telegram/SourceFiles/passport/passport_panel_controller.cpp b/Telegram/SourceFiles/passport/passport_panel_controller.cpp index 093f6d50d..9a6677fbc 100644 --- a/Telegram/SourceFiles/passport/passport_panel_controller.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_controller.cpp @@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_boxes.h" namespace Passport { +namespace { constexpr auto kMaxNameSize = 255; constexpr auto kMaxDocumentSize = 24; @@ -32,6 +33,76 @@ constexpr auto kMinCitySize = 2; constexpr auto kMaxCitySize = 64; 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 PrepareSpecialFiles(const Value &value) { + auto result = std::map(); + 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( Scope::Type type, base::optional scansType, @@ -576,66 +647,47 @@ void PanelController::cancelPasswordSubmit() { [=] { if (*box) (*box)->closeBox(); _form->cancelPassword(); })); } -bool PanelController::canAddScan() const { +bool PanelController::canAddScan(FileType type) const { Expects(_editScope != 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(_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 fileIndex) { Expects(_editScope != 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 fileIndex) { Expects(_editScope != nullptr); Expects(_editDocument != nullptr); + Expects(_editDocument->requiresScan(type)); - _form->restoreScan(_editDocument, 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); + _form->restoreScan(_editDocument, type, fileIndex); } rpl::producer PanelController::scanUpdated() const { return _form->scanUpdated( ) | rpl::filter([=](not_null file) { return (file->value == _editDocument); - }) | rpl::map([=](not_null file) { - return collectScanInfo(*file); + }) | rpl::map([](not_null file) { + return CollectScanInfo(*file); }); } @@ -643,63 +695,8 @@ rpl::producer PanelController::saveErrors() const { 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 { - 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 PanelController::collectSaveErrors( not_null value) const { - using General = ScopeError::General; - auto result = std::vector(); for (const auto &[key, value] : value->data.parsedInEdit.fields) { if (!value.error.isEmpty()) { @@ -740,7 +737,8 @@ bool PanelController::hasValueDocument() const { return false; } return !_editDocument->data.parsed.fields.empty() - || !_editDocument->scans.empty() + || !_editDocument->files(FileType::Scan).empty() + || !_editDocument->files(FileType::Translation).empty() || !_editDocument->specialScans.empty(); } @@ -978,25 +976,22 @@ void PanelController::editWithUpload(int index, int documentIndex) { && documentIndex < _scopes[index].documents.size()); const auto document = _scopes[index].documents[documentIndex]; - const auto requiresSpecialScan = document->requiresSpecialScan( - SpecialFile::FrontSide); - const auto allowMany = !requiresSpecialScan; + const auto type = document->requiresSpecialScan(FileType::FrontSide) + ? FileType::FrontSide + : FileType::Scan; + const auto allowMany = (type == FileType::Scan); const auto widget = _panel->widget(); - EditScans::ChooseScan(widget.get(), [=](QByteArray &&content) { + EditScans::ChooseScan(widget.get(), type, [=](QByteArray &&content) { if (_scopeDocumentTypeBox) { _scopeDocumentTypeBox = BoxPointer(); } if (!_editScope || !_editDocument) { startScopeEdit(index, documentIndex); } - if (requiresSpecialScan) { - uploadSpecialScan(SpecialFile::FrontSide, std::move(content)); - } else { - uploadScan(std::move(content)); - } + uploadScan(type, std::move(content)); }, [=](ReadScanError error) { readScanError(error); - }, allowMany); + }); } void PanelController::readScanError(ReadScanError error) { @@ -1027,11 +1022,11 @@ bool PanelController::editRequiresScanUpload( return false; } const auto document = _scopes[index].documents[documentIndex]; - if (document->requiresSpecialScan(SpecialFile::FrontSide)) { + if (document->requiresSpecialScan(FileType::FrontSide)) { 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) { @@ -1068,6 +1063,14 @@ void PanelController::startScopeEdit(int index, int documentIndex) { case Scope::Type::Identity: case Scope::Type::Address: { 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 ? object_ptr( _panel->widget(), @@ -1080,9 +1083,9 @@ void PanelController::startScopeEdit(int index, int documentIndex) { _editValue->data.parsedInEdit, _editDocument->error, _editDocument->data.parsedInEdit, - _editDocument->scanMissingError, - valueFiles(*_editDocument), - valueSpecialFiles(*_editDocument)) + std::move(scans), + std::move(translations), + PrepareSpecialFiles(*_editDocument)) : object_ptr( _panel->widget(), this, @@ -1092,9 +1095,9 @@ void PanelController::startScopeEdit(int index, int documentIndex) { false), _editDocument->error, _editDocument->data.parsedInEdit, - _editDocument->scanMissingError, - valueFiles(*_editDocument), - valueSpecialFiles(*_editDocument)); + std::move(scans), + std::move(translations), + PrepareSpecialFiles(*_editDocument)); const auto weak = make_weak(result.data()); _panelHasUnsavedChanges = [=] { return weak ? weak->hasUnsavedChanges() : false; @@ -1183,13 +1186,13 @@ void PanelController::processValueSaveFinished( } bool PanelController::uploadingScopeScan() const { - return (_editValue && _form->uploadingScan(_editValue)) - || (_editDocument && _form->uploadingScan(_editDocument)); + return (_editValue && _editValue->uploadingScan()) + || (_editDocument && _editDocument->uploadingScan()); } bool PanelController::savingScope() const { - return (_editValue && _form->savingValue(_editValue)) - || (_editDocument && _form->savingValue(_editDocument)); + return (_editValue && _editValue->saving()) + || (_editDocument && _editDocument->saving()); } void PanelController::processVerificationNeeded( @@ -1253,37 +1256,6 @@ void PanelController::processVerificationNeeded( _verificationBoxes.emplace(value, box); } -std::vector PanelController::valueFiles( - const Value &value) const { - auto result = std::vector(); - for (const auto &scan : value.scansInEdit) { - result.push_back(collectScanInfo(scan)); - } - return result; -} - -std::map PanelController::valueSpecialFiles( - const Value &value) const { - auto result = std::map(); - 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() { Expects(_editScope != nullptr); diff --git a/Telegram/SourceFiles/passport/passport_panel_controller.h b/Telegram/SourceFiles/passport/passport_panel_controller.h index 329813d20..074176149 100644 --- a/Telegram/SourceFiles/passport/passport_panel_controller.h +++ b/Telegram/SourceFiles/passport/passport_panel_controller.h @@ -36,9 +36,8 @@ struct ScanInfo { QString status; QImage thumb; bool deleted = false; - base::optional special; + FileType type = FileType(); QString error; - }; struct ScopeError { @@ -88,13 +87,10 @@ public: void setupPassword(); void cancelPasswordSubmit(); - bool canAddScan() const; - void uploadScan(QByteArray &&content); - void deleteScan(int fileIndex); - void restoreScan(int fileIndex); - void uploadSpecialScan(SpecialFile type, QByteArray &&content); - void deleteSpecialScan(SpecialFile type); - void restoreSpecialScan(SpecialFile type); + bool canAddScan(FileType type) const; + void uploadScan(FileType type, QByteArray &&content); + void deleteScan(FileType type, base::optional fileIndex); + void restoreScan(FileType type, base::optional fileIndex); rpl::producer scanUpdated() const; rpl::producer saveErrors() const; void readScanError(ReadScanError error); @@ -151,8 +147,7 @@ private: int findNonEmptyDocumentIndex(const Scope &scope) const; void requestScopeFilesType(int index); void cancelValueEdit(); - std::vector valueFiles(const Value &value) const; - std::map valueSpecialFiles( + std::map valueSpecialFiles( const Value &value) const; void processValueSaveFinished(not_null value); void processVerificationNeeded(not_null value); @@ -161,7 +156,6 @@ private: bool uploadingScopeScan() const; bool hasValueDocument() const; bool hasValueFields() const; - ScanInfo collectScanInfo(const EditFile &file) const; std::vector collectSaveErrors( not_null value) const; QString getDefaultContactValue(Scope::Type type) const; diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp b/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp index b75b1e248..f7551e120 100644 --- a/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp @@ -214,9 +214,9 @@ PanelEditDocument::PanelEditDocument( const ValueMap &data, const QString &scansError, const ValueMap &scansData, - const QString &missingScansError, - std::vector &&files, - std::map &&specialFiles) + ScanListData &&scans, + base::optional &&translations, + std::map &&specialFiles) : _controller(controller) , _scheme(std::move(scheme)) , _scroll(this, st::passportPanelScroll) @@ -231,8 +231,8 @@ PanelEditDocument::PanelEditDocument( &data, &scansError, &scansData, - missingScansError, - std::move(files), + std::move(scans), + std::move(translations), std::move(specialFiles)); } @@ -242,9 +242,9 @@ PanelEditDocument::PanelEditDocument( Scheme scheme, const QString &scansError, const ValueMap &scansData, - const QString &missingScansError, - std::vector &&files, - std::map &&specialFiles) + ScanListData &&scans, + base::optional &&translations, + std::map &&specialFiles) : _controller(controller) , _scheme(std::move(scheme)) , _scroll(this, st::passportPanelScroll) @@ -259,8 +259,8 @@ PanelEditDocument::PanelEditDocument( nullptr, &scansError, &scansData, - missingScansError, - std::move(files), + std::move(scans), + std::move(translations), std::move(specialFiles)); } @@ -279,7 +279,7 @@ PanelEditDocument::PanelEditDocument( this, langFactory(lng_passport_save_value), st::passportPanelSaveValue) { - setupControls(&error, &data, nullptr, nullptr, QString(), {}, {}); + setupControls(&error, &data, nullptr, nullptr, {}, {}, {}); } void PanelEditDocument::setupControls( @@ -287,16 +287,16 @@ void PanelEditDocument::setupControls( const ValueMap *data, const QString *scansError, const ValueMap *scansData, - const QString &missingScansError, - std::vector &&files, - std::map &&specialFiles) { + ScanListData &&scans, + base::optional &&translations, + std::map &&specialFiles) { const auto inner = setupContent( error, data, scansError, scansData, - missingScansError, - std::move(files), + std::move(scans), + std::move(translations), std::move(specialFiles)); using namespace rpl::mappers; @@ -315,9 +315,9 @@ not_null PanelEditDocument::setupContent( const ValueMap *data, const QString *scansError, const ValueMap *scansData, - const QString &missingScansError, - std::vector &&files, - std::map &&specialFiles) { + ScanListData &&scans, + base::optional &&translations, + std::map &&specialFiles) { const auto inner = _scroll->setOwnedWidget( object_ptr(this)); _scroll->widthValue( @@ -331,7 +331,8 @@ not_null PanelEditDocument::setupContent( inner, _controller, *scansError, - std::move(specialFiles))); + std::move(specialFiles), + std::move(translations))); } else if (scansData) { _editScans = inner->add( object_ptr( @@ -339,8 +340,8 @@ not_null PanelEditDocument::setupContent( _controller, _scheme.scansHeader, *scansError, - missingScansError, - std::move(files))); + std::move(scans), + std::move(translations))); } const auto valueOrEmpty = [&]( diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_document.h b/Telegram/SourceFiles/passport/passport_panel_edit_document.h index 22d31fb68..c1e23f977 100644 --- a/Telegram/SourceFiles/passport/passport_panel_edit_document.h +++ b/Telegram/SourceFiles/passport/passport_panel_edit_document.h @@ -33,8 +33,9 @@ struct ValueMap; struct ScanInfo; class EditScans; class PanelDetailsRow; -enum class SpecialFile; +enum class FileType; enum class PanelDetailsType; +struct ScanListData; struct EditDocumentScheme { enum class ValueClass { @@ -72,18 +73,18 @@ public: const ValueMap &data, const QString &scansError, const ValueMap &scansData, - const QString &missingScansError, - std::vector &&files, - std::map &&specialFiles); + ScanListData &&scans, + base::optional &&translations, + std::map &&specialFiles); PanelEditDocument( QWidget *parent, not_null controller, Scheme scheme, const QString &scansError, const ValueMap &scansData, - const QString &missingScansError, - std::vector &&files, - std::map &&specialFiles); + ScanListData &&scans, + base::optional &&translations, + std::map &&specialFiles); PanelEditDocument( QWidget *parent, not_null controller, @@ -104,17 +105,17 @@ private: const ValueMap *data, const QString *scansError, const ValueMap *scansData, - const QString &missingScansError, - std::vector &&files, - std::map &&specialFiles); + ScanListData &&scans, + base::optional &&translations, + std::map &&specialFiles); not_null setupContent( const QString *error, const ValueMap *data, const QString *scansError, const ValueMap *scansData, - const QString &missingScansError, - std::vector &&files, - std::map &&specialFiles); + ScanListData &&scans, + base::optional &&translations, + std::map &&specialFiles); void updateControlsGeometry(); void updateCommonError(); diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_scans.cpp b/Telegram/SourceFiles/passport/passport_panel_edit_scans.cpp index 66c6ae318..55b3e7c88 100644 --- a/Telegram/SourceFiles/passport/passport_panel_edit_scans.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_edit_scans.cpp @@ -120,6 +120,173 @@ struct EditScans::SpecialScan { rpl::variable rowCreated; }; +void UpdateFileRow( + not_null button, + const ScanInfo &info) { + button->setStatus(info.status); + button->setImage(info.thumb); + button->setDeleted(info.deleted); + button->setError(!info.error.isEmpty()); +} + +base::unique_qptr> CreateScan( + not_null parent, + const ScanInfo &info, + const QString &name) { + auto result = base::unique_qptr>( + parent->add(object_ptr>( + parent, + object_ptr( + parent, + st::passportScanRow, + name, + info.status, + info.deleted, + !info.error.isEmpty())))); + result->entity()->setImage(info.thumb); + return result; +} + +EditScans::List::List( + not_null controller, + ScanListData &&data) +: controller(controller) +, files(std::move(data.files)) +, initialCount(int(files.size())) +, errorMissing(data.errorMissing) { +} + +EditScans::List::List( + not_null controller, + base::optional &&data) +: controller(controller) +, files(data ? std::move(data->files) : std::vector()) +, 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 *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 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( QWidget *parent, const style::PassportScanRow &st, @@ -258,15 +425,14 @@ EditScans::EditScans( not_null controller, const QString &header, const QString &error, - const QString &errorMissing, - std::vector &&files) + ScanListData &&scans, + base::optional &&translations) : RpWidget(parent) , _controller(controller) -, _files(std::move(files)) -, _initialCount(_files.size()) , _error(error) -, _errorMissing(errorMissing) -, _content(this) { +, _content(this) +, _scansList(_controller, std::move(scans)) +, _translationsList(_controller, std::move(translations)) { setupScans(header); } @@ -274,26 +440,17 @@ EditScans::EditScans( QWidget *parent, not_null controller, const QString &error, - std::map &&specialFiles) + std::map &&specialFiles, + base::optional &&translations) : RpWidget(parent) , _controller(controller) -, _initialCount(-1) , _error(error) -, _content(this) { +, _content(this) +, _scansList(_controller) +, _translationsList(_controller, std::move(translations)) { 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 EditScans::validateGetErrorTop() { auto result = base::optional(); const auto suggestResult = [&](int value) { @@ -302,33 +459,23 @@ base::optional 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()) { suggestResult(_commonError->y()); } - if (_upload && (!exists - || ((errorExists || _uploadMoreError) && !uploadedSomeMore()))) { - toggleError(true); - suggestResult((_files.size() > 5) ? _upload->y() : _header->y()); - } - - const auto nonDeletedErrorIt = ranges::find_if( - _files, - [](const ScanInfo &file) { - return !file.error.isEmpty() && !file.deleted; - }); - if (nonDeletedErrorIt != end(_files)) { - const auto index = (nonDeletedErrorIt - begin(_files)); -// toggleError(true); - suggestResult(_rows[index]->y()); - } + const auto suggestList = [&](FileType type) { + auto &list = this->list(type); + if (list.uploadMoreRequired()) { + list.toggleError(true); + suggestResult((list.files.size() > 5) + ? list.upload->y() + : list.header->y()); + } + if (const auto row = list.nonDeletedErrorRow()) { + //toggleError(true); + suggestResult(row->y()); + } + }; + suggestList(FileType::Scan); for (const auto &[type, scan] : _specialScans) { if (!scan.file.key.id || scan.file.deleted @@ -337,9 +484,26 @@ base::optional EditScans::validateGetErrorTop() { suggestResult(scan.header->y()); } } + suggestList(FileType::Translation); 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) { const auto inner = _content.data(); inner->move(0, 0); @@ -357,80 +521,96 @@ void EditScans::setupScans(const QString &header) { _commonError->toggle(true, anim::type::instant); } - _divider = inner->add( - object_ptr>( - inner, - object_ptr( - inner, - st::passportFormDividerHeight))); - _divider->toggle(_files.empty(), anim::type::instant); - - _header = inner->add( - object_ptr>( - inner, - object_ptr( - 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>( - inner, - object_ptr( - inner, - _errorMissing, - Ui::FlatLabel::InitType::Simple, - st::passportVerifyErrorLabel), - st::passportUploadErrorPadding)); - _uploadMoreError->toggle(true, anim::type::instant); - } - _wrap = inner->add(object_ptr(inner)); - for (const auto &scan : _files) { - pushScan(scan); - _rows.back()->show(anim::type::instant); - } - - _upload = inner->add( - object_ptr( - inner, - _uploadTexts.events_starting_with( - uploadButtonText() - ) | rpl::flatten_latest(), - st::passportUploadButton), - st::passportUploadButtonPadding); - _upload->addClickHandler([=] { - chooseScan(); - }); - - inner->add(object_ptr( - inner, - st::passportFormDividerHeight)); + setupList(inner, FileType::Scan, header); + setupList(inner, FileType::Translation, "Translations"); init(); } -void EditScans::setupSpecialScans(std::map &&files) { - const auto requiresBothSides = files.find(SpecialFile::ReverseSide) +void EditScans::setupList( + not_null 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>( + container, + object_ptr( + container, + st::passportFormDividerHeight))); + list.divider->toggle(list.files.empty(), anim::type::instant); + } + list.header = container->add( + object_ptr>( + container, + object_ptr( + 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>( + container, + object_ptr( + container, + list.errorMissing, + Ui::FlatLabel::InitType::Simple, + st::passportVerifyErrorLabel), + st::passportUploadErrorPadding)); + list.uploadMoreError->toggle(true, anim::type::instant); + } + list.wrap = container->add(object_ptr(container)); + for (const auto &scan : list.files) { + list.pushScan(scan); + list.rows.back()->show(anim::type::instant); + } + + list.upload = container->add( + object_ptr( + container, + list.uploadTexts.events_starting_with( + list.uploadButtonText() + ) | rpl::flatten_latest(), + st::passportUploadButton), + st::passportUploadButtonPadding); + list.upload->addClickHandler([=] { + chooseScan(type); + }); + + container->add(object_ptr( + container, + st::passportFormDividerHeight)); +} + +void EditScans::setupSpecialScans(std::map &&files) { + const auto requiresBothSides = files.find(FileType::ReverseSide) != end(files); - const auto title = [&](SpecialFile type) { + const auto title = [&](FileType type) { switch (type) { - case SpecialFile::FrontSide: + case FileType::FrontSide: return lang(requiresBothSides ? lng_passport_front_side_title : lng_passport_main_page_title); - case SpecialFile::ReverseSide: + case FileType::ReverseSide: return lang(lng_passport_reverse_side_title); - case SpecialFile::Selfie: + case FileType::Selfie: return lang(lng_passport_selfie_title); } Unexpected("Type in special row title."); }; - const auto uploadKey = [=](SpecialFile type, bool hasScan) { + const auto uploadKey = [=](FileType type, bool hasScan) { switch (type) { - case SpecialFile::FrontSide: + case FileType::FrontSide: return requiresBothSides ? (hasScan ? lng_passport_reupload_front_side @@ -438,26 +618,26 @@ void EditScans::setupSpecialScans(std::map &&files) { : (hasScan ? lng_passport_reupload_main_page : lng_passport_upload_main_page); - case SpecialFile::ReverseSide: + case FileType::ReverseSide: return hasScan ? lng_passport_reupload_reverse_side : lng_passport_upload_reverse_side; - case SpecialFile::Selfie: + case FileType::Selfie: return hasScan ? lng_passport_reupload_selfie : lng_passport_upload_selfie; } Unexpected("Type in special row upload key."); }; - const auto description = [&](SpecialFile type) { + const auto description = [&](FileType type) { switch (type) { - case SpecialFile::FrontSide: + case FileType::FrontSide: return lang(requiresBothSides ? lng_passport_front_side_description : lng_passport_main_page_description); - case SpecialFile::ReverseSide: + case FileType::ReverseSide: return lang(lng_passport_reverse_side_description); - case SpecialFile::Selfie: + case FileType::Selfie: return lang(lng_passport_selfie_description); } Unexpected("Type in special row upload key."); @@ -511,7 +691,7 @@ void EditScans::setupSpecialScans(std::map &&files) { st::passportUploadButton), st::passportUploadButtonPadding); scan.upload->addClickHandler([=, type = type] { - chooseSpecialScan(type); + chooseScan(type); }); inner->add(object_ptr( @@ -524,6 +704,8 @@ void EditScans::setupSpecialScans(std::map &&files) { st::passportFormLabelPadding)); } + setupList(inner, FileType::Translation, lang(lng_passport_translation)); + init(); } @@ -545,29 +727,11 @@ void EditScans::init() { } void EditScans::updateScan(ScanInfo &&info) { - if (info.special) { - updateSpecialScan(*info.special, std::move(info)); + if (info.type != FileType::Scan && info.type != FileType::Translation) { + updateSpecialScan(std::move(info)); return; } - 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); - _divider->hide(anim::type::normal); - _header->show(anim::type::normal); - _uploadTexts.fire(uploadButtonText()); - } + list(info.type).updateScan(std::move(info), width()); updateErrorLabels(); } @@ -579,35 +743,46 @@ void EditScans::scanFieldsChanged(bool changed) { } void EditScans::updateErrorLabels() { - if (_uploadMoreError) { - _uploadMoreError->toggle(!uploadedSomeMore(), anim::type::normal); - } + const auto updateList = [&](FileType type) { + auto &list = this->list(type); + if (list.uploadMoreError) { + list.uploadMoreError->toggle( + !list.uploadedSomeMore(), + anim::type::normal); + } + }; + updateList(FileType::Scan); + updateList(FileType::Translation); if (_commonError) { _commonError->toggle(!somethingChanged(), anim::type::normal); } } 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); + const auto type = info.type; const auto i = _specialScans.find(type); if (i == end(_specialScans)) { return; } auto &scan = i->second; if (scan.file.key.id) { - updateFileRow(scan.row->entity(), info); + UpdateFileRow(scan.row->entity(), info); scan.rowCreated = !info.deleted; if (scan.file.key.id != info.key.id) { specialScanChanged(type, true); } } else { const auto requiresBothSides - = (_specialScans.find(SpecialFile::ReverseSide) + = (_specialScans.find(FileType::ReverseSide) != end(_specialScans)); createSpecialScanRow(scan, info, requiresBothSides); scan.wrap->resizeToWidth(width()); @@ -618,117 +793,60 @@ void EditScans::updateSpecialScan(SpecialFile type, ScanInfo &&info) { scan.file = std::move(info); } -void EditScans::updateFileRow( - not_null button, - const ScanInfo &info) { - button->setStatus(info.status); - button->setImage(info.thumb); - button->setDeleted(info.deleted); - button->setError(!info.error.isEmpty()); -} - void EditScans::createSpecialScanRow( SpecialScan &scan, const ScanInfo &info, 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 = [&] { switch (type) { - case SpecialFile::FrontSide: + case FileType::FrontSide: return lang(requiresBothSides ? lng_passport_front_side_name : lng_passport_main_page_name); - case SpecialFile::ReverseSide: + case FileType::ReverseSide: return lang(lng_passport_reverse_side_name); - case SpecialFile::Selfie: + case FileType::Selfie: return lang(lng_passport_selfie_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(); row->deleteClicks( ) | rpl::start_with_next([=] { - _controller->deleteSpecialScan(type); + _controller->deleteScan(type, base::none); }, row->lifetime()); row->restoreClicks( ) | rpl::start_with_next([=] { - _controller->restoreSpecialScan(type); + _controller->restoreScan(type, base::none); }, row->lifetime()); scan.rowCreated = !info.deleted; } -void EditScans::pushScan(const ScanInfo &info) { - const auto index = _rows.size(); - _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> EditScans::createScan( - not_null parent, - const ScanInfo &info, - const QString &name) { - auto result = base::unique_qptr>( - parent->add(object_ptr>( - parent, - object_ptr( - parent, - st::passportScanRow, - name, - info.status, - info.deleted, - !info.error.isEmpty())))); - result->entity()->setImage(info.thumb); - return result; -} - -void EditScans::chooseScan() { - if (!_controller->canAddScan()) { +void EditScans::chooseScan(FileType type) { + if (!_controller->canAddScan(type)) { _controller->showToast(lang(lng_passport_scans_limit_reached)); return; } - ChooseScan(this, [=](QByteArray &&content) { - _controller->uploadScan(std::move(content)); + ChooseScan(this, type, [=](QByteArray &&content) { + _controller->uploadScan(type, std::move(content)); }, [=](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( QPointer parent, + FileType type, Fn doneCallback, - Fn errorCallback, - bool allowMany) { + Fn errorCallback) { Expects(parent != nullptr); const auto processFiles = std::make_shared>(); @@ -802,6 +920,8 @@ void EditScans::ChooseScan( } } }; + const auto allowMany = (type == FileType::Scan) + || (type == FileType::Translation); (allowMany ? FileDialog::GetOpenPaths : FileDialog::GetOpenPath)( parent, lang(lng_passport_choose_image), @@ -810,44 +930,11 @@ void EditScans::ChooseScan( nullptr); } -rpl::producer EditScans::uploadButtonText() const { - 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) { +void EditScans::hideSpecialScanError(FileType type) { toggleSpecialScanError(type, false); } -void EditScans::specialScanChanged(SpecialFile type, bool changed) { +void EditScans::specialScanChanged(FileType type, bool changed) { hideSpecialScanError(type); if (_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); Assert(i != end(_specialScans)); return i->second; } -void EditScans::toggleSpecialScanError(SpecialFile type, bool shown) { +void EditScans::toggleSpecialScanError(FileType type, bool shown) { auto &scan = findSpecialScan(type); if (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); const auto error = scan.errorAnimation.current( scan.errorShown ? 1. : 0.); diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_scans.h b/Telegram/SourceFiles/passport/passport_panel_edit_scans.h index 84e00666f..fb0e3c37a 100644 --- a/Telegram/SourceFiles/passport/passport_panel_edit_scans.h +++ b/Telegram/SourceFiles/passport/passport_panel_edit_scans.h @@ -26,7 +26,7 @@ class Button; namespace Passport { -enum class SpecialFile; +enum class FileType; class PanelController; class ScanButton; struct ScanInfo; @@ -38,6 +38,11 @@ enum class ReadScanError { Unknown, }; +struct ScanListData { + std::vector files; + QString errorMissing; +}; + class EditScans : public Ui::RpWidget { public: EditScans( @@ -45,13 +50,14 @@ public: not_null controller, const QString &header, const QString &error, - const QString &errorMissing, - std::vector &&files); + ScanListData &&scans, + base::optional &&translations); EditScans( QWidget *parent, not_null controller, const QString &error, - std::map &&specialFiles); + std::map &&specialFiles, + base::optional &&translations); base::optional validateGetErrorTop(); @@ -59,27 +65,59 @@ public: static void ChooseScan( QPointer parent, + FileType type, Fn doneCallback, - Fn errorCallback, - bool allowMany); + Fn errorCallback); ~EditScans(); private: struct SpecialScan; + struct List { + List(not_null controller, ScanListData &&data); + List( + not_null controller, + base::optional &&data = base::none); + + bool uploadedSomeMore() const; + bool uploadMoreRequired() const; + Ui::SlideWrap *nonDeletedErrorRow() const; + rpl::producer uploadButtonText() const; + void toggleError(bool shown); + void hideError(); + void errorAnimationCallback(); + void updateScan(ScanInfo &&info, int width); + void pushScan(const ScanInfo &info); + + not_null controller; + std::vector files; + base::optional initialCount; + QString errorMissing; + QPointer> divider; + QPointer> header; + QPointer> uploadMoreError; + QPointer wrap; + std::vector>> rows; + QPointer upload; + rpl::event_stream> uploadTexts; + bool errorShown = false; + Animation errorAnimation; + }; + + List &list(FileType type); + const List &list(FileType type) const; void setupScans(const QString &header); - void setupSpecialScans(std::map &&files); + void setupList( + not_null container, + FileType type, + const QString &header); + void setupSpecialScans(std::map &&files); void init(); - void chooseScan(); - void chooseSpecialScan(SpecialFile type); + void chooseScan(FileType type); void updateScan(ScanInfo &&info); - void updateSpecialScan(SpecialFile type, ScanInfo &&info); - void updateFileRow( - not_null button, - const ScanInfo &info); - void pushScan(const ScanInfo &info); + void updateSpecialScan(ScanInfo &&info); void createSpecialScanRow( SpecialScan &scan, const ScanInfo &info, @@ -88,43 +126,27 @@ private: not_null parent, const ScanInfo &info, const QString &name); - SpecialScan &findSpecialScan(SpecialFile type); - - rpl::producer uploadButtonText() const; + SpecialScan &findSpecialScan(FileType type); void updateErrorLabels(); - void toggleError(bool shown); - void hideError(); - void errorAnimationCallback(); - bool uploadedSomeMore() const; bool somethingChanged() const; - void toggleSpecialScanError(SpecialFile type, bool shown); - void hideSpecialScanError(SpecialFile type); - void specialScanErrorAnimationCallback(SpecialFile type); - void specialScanChanged(SpecialFile type, bool changed); + void toggleSpecialScanError(FileType type, bool shown); + void hideSpecialScanError(FileType type); + void specialScanErrorAnimationCallback(FileType type); + void specialScanChanged(FileType type, bool changed); not_null _controller; - std::vector _files; - int _initialCount = 0; QString _error; - QString _errorMissing; - object_ptr _content; - QPointer> _divider; - QPointer> _header; QPointer> _commonError; - QPointer> _uploadMoreError; - QPointer _wrap; - std::vector>> _rows; - QPointer _upload; - rpl::event_stream> _uploadTexts; bool _scanFieldsChanged = false; bool _specialScanChanged = false; - bool _errorShown = false; - Animation _errorAnimation; - std::map _specialScans; + List _scansList; + std::map _specialScans; + List _translationsList; + };