Support common error for the whole value.

It is removed (considered fixed) if anything changes in the data.
This commit is contained in:
John Preston 2018-08-14 14:37:03 +03:00
parent cb827406ca
commit b935d54fe7
11 changed files with 328 additions and 130 deletions

View file

@ -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;

View file

@ -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) {

View file

@ -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);

View 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;
}

View file

@ -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);

View file

@ -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));
}

View file

@ -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);

View file

@ -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)) {

View file

@ -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;

View file

@ -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));

View file

@ -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;