Display scope errors in passport.

This commit is contained in:
John Preston 2018-04-17 21:54:52 +04:00
parent 704e3c9423
commit 22bdf15825
15 changed files with 274 additions and 38 deletions

View File

@ -844,6 +844,7 @@ bool Messenger::openLocalUrl(const QString &url) {
const auto callback = params.value("callback_url", QString()); const auto callback = params.value("callback_url", QString());
const auto publicKey = params.value("public_key", QString()); const auto publicKey = params.value("public_key", QString());
const auto payload = params.value("payload", QString()); const auto payload = params.value("payload", QString());
const auto errors = params.value("errors", QString());
if (const auto window = App::wnd()) { if (const auto window = App::wnd()) {
if (const auto controller = window->controller()) { if (const auto controller = window->controller()) {
controller->showPassportForm(Passport::FormRequest( controller->showPassportForm(Passport::FormRequest(
@ -851,7 +852,8 @@ bool Messenger::openLocalUrl(const QString &url) {
scope, scope,
callback, callback,
publicKey, publicKey,
payload)); payload,
errors));
return true; return true;
} }
} }

View File

@ -172,6 +172,7 @@ passportUploadButton: InfoProfileButton {
} }
passportUploadButtonPadding: margins(0px, 10px, 0px, 10px); passportUploadButtonPadding: margins(0px, 10px, 0px, 10px);
passportUploadHeaderPadding: margins(22px, 14px, 22px, 3px); passportUploadHeaderPadding: margins(22px, 14px, 22px, 3px);
passportUploadErrorPadding: margins(22px, 5px, 22px, 5px);
passportDeleteButton: InfoProfileButton(passportUploadButton) { passportDeleteButton: InfoProfileButton(passportUploadButton) {
textFg: attentionButtonFg; textFg: attentionButtonFg;
textFgOver: attentionButtonFgOver; textFgOver: attentionButtonFgOver;

View File

@ -238,6 +238,78 @@ std::map<QString, QString> DeserializeData(bytes::const_span bytes) {
return result; return result;
} }
std::vector<DataError> DeserializeErrors(bytes::const_span json) {
const auto serialized = QByteArray::fromRawData(
reinterpret_cast<const char*>(json.data()),
json.size());
auto error = QJsonParseError();
auto document = QJsonDocument::fromJson(serialized, &error);
if (error.error != QJsonParseError::NoError) {
LOG(("API Error: Could not deserialize errors JSON, error %1"
).arg(error.errorString()));
return {};
} else if (!document.isArray()) {
LOG(("API Error: Errors JSON root is not an array."));
return {};
}
auto array = document.array();
auto result = std::vector<DataError>();
for (const auto &error : array) {
if (!error.isObject()) {
LOG(("API Error: Not an object inside errors JSON."));
continue;
}
auto fields = error.toObject();
const auto typeIt = fields.constFind("type");
if (typeIt == fields.constEnd()) {
LOG(("API Error: type was not found in an error."));
continue;
} else if (!typeIt->isString()) {
LOG(("API Error: type was not a string in an error."));
continue;
}
const auto descriptionIt = fields.constFind("description");
if (descriptionIt == fields.constEnd()) {
LOG(("API Error: description was not found in an error."));
continue;
} else if (!typeIt->isString()) {
LOG(("API Error: description was not a string in an error."));
continue;
}
const auto targetIt = fields.constFind("target");
if (targetIt == fields.constEnd()) {
LOG(("API Error: target aws not found in an error."));
continue;
} else if (!targetIt->isString()) {
LOG(("API Error: target was not as string in an error."));
continue;
}
auto next = DataError();
next.type = typeIt->toString();
next.text = descriptionIt->toString();
const auto fieldIt = fields.constFind("field");
const auto fileHashIt = fields.constFind("file_hash");
if (fieldIt != fields.constEnd()) {
if (!fieldIt->isString()) {
LOG(("API Error: field was not a string in an error."));
continue;
}
next.key = fieldIt->toString();
} else if (fileHashIt != fields.constEnd()) {
if (!fileHashIt->isString()) {
LOG(("API Error: file_hash was not a string in an error."));
continue;
}
next.key = QByteArray::fromBase64(
fileHashIt->toString().toUtf8());
} else if (targetIt->toString() == "selfie") {
next.key = QByteArray();
}
result.push_back(std::move(next));
}
return result;
}
EncryptedData EncryptData(bytes::const_span bytes) { EncryptedData EncryptData(bytes::const_span bytes) {
return EncryptData(bytes, GenerateSecretBytes()); return EncryptData(bytes, GenerateSecretBytes());
} }

View File

@ -23,6 +23,17 @@ bytes::vector DecryptSecureSecret(
bytes::vector SerializeData(const std::map<QString, QString> &data); bytes::vector SerializeData(const std::map<QString, QString> &data);
std::map<QString, QString> DeserializeData(bytes::const_span bytes); std::map<QString, QString> DeserializeData(bytes::const_span bytes);
struct DataError {
// QByteArray - bad existing scan with such file_hash
// QString - bad data field value with such key
// base::none - additional scan required
base::optional_variant<QByteArray, QString> key;
QString type; // personal_details, passport, etc.
QString text;
};
std::vector<DataError> DeserializeErrors(bytes::const_span json);
struct EncryptedData { struct EncryptedData {
bytes::vector secret; bytes::vector secret;
bytes::vector hash; bytes::vector hash;

View File

@ -156,12 +156,14 @@ FormRequest::FormRequest(
const QString &scope, const QString &scope,
const QString &callbackUrl, const QString &callbackUrl,
const QString &publicKey, const QString &publicKey,
const QString &payload) const QString &payload,
const QString &errors)
: botId(botId) : botId(botId)
, scope(scope) , scope(scope)
, callbackUrl(callbackUrl) , callbackUrl(callbackUrl)
, publicKey(publicKey) , publicKey(publicKey)
, payload(payload) { , payload(payload)
, errors(errors) {
} }
EditFile::EditFile( EditFile::EditFile(
@ -247,8 +249,8 @@ auto FormController::prepareFinalData() -> FinalData {
auto hashes = QVector<MTPSecureValueHash>(); auto hashes = QVector<MTPSecureValueHash>();
auto secureData = QJsonObject(); auto secureData = QJsonObject();
const auto addValueToJSON = [&]( const auto addValueToJSON = [&](
const QString &key, const QString &key,
not_null<const Value*> value) { not_null<const Value*> value) {
auto object = QJsonObject(); auto object = QJsonObject();
if (!value->data.parsed.fields.empty()) { if (!value->data.parsed.fields.empty()) {
object.insert("data", GetJSONFromMap({ object.insert("data", GetJSONFromMap({
@ -279,8 +281,8 @@ auto FormController::prepareFinalData() -> FinalData {
}; };
const auto scopes = ComputeScopes(this); const auto scopes = ComputeScopes(this);
for (const auto &scope : scopes) { for (const auto &scope : scopes) {
const auto ready = ComputeScopeRowReadyString(scope); const auto row = ComputeScopeRow(scope);
if (ready.isEmpty()) { if (row.ready.isEmpty() || !row.error.isEmpty()) {
errors.push_back(scope.fields); errors.push_back(scope.fields);
continue; continue;
} }
@ -489,6 +491,43 @@ void FormController::decryptValues() {
for (auto &[type, value] : _form.values) { for (auto &[type, value] : _form.values) {
decryptValue(value); decryptValue(value);
} }
fillErrors();
}
void FormController::fillErrors() {
const auto errors = _request.errors.toUtf8();
const auto list = DeserializeErrors(bytes::make_span(errors));
for (const auto &error : list) {
for (auto &[type, value] : _form.values) {
if (ValueCredentialsKey(type) != error.type) {
continue;
}
if (!error.key.has_value()) {
value.scanMissingError = error.text;
} else if (const auto key = base::get_if<QString>(&error.key)) {
value.data.parsed.fields[(*key)].error = error.text;
} else if (auto hash = base::get_if<QByteArray>(&error.key)) {
const auto check = [&](const File &file) {
return *hash == QByteArray::fromRawData(
reinterpret_cast<const char*>(file.hash.data()),
file.hash.size());
};
for (auto &scan : value.scans) {
if (check(scan)) {
scan.error = error.text;
break;
}
}
if (value.selfie) {
if (check(*value.selfie) || hash->isEmpty()) {
value.selfie->error = error.text;
}
}
}
break;
}
}
} }
void FormController::decryptValue(Value &value) { void FormController::decryptValue(Value &value) {

View File

@ -32,13 +32,15 @@ struct FormRequest {
const QString &scope, const QString &scope,
const QString &callbackUrl, const QString &callbackUrl,
const QString &publicKey, const QString &publicKey,
const QString &payload); const QString &payload,
const QString &errors);
UserId botId; UserId botId;
QString scope; QString scope;
QString callbackUrl; QString callbackUrl;
QString publicKey; QString publicKey;
QString payload; QString payload;
QString errors;
}; };
@ -317,6 +319,7 @@ private:
void decryptValue(Value &value); void decryptValue(Value &value);
bool validateValueSecrets(Value &value); bool validateValueSecrets(Value &value);
void resetValue(Value &value); void resetValue(Value &value);
void fillErrors();
void loadFile(File &file); void loadFile(File &file);
void fileLoadDone(FileKey key, const QByteArray &bytes); void fileLoadDone(FileKey key, const QByteArray &bytes);

View File

@ -188,14 +188,28 @@ ScopeRow ComputeScopeRow(const Scope &scope) {
const auto addReadyError = [&](ScopeRow &&row) { const auto addReadyError = [&](ScopeRow &&row) {
const auto ready = ComputeScopeRowReadyString(scope); const auto ready = ComputeScopeRowReadyString(scope);
row.ready = ready; row.ready = ready;
// #TODO passport bot errors auto errors = QStringList();
//row.error = scope.fields->error.has_value() const auto addValueErrors = [&](not_null<const Value*> value) {
// ? (!scope.fields->error->isEmpty() for (const auto &scan : value->scans) {
// ? *scope.fields->error if (!scan.error.isEmpty()) {
// : !ready.isEmpty() errors.push_back(scan.error);
// ? ready }
// : row.description) }
// : QString(); if (value->selfie && !value->selfie->error.isEmpty()) {
errors.push_back(value->selfie->error);
}
if (!value->scanMissingError.isEmpty()) {
errors.push_back(value->scanMissingError);
}
for (const auto &[key, value] : value->data.parsed.fields) {
if (!value.error.isEmpty()) {
errors.push_back(value.error);
}
}
};
ranges::for_each(scope.documents, addValueErrors);
addValueErrors(scope.fields);
row.error = errors.join('\n');
return row; return row;
}; };
switch (scope.type) { switch (scope.type) {

View File

@ -565,7 +565,7 @@ ScanInfo PanelController::collectScanInfo(const EditFile &file) const {
&& (&file == &*_editDocument->selfieInEdit); && (&file == &*_editDocument->selfieInEdit);
return { return {
FileKey{ file.fields.id, file.fields.dcId }, FileKey{ file.fields.id, file.fields.dcId },
status, !file.fields.error.isEmpty() ? file.fields.error : status,
file.fields.image, file.fields.image,
file.deleted, file.deleted,
isSelfie, isSelfie,
@ -871,6 +871,7 @@ void PanelController::editScope(int index, int documentIndex) {
_editDocument->type), _editDocument->type),
_editValue->data.parsedInEdit, _editValue->data.parsedInEdit,
_editDocument->data.parsedInEdit, _editDocument->data.parsedInEdit,
_editDocument->scanMissingError,
valueFiles(*_editDocument), valueFiles(*_editDocument),
(_editScope->selfieRequired (_editScope->selfieRequired
? valueSelfie(*_editDocument) ? valueSelfie(*_editDocument)

View File

@ -930,10 +930,14 @@ int PanelDetailsRow::resizeGetHeight(int newWidth) {
const auto inputRight = padding.right(); const auto inputRight = padding.right();
const auto inputWidth = std::max(newWidth - inputLeft - inputRight, 0); const auto inputWidth = std::max(newWidth - inputLeft - inputRight, 0);
const auto innerHeight = resizeInner(inputLeft, inputTop, inputWidth); const auto innerHeight = resizeInner(inputLeft, inputTop, inputWidth);
return padding.top() const auto result = padding.top()
+ innerHeight + innerHeight
+ (_error ? _error->height() : 0) + (_error ? _error->height() : 0)
+ padding.bottom(); + padding.bottom();
if (_error) {
_error->moveToLeft(inputLeft, result - _error->height());
}
return result;
} }
void PanelDetailsRow::showError(const QString &error) { void PanelDetailsRow::showError(const QString &error) {
@ -959,12 +963,18 @@ void PanelDetailsRow::showError(const QString &error) {
} else { } else {
_error->entity()->setText(error); _error->entity()->setText(error);
} }
_error->heightValue(
) | rpl::start_with_next([=] {
resizeToWidth(width());
}, _error->lifetime());
_error->show(anim::type::normal); _error->show(anim::type::normal);
} else if (_error) {
_error->hide(anim::type::normal);
} }
} }
bool PanelDetailsRow::errorShown() const {
return _errorShown;
}
void PanelDetailsRow::hideError() { void PanelDetailsRow::hideError() {
startErrorAnimation(false); startErrorAnimation(false);
if (_error) { if (_error) {

View File

@ -66,6 +66,7 @@ public:
virtual rpl::producer<QString> value() const = 0; virtual rpl::producer<QString> value() const = 0;
virtual QString valueCurrent() const = 0; virtual QString valueCurrent() const = 0;
void showError(const QString &error); void showError(const QString &error);
bool errorShown() const;
void hideError(); void hideError();
void finishAnimating(); void finishAnimating();

View File

@ -211,6 +211,7 @@ PanelEditDocument::PanelEditDocument(
Scheme scheme, Scheme scheme,
const ValueMap &data, const ValueMap &data,
const ValueMap &scanData, const ValueMap &scanData,
const QString &missingScansError,
std::vector<ScanInfo> &&files, std::vector<ScanInfo> &&files,
std::unique_ptr<ScanInfo> &&selfie) std::unique_ptr<ScanInfo> &&selfie)
: _controller(controller) : _controller(controller)
@ -222,7 +223,12 @@ PanelEditDocument::PanelEditDocument(
this, this,
langFactory(lng_passport_save_value), langFactory(lng_passport_save_value),
st::passportPanelSaveValue) { st::passportPanelSaveValue) {
setupControls(data, &scanData, std::move(files), std::move(selfie)); setupControls(
data,
&scanData,
missingScansError,
std::move(files),
std::move(selfie));
} }
PanelEditDocument::PanelEditDocument( PanelEditDocument::PanelEditDocument(
@ -239,17 +245,19 @@ PanelEditDocument::PanelEditDocument(
this, this,
langFactory(lng_passport_save_value), langFactory(lng_passport_save_value),
st::passportPanelSaveValue) { st::passportPanelSaveValue) {
setupControls(data, nullptr, {}, nullptr); setupControls(data, nullptr, QString(), {}, nullptr);
} }
void PanelEditDocument::setupControls( void PanelEditDocument::setupControls(
const ValueMap &data, const ValueMap &data,
const ValueMap *scanData, const ValueMap *scanData,
const QString &missingScansError,
std::vector<ScanInfo> &&files, std::vector<ScanInfo> &&files,
std::unique_ptr<ScanInfo> &&selfie) { std::unique_ptr<ScanInfo> &&selfie) {
const auto inner = setupContent( const auto inner = setupContent(
data, data,
scanData, scanData,
missingScansError,
std::move(files), std::move(files),
std::move(selfie)); std::move(selfie));
@ -267,6 +275,7 @@ void PanelEditDocument::setupControls(
not_null<Ui::RpWidget*> PanelEditDocument::setupContent( not_null<Ui::RpWidget*> PanelEditDocument::setupContent(
const ValueMap &data, const ValueMap &data,
const ValueMap *scanData, const ValueMap *scanData,
const QString &missingScansError,
std::vector<ScanInfo> &&files, std::vector<ScanInfo> &&files,
std::unique_ptr<ScanInfo> &&selfie) { std::unique_ptr<ScanInfo> &&selfie) {
const auto inner = _scroll->setOwnedWidget( const auto inner = _scroll->setOwnedWidget(
@ -282,6 +291,7 @@ not_null<Ui::RpWidget*> PanelEditDocument::setupContent(
inner, inner,
_controller, _controller,
_scheme.scansHeader, _scheme.scansHeader,
missingScansError,
std::move(files), std::move(files),
std::move(selfie))); std::move(selfie)));
} else { } else {
@ -401,13 +411,16 @@ bool PanelEditDocument::validate() {
auto first = QPointer<PanelDetailsRow>(); auto first = QPointer<PanelDetailsRow>();
for (const auto [i, field] : base::reversed(_details)) { for (const auto [i, field] : base::reversed(_details)) {
const auto &row = _scheme.rows[i]; const auto &row = _scheme.rows[i];
if (row.validate && !row.validate(field->valueCurrent())) { if (field->errorShown()
|| (row.validate && !row.validate(field->valueCurrent()))) {
field->showError(QString()); field->showError(QString());
first = field; first = field;
} }
} }
if (!first) { if (error) {
return !error; return false;
} else if (!first) {
return true;
} }
const auto firsttop = first->mapToGlobal(QPoint(0, 0)); const auto firsttop = first->mapToGlobal(QPoint(0, 0));
const auto scrolltop = _scroll->mapToGlobal(QPoint(0, 0)); const auto scrolltop = _scroll->mapToGlobal(QPoint(0, 0));

View File

@ -62,6 +62,7 @@ public:
Scheme scheme, Scheme scheme,
const ValueMap &data, const ValueMap &data,
const ValueMap &scanData, const ValueMap &scanData,
const QString &missingScansError,
std::vector<ScanInfo> &&files, std::vector<ScanInfo> &&files,
std::unique_ptr<ScanInfo> &&selfie); std::unique_ptr<ScanInfo> &&selfie);
PanelEditDocument( PanelEditDocument(
@ -81,11 +82,13 @@ private:
void setupControls( void setupControls(
const ValueMap &data, const ValueMap &data,
const ValueMap *scanData, const ValueMap *scanData,
const QString &missingScansError,
std::vector<ScanInfo> &&files, std::vector<ScanInfo> &&files,
std::unique_ptr<ScanInfo> &&selfie); std::unique_ptr<ScanInfo> &&selfie);
not_null<Ui::RpWidget*> setupContent( not_null<Ui::RpWidget*> setupContent(
const ValueMap &data, const ValueMap &data,
const ValueMap *scanData, const ValueMap *scanData,
const QString &missingScansError,
std::vector<ScanInfo> &&files, std::vector<ScanInfo> &&files,
std::unique_ptr<ScanInfo> &&selfie); std::unique_ptr<ScanInfo> &&selfie);
void updateControlsGeometry(); void updateControlsGeometry();

View File

@ -31,11 +31,13 @@ public:
const style::PassportScanRow &st, const style::PassportScanRow &st,
const QString &name, const QString &name,
const QString &status, const QString &status,
bool deleted); bool deleted,
bool error);
void setImage(const QImage &image); void setImage(const QImage &image);
void setStatus(const QString &status); void setStatus(const QString &status);
void setDeleted(bool deleted); void setDeleted(bool deleted);
void setError(bool error);
rpl::producer<> deleteClicks() const { rpl::producer<> deleteClicks() const {
return _delete->entity()->clicks(); return _delete->entity()->clicks();
@ -57,6 +59,7 @@ private:
Text _status; Text _status;
int _nameHeight = 0; int _nameHeight = 0;
int _statusHeight = 0; int _statusHeight = 0;
bool _error = false;
QImage _image; QImage _image;
object_ptr<Ui::FadeWrapScaled<Ui::IconButton>> _delete; object_ptr<Ui::FadeWrapScaled<Ui::IconButton>> _delete;
object_ptr<Ui::FadeWrapScaled<Ui::RoundButton>> _restore; object_ptr<Ui::FadeWrapScaled<Ui::RoundButton>> _restore;
@ -68,7 +71,8 @@ ScanButton::ScanButton(
const style::PassportScanRow &st, const style::PassportScanRow &st,
const QString &name, const QString &name,
const QString &status, const QString &status,
bool deleted) bool deleted,
bool error)
: AbstractButton(parent) : AbstractButton(parent)
, _st(st) , _st(st)
, _name( , _name(
@ -79,6 +83,7 @@ ScanButton::ScanButton(
st::defaultTextStyle, st::defaultTextStyle,
status, status,
Ui::NameTextOptions()) Ui::NameTextOptions())
, _error(error)
, _delete(this, object_ptr<Ui::IconButton>(this, _st.remove)) , _delete(this, object_ptr<Ui::IconButton>(this, _st.remove))
, _restore( , _restore(
this, this,
@ -109,6 +114,11 @@ void ScanButton::setDeleted(bool deleted) {
update(); update();
} }
void ScanButton::setError(bool error) {
_error = error;
update();
}
int ScanButton::resizeGetHeight(int newWidth) { int ScanButton::resizeGetHeight(int newWidth) {
_nameHeight = st::semiboldFont->height; _nameHeight = st::semiboldFont->height;
_statusHeight = st::normalFont->height; _statusHeight = st::normalFont->height;
@ -145,7 +155,8 @@ void ScanButton::paintEvent(QPaintEvent *e) {
_st.border, _st.border,
_st.borderFg); _st.borderFg);
if (_restore->toggled()) { const auto deleted = _restore->toggled();
if (deleted) {
p.setOpacity(st::passportScanDeletedOpacity); p.setOpacity(st::passportScanDeletedOpacity);
} }
@ -173,7 +184,9 @@ void ScanButton::paintEvent(QPaintEvent *e) {
top + _st.nameTop, top + _st.nameTop,
availableWidth, availableWidth,
width()); width());
p.setPen(st::windowSubTextFg); p.setPen((_error && !deleted)
? st::boxTextFgError
: st::windowSubTextFg);
_status.drawLeftElided( _status.drawLeftElided(
p, p,
left + _st.textLeft, left + _st.textLeft,
@ -186,26 +199,56 @@ EditScans::EditScans(
QWidget *parent, QWidget *parent,
not_null<PanelController*> controller, not_null<PanelController*> controller,
const QString &header, const QString &header,
const QString &errorMissing,
std::vector<ScanInfo> &&files, std::vector<ScanInfo> &&files,
std::unique_ptr<ScanInfo> &&selfie) std::unique_ptr<ScanInfo> &&selfie)
: RpWidget(parent) : RpWidget(parent)
, _controller(controller) , _controller(controller)
, _files(std::move(files)) , _files(std::move(files))
, _selfie(std::move(selfie)) , _selfie(std::move(selfie))
, _initialCount(_files.size())
, _errorMissing(errorMissing)
, _content(this) { , _content(this) {
setupContent(header); setupContent(header);
} }
bool EditScans::uploadedSomeMore() const {
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() { base::optional<int> EditScans::validateGetErrorTop() {
const auto exists = ranges::find( const auto exists = ranges::find_if(
_files, _files,
false, [](const ScanInfo &file) { return !file.deleted; }) != end(_files);
[](const ScanInfo &file) { return file.deleted; }) != end(_files); const auto errorExists = ranges::find_if(
if (!exists) { _files,
[](const ScanInfo &file) { return !file.error.isEmpty(); }
) != end(_files);
if (!exists
|| ((errorExists || _uploadMoreError) && !uploadedSomeMore())) {
toggleError(true); toggleError(true);
return (_files.size() > 5) ? _upload->y() : _header->y(); return (_files.size() > 5) ? _upload->y() : _header->y();
} }
if (_selfie && (!_selfie->key.id || _selfie->deleted)) {
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);
return _rows[index]->y();
}
if (_selfie
&& (!_selfie->key.id
|| _selfie->deleted
|| !_selfie->error.isEmpty())) {
toggleSelfieError(true); toggleSelfieError(true);
return _selfieHeader->y(); return _selfieHeader->y();
} }
@ -234,7 +277,18 @@ void EditScans::setupContent(const QString &header) {
st::passportFormHeader), st::passportFormHeader),
st::passportUploadHeaderPadding)); st::passportUploadHeaderPadding));
_header->toggle(!_files.empty(), anim::type::instant); _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)); _wrap = inner->add(object_ptr<Ui::VerticalLayout>(inner));
for (const auto &scan : _files) { for (const auto &scan : _files) {
pushScan(scan); pushScan(scan);
@ -317,6 +371,7 @@ void EditScans::updateScan(ScanInfo &&info) {
button->setStatus(info.status); button->setStatus(info.status);
button->setImage(info.thumb); button->setImage(info.thumb);
button->setDeleted(info.deleted); button->setDeleted(info.deleted);
button->setError(!info.error.isEmpty());
}; };
if (info.selfie) { if (info.selfie) {
Assert(info.key.id != 0); Assert(info.key.id != 0);
@ -413,7 +468,8 @@ base::unique_qptr<Ui::SlideWrap<ScanButton>> EditScans::createScan(
st::passportScanRow, st::passportScanRow,
name, name,
info.status, info.status,
info.deleted)))); info.deleted,
!info.error.isEmpty()))));
result->entity()->setImage(info.thumb); result->entity()->setImage(info.thumb);
return result; return result;
} }

View File

@ -36,6 +36,7 @@ public:
QWidget *parent, QWidget *parent,
not_null<PanelController*> controller, not_null<PanelController*> controller,
const QString &header, const QString &header,
const QString &errorMissing,
std::vector<ScanInfo> &&files, std::vector<ScanInfo> &&files,
std::unique_ptr<ScanInfo> &&selfie); std::unique_ptr<ScanInfo> &&selfie);
@ -62,6 +63,7 @@ private:
void toggleError(bool shown); void toggleError(bool shown);
void hideError(); void hideError();
void errorAnimationCallback(); void errorAnimationCallback();
bool uploadedSomeMore() const;
void toggleSelfieError(bool shown); void toggleSelfieError(bool shown);
void hideSelfieError(); void hideSelfieError();
@ -70,10 +72,13 @@ private:
not_null<PanelController*> _controller; not_null<PanelController*> _controller;
std::vector<ScanInfo> _files; std::vector<ScanInfo> _files;
std::unique_ptr<ScanInfo> _selfie; std::unique_ptr<ScanInfo> _selfie;
int _initialCount = 0;
QString _errorMissing;
object_ptr<Ui::VerticalLayout> _content; object_ptr<Ui::VerticalLayout> _content;
QPointer<Ui::SlideWrap<BoxContentDivider>> _divider; QPointer<Ui::SlideWrap<BoxContentDivider>> _divider;
QPointer<Ui::SlideWrap<Ui::FlatLabel>> _header; QPointer<Ui::SlideWrap<Ui::FlatLabel>> _header;
QPointer<Ui::SlideWrap<Ui::FlatLabel>> _uploadMoreError;
QPointer<Ui::VerticalLayout> _wrap; QPointer<Ui::VerticalLayout> _wrap;
std::vector<base::unique_qptr<Ui::SlideWrap<ScanButton>>> _rows; std::vector<base::unique_qptr<Ui::SlideWrap<ScanButton>>> _rows;
QPointer<Info::Profile::Button> _upload; QPointer<Info::Profile::Button> _upload;

View File

@ -74,8 +74,13 @@ void PanelForm::Row::updateContent(
_description.setText( _description.setText(
st::defaultTextStyle, st::defaultTextStyle,
description, description,
Ui::NameTextOptions()); TextParseOptions {
_ready = ready; TextParseMultiline,
0,
0,
Qt::LayoutDirectionAuto
});
_ready = ready && !error;
if (_error != error) { if (_error != error) {
_error = error; _error = error;
if (animated == anim::type::instant) { if (animated == anim::type::instant) {