mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-03-25 04:38:23 +00:00
Support common error for the whole value.
It is removed (considered fixed) if anything changes in the data.
This commit is contained in:
parent
cb827406ca
commit
b935d54fe7
Telegram/SourceFiles/passport
passport.stylepassport_form_controller.cpppassport_form_controller.hpassport_form_view_controller.cpppassport_form_view_controller.hpassport_panel_controller.cpppassport_panel_controller.hpassport_panel_edit_document.cpppassport_panel_edit_document.hpassport_panel_edit_scans.cpppassport_panel_edit_scans.h
@ -139,6 +139,7 @@ passportUploadButton: InfoProfileButton {
|
||||
passportUploadButtonPadding: margins(0px, 10px, 0px, 10px);
|
||||
passportUploadHeaderPadding: margins(22px, 14px, 22px, 3px);
|
||||
passportUploadErrorPadding: margins(22px, 5px, 22px, 5px);
|
||||
passportValueErrorPadding: passportUploadHeaderPadding;
|
||||
passportDeleteButton: InfoProfileButton(passportUploadButton) {
|
||||
textFg: attentionButtonFg;
|
||||
textFgOver: attentionButtonFgOver;
|
||||
|
@ -302,6 +302,20 @@ bool Value::requiresSpecialScan(SpecialFile type) const {
|
||||
Unexpected("Special scan type in requiresSpecialScan.");
|
||||
}
|
||||
|
||||
void Value::fillDataFrom(Value &&other) {
|
||||
const auto savedSelfieRequired = selfieRequired;
|
||||
const auto savedTranslationRequired = translationRequired;
|
||||
const auto savedNativeNames = nativeNames;
|
||||
const auto savedEditScreens = editScreens;
|
||||
|
||||
*this = std::move(other);
|
||||
|
||||
selfieRequired = savedSelfieRequired;
|
||||
translationRequired = savedTranslationRequired;
|
||||
nativeNames = savedNativeNames;
|
||||
editScreens = savedEditScreens;
|
||||
}
|
||||
|
||||
bool Value::scansAreFilled() const {
|
||||
if (!requiresSpecialScan(SpecialFile::FrontSide) && scans.empty()) {
|
||||
return false;
|
||||
@ -876,12 +890,15 @@ void FormController::fillErrors() {
|
||||
for (const auto &error : _form.pendingErrors) {
|
||||
error.match([&](const MTPDsecureValueError &data) {
|
||||
if (const auto value = find(data.vtype)) {
|
||||
value->error = qs(data.vtext);
|
||||
if (CanHaveErrors(value->type)) {
|
||||
value->error = qs(data.vtext);
|
||||
}
|
||||
}
|
||||
}, [&](const MTPDsecureValueErrorData &data) {
|
||||
if (const auto value = find(data.vtype)) {
|
||||
const auto key = qs(data.vfield);
|
||||
if (!SkipFieldCheck(value, key)) {
|
||||
if (CanHaveErrors(value->type)
|
||||
&& !SkipFieldCheck(value, key)) {
|
||||
value->data.parsed.fields[key].error = qs(data.vtext);
|
||||
}
|
||||
}
|
||||
@ -894,7 +911,9 @@ void FormController::fillErrors() {
|
||||
}
|
||||
}, [&](const MTPDsecureValueErrorFiles &data) {
|
||||
if (const auto value = find(data.vtype)) {
|
||||
value->scanMissingError = qs(data.vtext);
|
||||
if (CanRequireScans(value->type)) {
|
||||
value->scanMissingError = qs(data.vtext);
|
||||
}
|
||||
}
|
||||
}, [&](const MTPDsecureValueErrorTranslationFile &data) {
|
||||
const auto hash = bytes::make_span(data.vfile_hash.v);
|
||||
@ -921,7 +940,7 @@ void FormController::fillErrors() {
|
||||
}
|
||||
}
|
||||
|
||||
void FormController::decryptValue(Value &value) {
|
||||
void FormController::decryptValue(Value &value) const {
|
||||
Expects(!_secret.empty());
|
||||
|
||||
if (!validateValueSecrets(value)) {
|
||||
@ -946,7 +965,7 @@ void FormController::decryptValue(Value &value) {
|
||||
}
|
||||
}
|
||||
|
||||
bool FormController::validateValueSecrets(Value &value) {
|
||||
bool FormController::validateValueSecrets(Value &value) const {
|
||||
if (!value.data.original.isEmpty()) {
|
||||
value.data.secret = DecryptValueSecret(
|
||||
value.data.encryptedSecret,
|
||||
@ -981,8 +1000,8 @@ bool FormController::validateValueSecrets(Value &value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void FormController::resetValue(Value &value) {
|
||||
value = Value(value.type);
|
||||
void FormController::resetValue(Value &value) const {
|
||||
value.fillDataFrom(Value(value.type));
|
||||
}
|
||||
|
||||
rpl::producer<QString> FormController::passwordError() const {
|
||||
@ -1600,6 +1619,9 @@ void FormController::saveValueEdit(
|
||||
return;
|
||||
}
|
||||
|
||||
// If we didn't change anything, we don't send save request
|
||||
// and we don't reset value->error/[scan|translation]MissingError.
|
||||
// Otherwise we reset them after save by re-parsing the value.
|
||||
const auto nonconst = findValue(value);
|
||||
if (!editValueChanged(nonconst, data)) {
|
||||
nonconst->saveRequestId = -1;
|
||||
@ -1632,10 +1654,7 @@ void FormController::deleteValueEdit(not_null<const Value*> value) {
|
||||
nonconst->saveRequestId = request(MTPaccount_DeleteSecureValue(
|
||||
MTP_vector<MTPSecureValueType>(1, ConvertType(nonconst->type))
|
||||
)).done([=](const MTPBool &result) {
|
||||
const auto editScreens = value->editScreens;
|
||||
*nonconst = Value(nonconst->type);
|
||||
nonconst->editScreens = editScreens;
|
||||
|
||||
resetValue(*nonconst);
|
||||
_valueSaveFinished.fire_copy(value);
|
||||
}).fail([=](const RPCError &error) {
|
||||
nonconst->saveRequestId = 0;
|
||||
@ -1791,10 +1810,9 @@ void FormController::sendSaveRequest(
|
||||
scansInEdit.push_back(std::move(scan));
|
||||
}
|
||||
|
||||
const auto editScreens = value->editScreens;
|
||||
*value = parseValue(result, scansInEdit);
|
||||
decryptValue(*value);
|
||||
value->editScreens = editScreens;
|
||||
auto refreshed = parseValue(result, scansInEdit);
|
||||
decryptValue(refreshed);
|
||||
value->fillDataFrom(std::move(refreshed));
|
||||
|
||||
_valueSaveFinished.fire_copy(value);
|
||||
}).fail([=](const RPCError &error) {
|
||||
@ -2257,13 +2275,13 @@ bool FormController::parseForm(const MTPaccount_AuthorizationForm &result) {
|
||||
}
|
||||
for (const auto &required : data.vrequired_types.v) {
|
||||
const auto row = CollectRequestedRow(required);
|
||||
for (const auto value : row.values) {
|
||||
const auto [i, ok] = _form.values.emplace(
|
||||
value.type,
|
||||
Value(value.type));
|
||||
i->second.selfieRequired = value.selfieRequired;
|
||||
i->second.translationRequired = value.translationRequired;
|
||||
i->second.nativeNames = value.nativeNames;
|
||||
for (const auto requested : row.values) {
|
||||
const auto type = requested.type;
|
||||
const auto [i, ok] = _form.values.emplace(type, Value(type));
|
||||
auto &value = i->second;
|
||||
value.translationRequired = requested.translationRequired;
|
||||
value.selfieRequired = requested.selfieRequired;
|
||||
value.nativeNames = requested.nativeNames;
|
||||
}
|
||||
_form.request.push_back(row.values
|
||||
| ranges::view::transform([](const RequestedValue &value) {
|
||||
|
@ -164,10 +164,14 @@ struct Value {
|
||||
Email,
|
||||
};
|
||||
|
||||
|
||||
explicit Value(Type type);
|
||||
Value(Value &&other) = default;
|
||||
Value &operator=(Value &&other) = default;
|
||||
|
||||
// Some data is not parsed from server-provided values.
|
||||
// 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 scansAreFilled() const;
|
||||
|
||||
@ -188,10 +192,13 @@ struct Value {
|
||||
bool selfieRequired = false;
|
||||
bool translationRequired = false;
|
||||
bool nativeNames = false;
|
||||
|
||||
int editScreens = 0;
|
||||
|
||||
mtpRequestId saveRequestId = 0;
|
||||
|
||||
private:
|
||||
Value &operator=(Value &&other) = default;
|
||||
|
||||
};
|
||||
|
||||
struct RequestedValue {
|
||||
@ -395,9 +402,9 @@ private:
|
||||
bytes::const_span passwordBytes,
|
||||
uint64 serverSecretId);
|
||||
void decryptValues();
|
||||
void decryptValue(Value &value);
|
||||
bool validateValueSecrets(Value &value);
|
||||
void resetValue(Value &value);
|
||||
void decryptValue(Value &value) const;
|
||||
bool validateValueSecrets(Value &value) const;
|
||||
void resetValue(Value &value) const;
|
||||
void fillErrors();
|
||||
|
||||
void loadFile(File &file);
|
||||
|
@ -98,6 +98,31 @@ bool InlineDetails(const Form::Request &request, Value::Type details) {
|
||||
Scope::Scope(Type type) : type(type) {
|
||||
}
|
||||
|
||||
bool CanRequireSelfie(Value::Type type) {
|
||||
const auto scope = ScopeTypeForValueType(type);
|
||||
return (scope == Scope::Type::Address)
|
||||
|| (scope == Scope::Type::Identity);
|
||||
}
|
||||
|
||||
bool CanRequireScans(Value::Type type) {
|
||||
const auto scope = ScopeTypeForValueType(type);
|
||||
return (scope == Scope::Type::Address);
|
||||
}
|
||||
|
||||
bool CanRequireTranslation(Value::Type type) {
|
||||
const auto scope = ScopeTypeForValueType(type);
|
||||
return (scope == Scope::Type::Address)
|
||||
|| (scope == Scope::Type::Identity);
|
||||
}
|
||||
|
||||
bool CanRequireNativeNames(Value::Type type) {
|
||||
return (type == Value::Type::PersonalDetails);
|
||||
}
|
||||
|
||||
bool CanHaveErrors(Value::Type type) {
|
||||
return (type != Value::Type::Phone) && (type != Value::Type::Email);
|
||||
}
|
||||
|
||||
bool ValidateForm(const Form &form) {
|
||||
base::flat_set<Value::Type> values;
|
||||
for (const auto &requested : form.request) {
|
||||
@ -120,30 +145,36 @@ bool ValidateForm(const Form &form) {
|
||||
values.emplace(type);
|
||||
}
|
||||
}
|
||||
|
||||
// Invalid errors should be skipped while parsing the form.
|
||||
for (const auto &[type, value] : form.values) {
|
||||
if (value.selfieRequired && !CanRequireSelfie(type)) {
|
||||
LOG(("API Error: Bad value requiring selfie."));
|
||||
return false;
|
||||
} else if (value.translationRequired
|
||||
&& !CanRequireTranslation(type)) {
|
||||
LOG(("API Error: Bad value requiring translation."));
|
||||
return false;
|
||||
} else if (value.nativeNames && !CanRequireNativeNames(type)) {
|
||||
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 (!scan.error.isEmpty()) {
|
||||
LOG(("API Error: "
|
||||
"Translation error in authorization form value."));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!value.translationMissingError.isEmpty()) {
|
||||
LOG(("API Error: "
|
||||
"Translations error in authorization form value."));
|
||||
return false;
|
||||
Assert(scan.error.isEmpty());
|
||||
}
|
||||
Assert(value.translationMissingError.isEmpty());
|
||||
}
|
||||
for (const auto &[type, specialScan] : value.specialScans) {
|
||||
if (!value.requiresSpecialScan(type)
|
||||
&& !specialScan.error.isEmpty()) {
|
||||
LOG(("API Error: "
|
||||
"Special scan error in authorization form value."));
|
||||
return false;
|
||||
if (!value.requiresSpecialScan(type)) {
|
||||
Assert(specialScan.error.isEmpty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -34,6 +34,8 @@ 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);
|
||||
QString ComputeScopeRowReadyString(const Scope &scope);
|
||||
|
@ -105,8 +105,8 @@ EditDocumentScheme GetDocumentScheme(
|
||||
return NameValidate(value);
|
||||
};
|
||||
|
||||
// #TODO passport scheme
|
||||
switch (type) {
|
||||
case Scope::Type::PersonalDetails:
|
||||
case Scope::Type::Identity: {
|
||||
auto result = Scheme();
|
||||
result.detailsHeader = lang(lng_passport_personal_details);
|
||||
@ -212,6 +212,7 @@ EditDocumentScheme GetDocumentScheme(
|
||||
return result;
|
||||
} break;
|
||||
|
||||
case Scope::Type::AddressDetails:
|
||||
case Scope::Type::Address: {
|
||||
auto result = Scheme();
|
||||
result.detailsHeader = lang(lng_passport_address);
|
||||
@ -341,7 +342,7 @@ EditContactScheme GetContactScheme(Scope::Type type) {
|
||||
Unexpected("Type in GetContactScheme().");
|
||||
}
|
||||
|
||||
const std::map<QString, QString> &NativeNameKeys() {
|
||||
const std::map<QString, QString> &LatinToNativeMap() {
|
||||
static const auto result = std::map<QString, QString> {
|
||||
{ qsl("first_name"), qsl("first_name_native") },
|
||||
{ qsl("last_name"), qsl("last_name_native") },
|
||||
@ -350,11 +351,11 @@ const std::map<QString, QString> &NativeNameKeys() {
|
||||
return result;
|
||||
}
|
||||
|
||||
const std::map<QString, QString> &LatinNameKeys() {
|
||||
const std::map<QString, QString> &NativeToLatinMap() {
|
||||
static const auto result = std::map<QString, QString> {
|
||||
{ qsl("first_name_native"), qsl("first_name") },
|
||||
{ qsl("last_name_native"), qsl("last_name") },
|
||||
{ qsl("_nativemiddle_name"), qsl("middle_name") },
|
||||
{ qsl("middle_name_native"), qsl("middle_name") },
|
||||
};
|
||||
return result;
|
||||
}
|
||||
@ -363,10 +364,10 @@ bool SkipFieldCheck(not_null<const Value*> value, const QString &key) {
|
||||
if (value->type != Value::Type::PersonalDetails) {
|
||||
return false;
|
||||
}
|
||||
const auto &namesMap = value->nativeNames
|
||||
? NativeNameKeys()
|
||||
: LatinNameKeys();
|
||||
return namesMap.find(key) == end(namesMap);
|
||||
const auto &dontCheckNames = value->nativeNames
|
||||
? LatinToNativeMap()
|
||||
: NativeToLatinMap();
|
||||
return dontCheckNames.find(key) != end(dontCheckNames);
|
||||
}
|
||||
|
||||
BoxPointer::BoxPointer(QPointer<BoxContent> value)
|
||||
@ -679,37 +680,11 @@ ScanInfo PanelController::collectScanInfo(const EditFile &file) const {
|
||||
file.fields.error };
|
||||
}
|
||||
|
||||
std::vector<ScopeError> PanelController::collectErrors(
|
||||
std::vector<ScopeError> PanelController::collectSaveErrors(
|
||||
not_null<const Value*> value) const {
|
||||
using General = ScopeError::General;
|
||||
|
||||
auto result = std::vector<ScopeError>();
|
||||
if (!value->error.isEmpty()) {
|
||||
result.push_back({ General::WholeValue, value->error });
|
||||
}
|
||||
if (!value->scanMissingError.isEmpty()) {
|
||||
result.push_back({ General::ScanMissing, value->scanMissingError });
|
||||
}
|
||||
if (!value->translationMissingError.isEmpty()) {
|
||||
result.push_back({
|
||||
General::TranslationMissing,
|
||||
value->translationMissingError });
|
||||
}
|
||||
const auto addFileError = [&](const EditFile &file) {
|
||||
if (!file.fields.error.isEmpty()) {
|
||||
const auto key = FileKey{ file.fields.id, file.fields.dcId };
|
||||
result.push_back({ key, file.fields.error });
|
||||
}
|
||||
};
|
||||
for (const auto &scan : value->scansInEdit) {
|
||||
addFileError(scan);
|
||||
}
|
||||
for (const auto &scan : value->translationsInEdit) {
|
||||
addFileError(scan);
|
||||
}
|
||||
for (const auto &[type, scan] : value->specialScansInEdit) {
|
||||
addFileError(scan);
|
||||
}
|
||||
for (const auto &[key, value] : value->data.parsedInEdit.fields) {
|
||||
if (!value.error.isEmpty()) {
|
||||
result.push_back({ key, value.error });
|
||||
@ -912,12 +887,10 @@ void PanelController::editScope(int index) {
|
||||
editScope(index, -1);
|
||||
} else {
|
||||
const auto documentIndex = findNonEmptyDocumentIndex(scope);
|
||||
if (documentIndex >= 0) {
|
||||
editScope(index, documentIndex);
|
||||
} else if (scope.documents.size() > 1) {
|
||||
requestScopeFilesType(index);
|
||||
if (documentIndex >= 0 || scope.documents.size() == 1) {
|
||||
editScope(index, (documentIndex >= 0) ? documentIndex : 0);
|
||||
} else {
|
||||
editWithUpload(index, 0);
|
||||
requestScopeFilesType(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -988,7 +961,7 @@ void PanelController::editWithUpload(int index, int documentIndex) {
|
||||
Expects(documentIndex >= 0
|
||||
&& documentIndex < _scopes[index].documents.size());
|
||||
|
||||
const auto &document = _scopes[index].documents[documentIndex];
|
||||
const auto document = _scopes[index].documents[documentIndex];
|
||||
const auto requiresSpecialScan = document->requiresSpecialScan(
|
||||
SpecialFile::FrontSide);
|
||||
const auto allowMany = !requiresSpecialScan;
|
||||
@ -998,7 +971,7 @@ void PanelController::editWithUpload(int index, int documentIndex) {
|
||||
_scopeDocumentTypeBox = BoxPointer();
|
||||
}
|
||||
if (!_editScope || !_editDocument) {
|
||||
editScope(index, documentIndex);
|
||||
startScopeEdit(index, documentIndex);
|
||||
}
|
||||
if (requiresSpecialScan) {
|
||||
uploadSpecialScan(SpecialFile::FrontSide, std::move(content));
|
||||
@ -1026,9 +999,37 @@ void PanelController::readScanError(ReadScanError error) {
|
||||
}()));
|
||||
}
|
||||
|
||||
bool PanelController::editRequiresScanUpload(
|
||||
int index,
|
||||
int documentIndex) const {
|
||||
Expects(index >= 0 && index < _scopes.size());
|
||||
Expects((documentIndex < 0)
|
||||
|| (documentIndex >= 0
|
||||
&& documentIndex < _scopes[index].documents.size()));
|
||||
|
||||
if (documentIndex < 0) {
|
||||
return false;
|
||||
}
|
||||
const auto document = _scopes[index].documents[documentIndex];
|
||||
if (document->requiresSpecialScan(SpecialFile::FrontSide)) {
|
||||
const auto &scans = document->specialScans;
|
||||
return (scans.find(SpecialFile::FrontSide) == end(scans));
|
||||
}
|
||||
return document->scans.empty();
|
||||
}
|
||||
|
||||
void PanelController::editScope(int index, int documentIndex) {
|
||||
if (editRequiresScanUpload(index, documentIndex)) {
|
||||
editWithUpload(index, documentIndex);
|
||||
} else {
|
||||
startScopeEdit(index, documentIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void PanelController::startScopeEdit(int index, int documentIndex) {
|
||||
Expects(_panel != nullptr);
|
||||
Expects(index >= 0 && index < _scopes.size());
|
||||
Expects(_scopes[index].details != 0 || documentIndex >= 0);
|
||||
Expects((documentIndex < 0)
|
||||
|| (documentIndex >= 0
|
||||
&& documentIndex < _scopes[index].documents.size()));
|
||||
@ -1038,7 +1039,6 @@ void PanelController::editScope(int index, int documentIndex) {
|
||||
_editDocument = (documentIndex >= 0)
|
||||
? _scopes[index].documents[documentIndex].get()
|
||||
: nullptr;
|
||||
Assert(_editValue || _editDocument);
|
||||
|
||||
if (_editValue) {
|
||||
_form->startValueEdit(_editValue);
|
||||
@ -1048,7 +1048,6 @@ void PanelController::editScope(int index, int documentIndex) {
|
||||
}
|
||||
|
||||
auto content = [&]() -> object_ptr<Ui::RpWidget> {
|
||||
// #TODO passport pass and display value->error
|
||||
switch (_editScope->type) {
|
||||
case Scope::Type::Identity:
|
||||
case Scope::Type::Address: {
|
||||
@ -1060,7 +1059,9 @@ void PanelController::editScope(int index, int documentIndex) {
|
||||
GetDocumentScheme(
|
||||
_editScope->type,
|
||||
_editDocument->type),
|
||||
_editValue->error,
|
||||
_editValue->data.parsedInEdit,
|
||||
_editDocument->error,
|
||||
_editDocument->data.parsedInEdit,
|
||||
_editDocument->scanMissingError,
|
||||
valueFiles(*_editDocument),
|
||||
@ -1071,6 +1072,7 @@ void PanelController::editScope(int index, int documentIndex) {
|
||||
GetDocumentScheme(
|
||||
_editScope->type,
|
||||
_editDocument->type),
|
||||
_editDocument->error,
|
||||
_editDocument->data.parsedInEdit,
|
||||
_editDocument->scanMissingError,
|
||||
valueFiles(*_editDocument),
|
||||
@ -1085,10 +1087,11 @@ void PanelController::editScope(int index, int documentIndex) {
|
||||
case Scope::Type::AddressDetails: {
|
||||
Assert(_editValue != nullptr);
|
||||
auto result = object_ptr<PanelEditDocument>(
|
||||
_panel->widget(),
|
||||
this,
|
||||
GetDocumentScheme(_editScope->type),
|
||||
_editValue->data.parsedInEdit);
|
||||
_panel->widget(),
|
||||
this,
|
||||
GetDocumentScheme(_editScope->type),
|
||||
_editValue->error,
|
||||
_editValue->data.parsedInEdit);
|
||||
const auto weak = make_weak(result.data());
|
||||
_panelHasUnsavedChanges = [=] {
|
||||
return weak ? weak->hasUnsavedChanges() : false;
|
||||
@ -1148,7 +1151,7 @@ void PanelController::processValueSaveFinished(
|
||||
}
|
||||
|
||||
if ((_editValue == value || _editDocument == value) && !savingScope()) {
|
||||
if (auto errors = collectErrors(value); !errors.empty()) {
|
||||
if (auto errors = collectSaveErrors(value); !errors.empty()) {
|
||||
for (auto &&error : errors) {
|
||||
_saveErrors.fire(std::move(error));
|
||||
}
|
||||
|
@ -25,8 +25,8 @@ EditDocumentScheme GetDocumentScheme(
|
||||
base::optional<Value::Type> scansType = base::none);
|
||||
EditContactScheme GetContactScheme(Scope::Type type);
|
||||
|
||||
const std::map<QString, QString> &NativeNameKeys();
|
||||
const std::map<QString, QString> &LatinNameKeys();
|
||||
const std::map<QString, QString> &LatinToNativeMap();
|
||||
const std::map<QString, QString> &NativeToLatinMap();
|
||||
bool SkipFieldCheck(not_null<const Value*> value, const QString &key);
|
||||
|
||||
struct ScanInfo {
|
||||
@ -144,6 +144,8 @@ private:
|
||||
|
||||
void editScope(int index, int documentIndex);
|
||||
void editWithUpload(int index, int documentIndex);
|
||||
bool editRequiresScanUpload(int index, int documentIndex) const;
|
||||
void startScopeEdit(int index, int documentIndex);
|
||||
int findNonEmptyDocumentIndex(const Scope &scope) const;
|
||||
void requestScopeFilesType(int index);
|
||||
void cancelValueEdit();
|
||||
@ -158,7 +160,7 @@ private:
|
||||
bool hasValueDocument() const;
|
||||
bool hasValueFields() const;
|
||||
ScanInfo collectScanInfo(const EditFile &file) const;
|
||||
std::vector<ScopeError> collectErrors(
|
||||
std::vector<ScopeError> collectSaveErrors(
|
||||
not_null<const Value*> value) const;
|
||||
QString getDefaultContactValue(Scope::Type type) const;
|
||||
void deleteValueSure(bool withDetails);
|
||||
|
@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/wrap/fade_wrap.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "boxes/abstract_box.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "lang/lang_keys.h"
|
||||
@ -209,8 +210,10 @@ PanelEditDocument::PanelEditDocument(
|
||||
QWidget*,
|
||||
not_null<PanelController*> controller,
|
||||
Scheme scheme,
|
||||
const QString &error,
|
||||
const ValueMap &data,
|
||||
const ValueMap &scanData,
|
||||
const QString &scansError,
|
||||
const ValueMap &scansData,
|
||||
const QString &missingScansError,
|
||||
std::vector<ScanInfo> &&files,
|
||||
std::map<SpecialFile, ScanInfo> &&specialFiles)
|
||||
@ -224,8 +227,10 @@ PanelEditDocument::PanelEditDocument(
|
||||
langFactory(lng_passport_save_value),
|
||||
st::passportPanelSaveValue) {
|
||||
setupControls(
|
||||
&error,
|
||||
&data,
|
||||
&scanData,
|
||||
&scansError,
|
||||
&scansData,
|
||||
missingScansError,
|
||||
std::move(files),
|
||||
std::move(specialFiles));
|
||||
@ -235,7 +240,8 @@ PanelEditDocument::PanelEditDocument(
|
||||
QWidget*,
|
||||
not_null<PanelController*> controller,
|
||||
Scheme scheme,
|
||||
const ValueMap &scanData,
|
||||
const QString &scansError,
|
||||
const ValueMap &scansData,
|
||||
const QString &missingScansError,
|
||||
std::vector<ScanInfo> &&files,
|
||||
std::map<SpecialFile, ScanInfo> &&specialFiles)
|
||||
@ -250,7 +256,9 @@ PanelEditDocument::PanelEditDocument(
|
||||
st::passportPanelSaveValue) {
|
||||
setupControls(
|
||||
nullptr,
|
||||
&scanData,
|
||||
nullptr,
|
||||
&scansError,
|
||||
&scansData,
|
||||
missingScansError,
|
||||
std::move(files),
|
||||
std::move(specialFiles));
|
||||
@ -260,6 +268,7 @@ PanelEditDocument::PanelEditDocument(
|
||||
QWidget*,
|
||||
not_null<PanelController*> controller,
|
||||
Scheme scheme,
|
||||
const QString &error,
|
||||
const ValueMap &data)
|
||||
: _controller(controller)
|
||||
, _scheme(std::move(scheme))
|
||||
@ -270,18 +279,22 @@ PanelEditDocument::PanelEditDocument(
|
||||
this,
|
||||
langFactory(lng_passport_save_value),
|
||||
st::passportPanelSaveValue) {
|
||||
setupControls(&data, nullptr, QString(), {}, {});
|
||||
setupControls(&error, &data, nullptr, nullptr, QString(), {}, {});
|
||||
}
|
||||
|
||||
void PanelEditDocument::setupControls(
|
||||
const QString *error,
|
||||
const ValueMap *data,
|
||||
const ValueMap *scanData,
|
||||
const QString *scansError,
|
||||
const ValueMap *scansData,
|
||||
const QString &missingScansError,
|
||||
std::vector<ScanInfo> &&files,
|
||||
std::map<SpecialFile, ScanInfo> &&specialFiles) {
|
||||
const auto inner = setupContent(
|
||||
error,
|
||||
data,
|
||||
scanData,
|
||||
scansError,
|
||||
scansData,
|
||||
missingScansError,
|
||||
std::move(files),
|
||||
std::move(specialFiles));
|
||||
@ -298,8 +311,10 @@ void PanelEditDocument::setupControls(
|
||||
}
|
||||
|
||||
not_null<Ui::RpWidget*> PanelEditDocument::setupContent(
|
||||
const QString *error,
|
||||
const ValueMap *data,
|
||||
const ValueMap *scanData,
|
||||
const QString *scansError,
|
||||
const ValueMap *scansData,
|
||||
const QString &missingScansError,
|
||||
std::vector<ScanInfo> &&files,
|
||||
std::map<SpecialFile, ScanInfo> &&specialFiles) {
|
||||
@ -315,22 +330,17 @@ not_null<Ui::RpWidget*> PanelEditDocument::setupContent(
|
||||
object_ptr<EditScans>(
|
||||
inner,
|
||||
_controller,
|
||||
// _scheme.scansHeader,
|
||||
// missingScansError,
|
||||
// std::move(files),
|
||||
*scansError,
|
||||
std::move(specialFiles)));
|
||||
} else if (scanData) {
|
||||
} else if (scansData) {
|
||||
_editScans = inner->add(
|
||||
object_ptr<EditScans>(
|
||||
inner,
|
||||
_controller,
|
||||
_scheme.scansHeader,
|
||||
*scansError,
|
||||
missingScansError,
|
||||
std::move(files)));
|
||||
} else {
|
||||
inner->add(object_ptr<BoxContentDivider>(
|
||||
inner,
|
||||
st::passportFormDividerHeight));
|
||||
}
|
||||
|
||||
const auto valueOrEmpty = [&](
|
||||
@ -348,7 +358,7 @@ not_null<Ui::RpWidget*> PanelEditDocument::setupContent(
|
||||
const auto &row = _scheme.rows[i];
|
||||
auto fields = (row.valueClass == Scheme::ValueClass::Fields)
|
||||
? data
|
||||
: scanData;
|
||||
: scansData;
|
||||
if (!fields) {
|
||||
continue;
|
||||
}
|
||||
@ -365,6 +375,18 @@ not_null<Ui::RpWidget*> PanelEditDocument::setupContent(
|
||||
PanelDetailsRow::LabelWidth(row.label));
|
||||
});
|
||||
if (maxLabelWidth > 0) {
|
||||
if (error && !error->isEmpty()) {
|
||||
_commonError = inner->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::FlatLabel>>(
|
||||
inner,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
inner,
|
||||
*error,
|
||||
Ui::FlatLabel::InitType::Simple,
|
||||
st::passportVerifyErrorLabel),
|
||||
st::passportValueErrorPadding));
|
||||
_commonError->toggle(true, anim::type::instant);
|
||||
}
|
||||
inner->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
inner,
|
||||
@ -377,15 +399,28 @@ not_null<Ui::RpWidget*> PanelEditDocument::setupContent(
|
||||
const EditDocumentScheme::Row &row,
|
||||
const ValueMap &fields) {
|
||||
const auto current = valueOrEmpty(fields, row.key);
|
||||
_details.emplace(i, inner->add(PanelDetailsRow::Create(
|
||||
inner,
|
||||
row.inputType,
|
||||
_controller,
|
||||
row.label,
|
||||
maxLabelWidth,
|
||||
current.text,
|
||||
current.error,
|
||||
row.lengthLimit)));
|
||||
const auto [it, ok] = _details.emplace(
|
||||
i,
|
||||
inner->add(PanelDetailsRow::Create(
|
||||
inner,
|
||||
row.inputType,
|
||||
_controller,
|
||||
row.label,
|
||||
maxLabelWidth,
|
||||
current.text,
|
||||
current.error,
|
||||
row.lengthLimit)));
|
||||
const bool details = (&fields == data);
|
||||
it->second->value(
|
||||
) | rpl::skip(1) | rpl::start_with_next([=] {
|
||||
if (details) {
|
||||
_fieldsChanged = true;
|
||||
updateCommonError();
|
||||
} else {
|
||||
Assert(_editScans != nullptr);
|
||||
_editScans->scanFieldsChanged(true);
|
||||
}
|
||||
}, it->second->lifetime());
|
||||
});
|
||||
|
||||
inner->add(
|
||||
@ -406,6 +441,12 @@ not_null<Ui::RpWidget*> PanelEditDocument::setupContent(
|
||||
return inner;
|
||||
}
|
||||
|
||||
void PanelEditDocument::updateCommonError() {
|
||||
if (_commonError) {
|
||||
_commonError->toggle(!_fieldsChanged, anim::type::normal);
|
||||
}
|
||||
}
|
||||
|
||||
void PanelEditDocument::focusInEvent(QFocusEvent *e) {
|
||||
crl::on_main(this, [=] {
|
||||
for (const auto [index, row] : _details) {
|
||||
@ -451,7 +492,7 @@ PanelEditDocument::Result PanelEditDocument::collect() const {
|
||||
}
|
||||
|
||||
bool PanelEditDocument::validate() {
|
||||
const auto error = _editScans
|
||||
auto error = _editScans
|
||||
? _editScans->validateGetErrorTop()
|
||||
: base::none;
|
||||
if (error) {
|
||||
@ -459,6 +500,12 @@ bool PanelEditDocument::validate() {
|
||||
const auto scrolltop = _scroll->mapToGlobal(QPoint(0, 0));
|
||||
const auto scrolldelta = errortop.y() - scrolltop.y();
|
||||
_scroll->scrollToY(_scroll->scrollTop() + scrolldelta);
|
||||
} else if (_commonError && !_fieldsChanged) {
|
||||
const auto firsttop = _commonError->mapToGlobal(QPoint(0, 0));
|
||||
const auto scrolltop = _scroll->mapToGlobal(QPoint(0, 0));
|
||||
const auto scrolldelta = firsttop.y() - scrolltop.y();
|
||||
_scroll->scrollToY(_scroll->scrollTop() + scrolldelta);
|
||||
error = firsttop.y();
|
||||
}
|
||||
auto first = QPointer<PanelDetailsRow>();
|
||||
for (const auto [i, field] : base::reversed(_details)) {
|
||||
|
@ -14,7 +14,10 @@ class InputField;
|
||||
class ScrollArea;
|
||||
class FadeShadow;
|
||||
class PlainShadow;
|
||||
class FlatLabel;
|
||||
class RoundButton;
|
||||
template <typename Widget>
|
||||
class SlideWrap;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Info {
|
||||
@ -63,8 +66,10 @@ public:
|
||||
QWidget *parent,
|
||||
not_null<PanelController*> controller,
|
||||
Scheme scheme,
|
||||
const QString &error,
|
||||
const ValueMap &data,
|
||||
const ValueMap &scanData,
|
||||
const QString &scansError,
|
||||
const ValueMap &scansData,
|
||||
const QString &missingScansError,
|
||||
std::vector<ScanInfo> &&files,
|
||||
std::map<SpecialFile, ScanInfo> &&specialFiles);
|
||||
@ -72,7 +77,8 @@ public:
|
||||
QWidget *parent,
|
||||
not_null<PanelController*> controller,
|
||||
Scheme scheme,
|
||||
const ValueMap &scanData,
|
||||
const QString &scansError,
|
||||
const ValueMap &scansData,
|
||||
const QString &missingScansError,
|
||||
std::vector<ScanInfo> &&files,
|
||||
std::map<SpecialFile, ScanInfo> &&specialFiles);
|
||||
@ -80,6 +86,7 @@ public:
|
||||
QWidget *parent,
|
||||
not_null<PanelController*> controller,
|
||||
Scheme scheme,
|
||||
const QString &error,
|
||||
const ValueMap &data);
|
||||
|
||||
bool hasUnsavedChanges() const;
|
||||
@ -91,18 +98,23 @@ protected:
|
||||
private:
|
||||
struct Result;
|
||||
void setupControls(
|
||||
const QString *error,
|
||||
const ValueMap *data,
|
||||
const ValueMap *scanData,
|
||||
const QString *scansError,
|
||||
const ValueMap *scansData,
|
||||
const QString &missingScansError,
|
||||
std::vector<ScanInfo> &&files,
|
||||
std::map<SpecialFile, ScanInfo> &&specialFiles);
|
||||
not_null<Ui::RpWidget*> setupContent(
|
||||
const QString *error,
|
||||
const ValueMap *data,
|
||||
const ValueMap *scanData,
|
||||
const QString *scansError,
|
||||
const ValueMap *scansData,
|
||||
const QString &missingScansError,
|
||||
std::vector<ScanInfo> &&files,
|
||||
std::map<SpecialFile, ScanInfo> &&specialFiles);
|
||||
void updateControlsGeometry();
|
||||
void updateCommonError();
|
||||
|
||||
Result collect() const;
|
||||
bool validate();
|
||||
@ -116,7 +128,9 @@ private:
|
||||
object_ptr<Ui::PlainShadow> _bottomShadow;
|
||||
|
||||
QPointer<EditScans> _editScans;
|
||||
QPointer<Ui::SlideWrap<Ui::FlatLabel>> _commonError;
|
||||
std::map<int, QPointer<PanelDetailsRow>> _details;
|
||||
bool _fieldsChanged = false;
|
||||
|
||||
QPointer<Info::Profile::Button> _delete;
|
||||
|
||||
|
@ -257,12 +257,14 @@ EditScans::EditScans(
|
||||
QWidget *parent,
|
||||
not_null<PanelController*> controller,
|
||||
const QString &header,
|
||||
const QString &error,
|
||||
const QString &errorMissing,
|
||||
std::vector<ScanInfo> &&files)
|
||||
: RpWidget(parent)
|
||||
, _controller(controller)
|
||||
, _files(std::move(files))
|
||||
, _initialCount(_files.size())
|
||||
, _error(error)
|
||||
, _errorMissing(errorMissing)
|
||||
, _content(this) {
|
||||
setupScans(header);
|
||||
@ -271,15 +273,20 @@ EditScans::EditScans(
|
||||
EditScans::EditScans(
|
||||
QWidget *parent,
|
||||
not_null<PanelController*> controller,
|
||||
const QString &error,
|
||||
std::map<SpecialFile, ScanInfo> &&specialFiles)
|
||||
: RpWidget(parent)
|
||||
, _controller(controller)
|
||||
, _initialCount(-1)
|
||||
, _error(error)
|
||||
, _content(this) {
|
||||
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) {
|
||||
@ -303,6 +310,9 @@ base::optional<int> EditScans::validateGetErrorTop() {
|
||||
[](const ScanInfo &file) { return !file.error.isEmpty(); }
|
||||
) != end(_files);
|
||||
|
||||
if (_commonError && !somethingChanged()) {
|
||||
suggestResult(_commonError->y());
|
||||
}
|
||||
if (_upload && (!exists
|
||||
|| ((errorExists || _uploadMoreError) && !uploadedSomeMore()))) {
|
||||
toggleError(true);
|
||||
@ -334,6 +344,19 @@ void EditScans::setupScans(const QString &header) {
|
||||
const auto inner = _content.data();
|
||||
inner->move(0, 0);
|
||||
|
||||
if (!_error.isEmpty()) {
|
||||
_commonError = inner->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::FlatLabel>>(
|
||||
inner,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
inner,
|
||||
_error,
|
||||
Ui::FlatLabel::InitType::Simple,
|
||||
st::passportVerifyErrorLabel),
|
||||
st::passportValueErrorPadding));
|
||||
_commonError->toggle(true, anim::type::instant);
|
||||
}
|
||||
|
||||
_divider = inner->add(
|
||||
object_ptr<Ui::SlideWrap<BoxContentDivider>>(
|
||||
inner,
|
||||
@ -442,6 +465,20 @@ void EditScans::setupSpecialScans(std::map<SpecialFile, ScanInfo> &&files) {
|
||||
|
||||
const auto inner = _content.data();
|
||||
inner->move(0, 0);
|
||||
|
||||
if (!_error.isEmpty()) {
|
||||
_commonError = inner->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::FlatLabel>>(
|
||||
inner,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
inner,
|
||||
_error,
|
||||
Ui::FlatLabel::InitType::Simple,
|
||||
st::passportVerifyErrorLabel),
|
||||
st::passportValueErrorPadding));
|
||||
_commonError->toggle(true, anim::type::instant);
|
||||
}
|
||||
|
||||
for (auto &[type, info] : files) {
|
||||
const auto i = _specialScans.emplace(
|
||||
type,
|
||||
@ -531,9 +568,27 @@ void EditScans::updateScan(ScanInfo &&info) {
|
||||
_header->show(anim::type::normal);
|
||||
_uploadTexts.fire(uploadButtonText());
|
||||
}
|
||||
updateErrorLabels();
|
||||
}
|
||||
|
||||
void EditScans::scanFieldsChanged(bool changed) {
|
||||
if (_scanFieldsChanged != changed) {
|
||||
_scanFieldsChanged = changed;
|
||||
updateErrorLabels();
|
||||
}
|
||||
}
|
||||
|
||||
void EditScans::updateErrorLabels() {
|
||||
if (_uploadMoreError) {
|
||||
_uploadMoreError->toggle(!uploadedSomeMore(), anim::type::normal);
|
||||
}
|
||||
if (_commonError) {
|
||||
_commonError->toggle(!somethingChanged(), anim::type::normal);
|
||||
}
|
||||
}
|
||||
|
||||
bool EditScans::somethingChanged() const {
|
||||
return uploadedSomeMore() || _scanFieldsChanged || _specialScanChanged;
|
||||
}
|
||||
|
||||
void EditScans::updateSpecialScan(SpecialFile type, ScanInfo &&info) {
|
||||
@ -547,8 +602,8 @@ void EditScans::updateSpecialScan(SpecialFile type, ScanInfo &&info) {
|
||||
if (scan.file.key.id) {
|
||||
updateFileRow(scan.row->entity(), info);
|
||||
scan.rowCreated = !info.deleted;
|
||||
if (!info.deleted) {
|
||||
hideSpecialScanError(type);
|
||||
if (scan.file.key.id != info.key.id) {
|
||||
specialScanChanged(type, true);
|
||||
}
|
||||
} else {
|
||||
const auto requiresBothSides
|
||||
@ -558,6 +613,7 @@ void EditScans::updateSpecialScan(SpecialFile type, ScanInfo &&info) {
|
||||
scan.wrap->resizeToWidth(width());
|
||||
scan.row->show(anim::type::normal);
|
||||
scan.header->show(anim::type::normal);
|
||||
specialScanChanged(type, true);
|
||||
}
|
||||
scan.file = std::move(info);
|
||||
}
|
||||
@ -569,8 +625,7 @@ void EditScans::updateFileRow(
|
||||
button->setImage(info.thumb);
|
||||
button->setDeleted(info.deleted);
|
||||
button->setError(!info.error.isEmpty());
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
void EditScans::createSpecialScanRow(
|
||||
SpecialScan &scan,
|
||||
@ -606,7 +661,6 @@ void EditScans::createSpecialScanRow(
|
||||
}, row->lifetime());
|
||||
|
||||
scan.rowCreated = !info.deleted;
|
||||
hideSpecialScanError(type);
|
||||
}
|
||||
|
||||
void EditScans::pushScan(const ScanInfo &info) {
|
||||
@ -793,6 +847,14 @@ void EditScans::hideSpecialScanError(SpecialFile type) {
|
||||
toggleSpecialScanError(type, false);
|
||||
}
|
||||
|
||||
void EditScans::specialScanChanged(SpecialFile type, bool changed) {
|
||||
hideSpecialScanError(type);
|
||||
if (_specialScanChanged != changed) {
|
||||
_specialScanChanged = changed;
|
||||
updateErrorLabels();
|
||||
}
|
||||
}
|
||||
|
||||
auto EditScans::findSpecialScan(SpecialFile type) -> SpecialScan& {
|
||||
const auto i = _specialScans.find(type);
|
||||
Assert(i != end(_specialScans));
|
||||
|
@ -44,15 +44,19 @@ public:
|
||||
QWidget *parent,
|
||||
not_null<PanelController*> controller,
|
||||
const QString &header,
|
||||
const QString &error,
|
||||
const QString &errorMissing,
|
||||
std::vector<ScanInfo> &&files);
|
||||
EditScans(
|
||||
QWidget *parent,
|
||||
not_null<PanelController*> controller,
|
||||
const QString &error,
|
||||
std::map<SpecialFile, ScanInfo> &&specialFiles);
|
||||
|
||||
base::optional<int> validateGetErrorTop();
|
||||
|
||||
void scanFieldsChanged(bool changed);
|
||||
|
||||
static void ChooseScan(
|
||||
QPointer<QWidget> parent,
|
||||
Fn<void(QByteArray&&)> doneCallback,
|
||||
@ -88,28 +92,35 @@ private:
|
||||
|
||||
rpl::producer<QString> uploadButtonText() const;
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user