mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-01-05 22:29:34 +00:00
Add translations support to passport.
This commit is contained in:
parent
6558a32794
commit
f76a2bc224
@ -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";
|
||||
|
@ -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)) {
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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 = [&](
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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.);
|
||||
|
@ -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;
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user