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

View File

@ -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<const Value*> 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<const Value*> value,
FileType type,
const File &fields,
std::unique_ptr<UploadScanData> &&uploadData)
: value(value)
, type(type)
, fields(std::move(fields))
, uploadData(std::move(uploadData))
, guard(std::make_shared<bool>(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<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(
not_null<Window::Controller*> 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<File> &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<File>;
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<const Value*> 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<int> {
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<const Value*> value,
int scanIndex) {
scanDeleteRestore(value, scanIndex, true);
FileType type,
base::optional<int> fileIndex) {
scanDeleteRestore(value, type, fileIndex, true);
}
void FormController::restoreScan(
not_null<const Value*> value,
int scanIndex) {
scanDeleteRestore(value, scanIndex, 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);
FileType type,
base::optional<int> fileIndex) {
scanDeleteRestore(value, type, fileIndex, false);
}
void FormController::prepareFile(
@ -1173,14 +1341,13 @@ void FormController::encryptFile(
void FormController::scanDeleteRestore(
not_null<const Value*> value,
int scanIndex,
FileType type,
base::optional<int> 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<const Value*> 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<const Value*> 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<Value*> FormController::findValue(not_null<const Value*> value) {
void FormController::startValueEdit(not_null<const Value*> 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<const Value*> 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<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) {
Expects(value->editScreens > 0);
@ -1575,7 +1685,7 @@ void FormController::cancelValueEdit(not_null<const Value*> value) {
}
void FormController::valueEditFailed(not_null<Value*> value) {
Expects(!savingValue(value));
Expects(!value->saving());
if (value->editScreens == 0) {
clearValueEdit(value);
@ -1583,20 +1693,16 @@ void FormController::valueEditFailed(not_null<Value*> value) {
}
void FormController::clearValueEdit(not_null<Value*> 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<const Value*> 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<const Value*> 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<const Value*> value) {
if (savingValue(value) || _submitRequestId) {
if (value->saving() || _submitRequestId) {
return;
}
@ -1689,24 +1791,21 @@ void FormController::saveEncryptedValue(not_null<Value*> value) {
MTP_long(file.fields.id),
MTP_long(file.fields.accessHash));
};
auto files = QVector<MTPInputSecureFile>();
files.reserve(value->scansInEdit.size());
for (const auto &scan : value->scansInEdit) {
if (scan.deleted) {
continue;
const auto wrapList = [&](not_null<const Value*> value, FileType type) {
const auto &list = value->filesInEdit(type);
auto result = QVector<MTPInputSecureFile>();
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<MTPInputSecureFile>();
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*> 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 <typename Condition>
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)) {

View File

@ -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<const Value*> value,
FileType type,
const File &fields,
std::unique_ptr<UploadScanData> &&uploadData);
not_null<const Value*> value;
FileType type;
File fields;
UploadScanDataPointer uploadData;
std::shared_ptr<bool> 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<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;
ValueData data;
std::vector<File> scans;
std::vector<File> translations;
std::map<SpecialFile, File> specialScans;
std::map<FileType, File> specialScans;
QString error;
QString scanMissingError;
QString translationMissingError;
std::vector<EditFile> scansInEdit;
std::vector<EditFile> translationsInEdit;
std::map<SpecialFile, EditFile> specialScansInEdit;
std::map<FileType, EditFile> specialScansInEdit;
Verification verification;
bytes::vector submitHash;
@ -199,6 +215,13 @@ struct Value {
private:
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);
@ -299,20 +322,19 @@ public:
void reloadAndSubmitPassword(const QByteArray &password);
void cancelPassword();
bool canAddScan(not_null<const Value*> value) const;
void uploadScan(not_null<const Value*> value, QByteArray &&content);
void deleteScan(not_null<const Value*> value, int fileIndex);
void restoreScan(not_null<const Value*> value, int fileIndex);
void uploadSpecialScan(
bool canAddScan(not_null<const Value*> value, FileType type) const;
void uploadScan(
not_null<const Value*> value,
SpecialFile type,
FileType type,
QByteArray &&content);
void deleteSpecialScan(
void deleteScan(
not_null<const Value*> value,
SpecialFile type);
void restoreSpecialScan(
FileType type,
base::optional<int> fileIndex);
void restoreScan(
not_null<const Value*> value,
SpecialFile type);
FileType type,
base::optional<int> fileIndex);
rpl::producer<> secretReadyEvents() const;
@ -331,8 +353,6 @@ public:
void cancelValueVerification(not_null<const Value*> value);
void saveValueEdit(not_null<const Value*> value, ValueMap &&data);
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 cancelSure();
@ -350,6 +370,9 @@ private:
QByteArray credentials;
std::vector<not_null<const Value*>> errors;
};
template <typename Condition>
EditFile *findEditFileByCondition(Condition &&condition);
EditFile *findEditFile(const FullMsgId &fullId);
EditFile *findEditFile(const FileKey &key);
std::pair<Value*, File*> findFile(const FileKey &key);
@ -432,11 +455,8 @@ private:
void scanUploadFail(const FullMsgId &fullId);
void scanDeleteRestore(
not_null<const Value*> value,
int fileIndex,
bool deleted);
void specialScanDeleteRestore(
not_null<const Value*> value,
SpecialFile type,
FileType type,
base::optional<int> fileIndex,
bool deleted);
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."));
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);

View File

@ -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<Scope> ComputeScopes(const Form &form);

View File

@ -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<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(
Scope::Type type,
base::optional<Value::Type> 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<int> 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<int> 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<ScanInfo> PanelController::scanUpdated() const {
return _form->scanUpdated(
) | rpl::filter([=](not_null<const EditFile*> file) {
return (file->value == _editDocument);
}) | rpl::map([=](not_null<const EditFile*> file) {
return collectScanInfo(*file);
}) | rpl::map([](not_null<const EditFile*> file) {
return CollectScanInfo(*file);
});
}
@ -643,63 +695,8 @@ rpl::producer<ScopeError> 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<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(
not_null<const Value*> value) const {
using General = ScopeError::General;
auto result = std::vector<ScopeError>();
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<PanelEditDocument>(
_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<PanelEditDocument>(
_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<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() {
Expects(_editScope != nullptr);

View File

@ -36,9 +36,8 @@ struct ScanInfo {
QString status;
QImage thumb;
bool deleted = false;
base::optional<SpecialFile> 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<int> fileIndex);
void restoreScan(FileType type, base::optional<int> fileIndex);
rpl::producer<ScanInfo> scanUpdated() const;
rpl::producer<ScopeError> 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<ScanInfo> valueFiles(const Value &value) const;
std::map<SpecialFile, ScanInfo> valueSpecialFiles(
std::map<FileType, ScanInfo> valueSpecialFiles(
const Value &value) const;
void processValueSaveFinished(not_null<const Value*> value);
void processVerificationNeeded(not_null<const Value*> value);
@ -161,7 +156,6 @@ private:
bool uploadingScopeScan() const;
bool hasValueDocument() const;
bool hasValueFields() const;
ScanInfo collectScanInfo(const EditFile &file) const;
std::vector<ScopeError> collectSaveErrors(
not_null<const Value*> value) const;
QString getDefaultContactValue(Scope::Type type) const;

View File

@ -214,9 +214,9 @@ PanelEditDocument::PanelEditDocument(
const ValueMap &data,
const QString &scansError,
const ValueMap &scansData,
const QString &missingScansError,
std::vector<ScanInfo> &&files,
std::map<SpecialFile, ScanInfo> &&specialFiles)
ScanListData &&scans,
base::optional<ScanListData> &&translations,
std::map<FileType, ScanInfo> &&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<ScanInfo> &&files,
std::map<SpecialFile, ScanInfo> &&specialFiles)
ScanListData &&scans,
base::optional<ScanListData> &&translations,
std::map<FileType, ScanInfo> &&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<ScanInfo> &&files,
std::map<SpecialFile, ScanInfo> &&specialFiles) {
ScanListData &&scans,
base::optional<ScanListData> &&translations,
std::map<FileType, ScanInfo> &&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<Ui::RpWidget*> PanelEditDocument::setupContent(
const ValueMap *data,
const QString *scansError,
const ValueMap *scansData,
const QString &missingScansError,
std::vector<ScanInfo> &&files,
std::map<SpecialFile, ScanInfo> &&specialFiles) {
ScanListData &&scans,
base::optional<ScanListData> &&translations,
std::map<FileType, ScanInfo> &&specialFiles) {
const auto inner = _scroll->setOwnedWidget(
object_ptr<Ui::VerticalLayout>(this));
_scroll->widthValue(
@ -331,7 +331,8 @@ not_null<Ui::RpWidget*> PanelEditDocument::setupContent(
inner,
_controller,
*scansError,
std::move(specialFiles)));
std::move(specialFiles),
std::move(translations)));
} else if (scansData) {
_editScans = inner->add(
object_ptr<EditScans>(
@ -339,8 +340,8 @@ not_null<Ui::RpWidget*> PanelEditDocument::setupContent(
_controller,
_scheme.scansHeader,
*scansError,
missingScansError,
std::move(files)));
std::move(scans),
std::move(translations)));
}
const auto valueOrEmpty = [&](

View File

@ -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<ScanInfo> &&files,
std::map<SpecialFile, ScanInfo> &&specialFiles);
ScanListData &&scans,
base::optional<ScanListData> &&translations,
std::map<FileType, ScanInfo> &&specialFiles);
PanelEditDocument(
QWidget *parent,
not_null<PanelController*> controller,
Scheme scheme,
const QString &scansError,
const ValueMap &scansData,
const QString &missingScansError,
std::vector<ScanInfo> &&files,
std::map<SpecialFile, ScanInfo> &&specialFiles);
ScanListData &&scans,
base::optional<ScanListData> &&translations,
std::map<FileType, ScanInfo> &&specialFiles);
PanelEditDocument(
QWidget *parent,
not_null<PanelController*> controller,
@ -104,17 +105,17 @@ private:
const ValueMap *data,
const QString *scansError,
const ValueMap *scansData,
const QString &missingScansError,
std::vector<ScanInfo> &&files,
std::map<SpecialFile, ScanInfo> &&specialFiles);
ScanListData &&scans,
base::optional<ScanListData> &&translations,
std::map<FileType, ScanInfo> &&specialFiles);
not_null<Ui::RpWidget*> setupContent(
const QString *error,
const ValueMap *data,
const QString *scansError,
const ValueMap *scansData,
const QString &missingScansError,
std::vector<ScanInfo> &&files,
std::map<SpecialFile, ScanInfo> &&specialFiles);
ScanListData &&scans,
base::optional<ScanListData> &&translations,
std::map<FileType, ScanInfo> &&specialFiles);
void updateControlsGeometry();
void updateCommonError();

View File

@ -120,6 +120,173 @@ struct EditScans::SpecialScan {
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(
QWidget *parent,
const style::PassportScanRow &st,
@ -258,15 +425,14 @@ EditScans::EditScans(
not_null<PanelController*> controller,
const QString &header,
const QString &error,
const QString &errorMissing,
std::vector<ScanInfo> &&files)
ScanListData &&scans,
base::optional<ScanListData> &&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<PanelController*> controller,
const QString &error,
std::map<SpecialFile, ScanInfo> &&specialFiles)
std::map<FileType, ScanInfo> &&specialFiles,
base::optional<ScanListData> &&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<int> EditScans::validateGetErrorTop() {
auto result = base::optional<int>();
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()) {
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<int> 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<Ui::SlideWrap<BoxContentDivider>>(
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));
setupList(inner, FileType::Scan, header);
setupList(inner, FileType::Translation, "Translations");
init();
}
void EditScans::setupSpecialScans(std::map<SpecialFile, ScanInfo> &&files) {
const auto requiresBothSides = files.find(SpecialFile::ReverseSide)
void EditScans::setupList(
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);
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<SpecialFile, ScanInfo> &&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<SpecialFile, ScanInfo> &&files) {
st::passportUploadButton),
st::passportUploadButtonPadding);
scan.upload->addClickHandler([=, type = type] {
chooseSpecialScan(type);
chooseScan(type);
});
inner->add(object_ptr<Ui::DividerLabel>(
@ -524,6 +704,8 @@ void EditScans::setupSpecialScans(std::map<SpecialFile, ScanInfo> &&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<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(
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<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()) {
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<QWidget> parent,
FileType type,
Fn<void(QByteArray&&)> doneCallback,
Fn<void(ReadScanError)> errorCallback,
bool allowMany) {
Fn<void(ReadScanError)> errorCallback) {
Expects(parent != nullptr);
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)(
parent,
lang(lng_passport_choose_image),
@ -810,44 +930,11 @@ void EditScans::ChooseScan(
nullptr);
}
rpl::producer<QString> 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.);

View File

@ -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<ScanInfo> files;
QString errorMissing;
};
class EditScans : public Ui::RpWidget {
public:
EditScans(
@ -45,13 +50,14 @@ public:
not_null<PanelController*> controller,
const QString &header,
const QString &error,
const QString &errorMissing,
std::vector<ScanInfo> &&files);
ScanListData &&scans,
base::optional<ScanListData> &&translations);
EditScans(
QWidget *parent,
not_null<PanelController*> controller,
const QString &error,
std::map<SpecialFile, ScanInfo> &&specialFiles);
std::map<FileType, ScanInfo> &&specialFiles,
base::optional<ScanListData> &&translations);
base::optional<int> validateGetErrorTop();
@ -59,27 +65,59 @@ public:
static void ChooseScan(
QPointer<QWidget> parent,
FileType type,
Fn<void(QByteArray&&)> doneCallback,
Fn<void(ReadScanError)> errorCallback,
bool allowMany);
Fn<void(ReadScanError)> errorCallback);
~EditScans();
private:
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 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 chooseScan();
void chooseSpecialScan(SpecialFile type);
void chooseScan(FileType type);
void updateScan(ScanInfo &&info);
void updateSpecialScan(SpecialFile type, ScanInfo &&info);
void updateFileRow(
not_null<ScanButton*> 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<Ui::VerticalLayout*> parent,
const ScanInfo &info,
const QString &name);
SpecialScan &findSpecialScan(SpecialFile type);
rpl::producer<QString> 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<PanelController*> _controller;
std::vector<ScanInfo> _files;
int _initialCount = 0;
QString _error;
QString _errorMissing;
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>> _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 _specialScanChanged = false;
bool _errorShown = false;
Animation _errorAnimation;
std::map<SpecialFile, SpecialScan> _specialScans;
List _scansList;
std::map<FileType, SpecialScan> _specialScans;
List _translationsList;
};