tdesktop/Telegram/SourceFiles/passport/passport_panel_edit_scans.cpp

986 lines
25 KiB
C++

/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "passport/passport_panel_edit_scans.h"
#include "passport/passport_panel_controller.h"
#include "passport/passport_panel_details_row.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/box_content_divider.h"
#include "ui/wrap/fade_wrap.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/text/text_utilities.h" // Ui::Text::ToUpper
#include "ui/text_options.h"
#include "core/file_utilities.h"
#include "lang/lang_keys.h"
#include "boxes/abstract_box.h"
#include "storage/storage_media_prepare.h"
#include "storage/file_upload.h" // For Storage::kUseBigFilesFrom.
#include "app.h"
#include "styles/style_layers.h"
#include "styles/style_passport.h"
#include <QtCore/QBuffer>
namespace Passport {
namespace {
constexpr auto kMaxDimensions = 2048;
constexpr auto kMaxSize = 10 * 1024 * 1024;
constexpr auto kJpegQuality = 89;
static_assert(kMaxSize <= Storage::kUseBigFilesFrom);
std::variant<ReadScanError, QByteArray> ProcessImage(QByteArray &&bytes) {
auto image = App::readImage(base::take(bytes));
if (image.isNull()) {
return ReadScanError::CantReadImage;
} else if (!Storage::ValidateThumbDimensions(image.width(), image.height())) {
return ReadScanError::BadImageSize;
}
if (std::max(image.width(), image.height()) > kMaxDimensions) {
image = std::move(image).scaled(
kMaxDimensions,
kMaxDimensions,
Qt::KeepAspectRatio,
Qt::SmoothTransformation);
}
auto result = QByteArray();
{
QBuffer buffer(&result);
if (!image.save(&buffer, QByteArray("JPG"), kJpegQuality)) {
return ReadScanError::Unknown;
}
base::take(image);
}
if (result.isEmpty()) {
return ReadScanError::Unknown;
} else if (result.size() > kMaxSize) {
return ReadScanError::FileTooLarge;
}
return result;
}
} // namespace
class ScanButton : public Ui::AbstractButton {
public:
ScanButton(
QWidget *parent,
const style::PassportScanRow &st,
const QString &name,
const QString &status,
bool deleted,
bool error);
void setImage(const QImage &image);
void setStatus(const QString &status);
void setDeleted(bool deleted);
void setError(bool error);
rpl::producer<> deleteClicks() const {
return _delete->entity()->clicks() | rpl::to_empty;
}
rpl::producer<> restoreClicks() const {
return _restore->entity()->clicks() | rpl::to_empty;
}
protected:
int resizeGetHeight(int newWidth) override;
void paintEvent(QPaintEvent *e) override;
private:
int countAvailableWidth() const;
const style::PassportScanRow &_st;
Ui::Text::String _name;
Ui::Text::String _status;
int _nameHeight = 0;
int _statusHeight = 0;
bool _error = false;
QImage _image;
object_ptr<Ui::FadeWrapScaled<Ui::IconButton>> _delete;
object_ptr<Ui::FadeWrapScaled<Ui::RoundButton>> _restore;
};
struct EditScans::SpecialScan {
SpecialScan(ScanInfo &&file);
ScanInfo file;
QPointer<Ui::SlideWrap<Ui::FlatLabel>> header;
QPointer<Ui::VerticalLayout> wrap;
base::unique_qptr<Ui::SlideWrap<ScanButton>> row;
QPointer<Ui::SettingsButton> upload;
bool errorShown = false;
Ui::Animations::Simple errorAnimation;
rpl::variable<bool> rowCreated;
};
void UpdateFileRow(
not_null<ScanButton*> button,
const ScanInfo &info) {
button->setStatus(info.status);
button->setImage(info.thumb);
button->setDeleted(info.deleted);
button->setError(!info.error.isEmpty());
}
base::unique_qptr<Ui::SlideWrap<ScanButton>> CreateScan(
not_null<Ui::VerticalLayout*> parent,
const ScanInfo &info,
const QString &name) {
auto result = base::unique_qptr<Ui::SlideWrap<ScanButton>>(
parent->add(object_ptr<Ui::SlideWrap<ScanButton>>(
parent,
object_ptr<ScanButton>(
parent,
st::passportScanRow,
name,
info.status,
info.deleted,
!info.error.isEmpty()))));
result->entity()->setImage(info.thumb);
return result;
}
EditScans::List::List(
not_null<PanelController*> controller,
ScanListData &&data)
: controller(controller)
, files(std::move(data.files))
, initialCount(int(files.size()))
, errorMissing(data.errorMissing) {
}
EditScans::List::List(
not_null<PanelController*> controller,
std::optional<ScanListData> &&data)
: controller(controller)
, files(data ? std::move(data->files) : std::vector<ScanInfo>())
, initialCount(data ? base::make_optional(int(files.size())) : std::nullopt)
, errorMissing(data ? std::move(data->errorMissing) : QString()) {
}
bool EditScans::List::uploadedSomeMore() const {
if (!initialCount) {
return false;
}
const auto from = begin(files) + *initialCount;
const auto till = end(files);
return std::find_if(from, till, [](const ScanInfo &file) {
return !file.deleted;
}) != till;
}
bool EditScans::List::uploadMoreRequired() const {
if (!upload) {
return false;
}
const auto exists = ranges::any_of(
files,
[](const ScanInfo &file) { return !file.deleted; });
if (!exists) {
return true;
}
const auto errorExists = ranges::any_of(
files,
[](const ScanInfo &file) { return !file.error.isEmpty(); });
return (errorExists || uploadMoreError) && !uploadedSomeMore();
}
Ui::SlideWrap<ScanButton> *EditScans::List::nonDeletedErrorRow() const {
const auto nonDeletedErrorIt = ranges::find_if(
files,
[](const ScanInfo &file) {
return !file.error.isEmpty() && !file.deleted;
});
if (nonDeletedErrorIt == end(files)) {
return nullptr;
}
const auto index = (nonDeletedErrorIt - begin(files));
return rows[index].get();
}
rpl::producer<QString> EditScans::List::uploadButtonText() const {
return (files.empty()
? tr::lng_passport_upload_scans
: tr::lng_passport_upload_more)() | Ui::Text::ToUpper();
}
void EditScans::List::hideError() {
toggleError(false);
}
void EditScans::List::toggleError(bool shown) {
if (errorShown != shown) {
errorShown = shown;
errorAnimation.start(
[=] { errorAnimationCallback(); },
errorShown ? 0. : 1.,
errorShown ? 1. : 0.,
st::passportDetailsField.duration);
}
}
void EditScans::List::errorAnimationCallback() {
const auto error = errorAnimation.value(errorShown ? 1. : 0.);
if (error == 0.) {
upload->setColorOverride(std::nullopt);
} else {
upload->setColorOverride(anim::color(
st::passportUploadButton.textFg,
st::boxTextFgError,
error));
}
}
void EditScans::List::updateScan(ScanInfo &&info, int width) {
const auto i = ranges::find(files, info.key, [](const ScanInfo &file) {
return file.key;
});
if (i != files.end()) {
*i = std::move(info);
const auto scan = rows[i - files.begin()]->entity();
UpdateFileRow(scan, *i);
if (!i->deleted) {
hideError();
}
} else {
files.push_back(std::move(info));
pushScan(files.back());
wrap->resizeToWidth(width);
rows.back()->show(anim::type::normal);
if (divider) {
divider->hide(anim::type::normal);
}
header->show(anim::type::normal);
uploadTexts.fire(uploadButtonText());
}
}
void EditScans::List::pushScan(const ScanInfo &info) {
const auto index = rows.size();
const auto type = info.type;
rows.push_back(CreateScan(
wrap,
info,
tr::lng_passport_scan_index(tr::now, lt_index, QString::number(index + 1))));
rows.back()->hide(anim::type::instant);
const auto scan = rows.back()->entity();
scan->deleteClicks(
) | rpl::start_with_next([=] {
controller->deleteScan(type, index);
}, scan->lifetime());
scan->restoreClicks(
) | rpl::start_with_next([=] {
controller->restoreScan(type, index);
}, scan->lifetime());
hideError();
}
ScanButton::ScanButton(
QWidget *parent,
const style::PassportScanRow &st,
const QString &name,
const QString &status,
bool deleted,
bool error)
: AbstractButton(parent)
, _st(st)
, _name(
st::passportScanNameStyle,
name,
Ui::NameTextOptions())
, _status(
st::defaultTextStyle,
status,
Ui::NameTextOptions())
, _error(error)
, _delete(this, object_ptr<Ui::IconButton>(this, _st.remove))
, _restore(
this,
object_ptr<Ui::RoundButton>(
this,
tr::lng_passport_delete_scan_undo(),
_st.restore)) {
_delete->toggle(!deleted, anim::type::instant);
_restore->toggle(deleted, anim::type::instant);
}
void ScanButton::setImage(const QImage &image) {
_image = image;
update();
}
void ScanButton::setStatus(const QString &status) {
_status.setText(
st::defaultTextStyle,
status,
Ui::NameTextOptions());
update();
}
void ScanButton::setDeleted(bool deleted) {
_delete->toggle(!deleted, anim::type::instant);
_restore->toggle(deleted, anim::type::instant);
update();
}
void ScanButton::setError(bool error) {
_error = error;
update();
}
int ScanButton::resizeGetHeight(int newWidth) {
_nameHeight = st::semiboldFont->height;
_statusHeight = st::normalFont->height;
const auto result = _st.padding.top() + _st.size + _st.padding.bottom();
const auto right = _st.padding.right();
_delete->moveToRight(
right,
(result - _delete->height()) / 2,
newWidth);
_restore->moveToRight(
right,
(result - _restore->height()) / 2,
newWidth);
return result + st::lineWidth;
}
int ScanButton::countAvailableWidth() const {
return width()
- _st.padding.left()
- _st.textLeft
- _st.padding.right()
- std::max(_delete->width(), _restore->width());
}
void ScanButton::paintEvent(QPaintEvent *e) {
Painter p(this);
const auto left = _st.padding.left();
const auto top = _st.padding.top();
p.fillRect(
left,
height() - _st.border,
width() - left,
_st.border,
_st.borderFg);
const auto deleted = _restore->toggled();
if (deleted) {
p.setOpacity(st::passportScanDeletedOpacity);
}
if (_image.isNull()) {
p.fillRect(left, top, _st.size, _st.size, Qt::black);
} else {
PainterHighQualityEnabler hq(p);
const auto fromRect = [&] {
if (_image.width() > _image.height()) {
const auto shift = (_image.width() - _image.height()) / 2;
return QRect(shift, 0, _image.height(), _image.height());
} else {
const auto shift = (_image.height() - _image.width()) / 2;
return QRect(0, shift, _image.width(), _image.width());
}
}();
p.drawImage(QRect(left, top, _st.size, _st.size), _image, fromRect);
}
const auto availableWidth = countAvailableWidth();
p.setPen(st::windowFg);
_name.drawLeftElided(
p,
left + _st.textLeft,
top + _st.nameTop,
availableWidth,
width());
p.setPen((_error && !deleted)
? st::boxTextFgError
: st::windowSubTextFg);
_status.drawLeftElided(
p,
left + _st.textLeft,
top + _st.statusTop,
availableWidth,
width());
}
EditScans::SpecialScan::SpecialScan(ScanInfo &&file)
: file(std::move(file)) {
}
EditScans::EditScans(
QWidget *parent,
not_null<PanelController*> controller,
const QString &header,
const QString &error,
ScanListData &&scans,
std::optional<ScanListData> &&translations)
: RpWidget(parent)
, _controller(controller)
, _error(error)
, _content(this)
, _scansList(_controller, std::move(scans))
, _translationsList(_controller, std::move(translations)) {
setupScans(header);
}
EditScans::EditScans(
QWidget *parent,
not_null<PanelController*> controller,
const QString &header,
const QString &error,
std::map<FileType, ScanInfo> &&specialFiles,
std::optional<ScanListData> &&translations)
: RpWidget(parent)
, _controller(controller)
, _error(error)
, _content(this)
, _scansList(_controller)
, _translationsList(_controller, std::move(translations)) {
setupSpecialScans(header, std::move(specialFiles));
}
std::optional<int> EditScans::validateGetErrorTop() {
auto result = std::optional<int>();
const auto suggestResult = [&](int value) {
if (!result || *result > value) {
result = value;
}
};
if (_commonError && !somethingChanged()) {
suggestResult(_commonError->y());
}
const auto suggestList = [&](FileType type) {
auto &list = this->list(type);
if (list.uploadMoreRequired()) {
list.toggleError(true);
suggestResult((list.files.size() > 5)
? list.upload->y()
: list.header->y());
}
if (const auto row = list.nonDeletedErrorRow()) {
//toggleError(true);
suggestResult(row->y());
}
};
suggestList(FileType::Scan);
for (const auto &[type, scan] : _specialScans) {
if (!scan.file.key.id
|| scan.file.deleted
|| !scan.file.error.isEmpty()) {
toggleSpecialScanError(type, true);
suggestResult(scan.header ? scan.header->y() : scan.wrap->y());
}
}
suggestList(FileType::Translation);
return result;
}
EditScans::List &EditScans::list(FileType type) {
switch (type) {
case FileType::Scan: return _scansList;
case FileType::Translation: return _translationsList;
}
Unexpected("Type in EditScans::list().");
}
const EditScans::List &EditScans::list(FileType type) const {
switch (type) {
case FileType::Scan: return _scansList;
case FileType::Translation: return _translationsList;
}
Unexpected("Type in EditScans::list() const.");
}
void EditScans::setupScans(const QString &header) {
const auto inner = _content.data();
inner->move(0, 0);
if (!_error.isEmpty()) {
_commonError = inner->add(
object_ptr<Ui::SlideWrap<Ui::FlatLabel>>(
inner,
object_ptr<Ui::FlatLabel>(
inner,
_error,
st::passportVerifyErrorLabel),
st::passportValueErrorPadding));
_commonError->toggle(true, anim::type::instant);
}
setupList(inner, FileType::Scan, header);
setupList(inner, FileType::Translation, tr::lng_passport_translation(tr::now));
init();
}
void EditScans::setupList(
not_null<Ui::VerticalLayout*> container,
FileType type,
const QString &header) {
auto &list = this->list(type);
if (!list.initialCount) {
return;
}
if (type == FileType::Scan) {
list.divider = container->add(
object_ptr<Ui::SlideWrap<Ui::BoxContentDivider>>(
container,
object_ptr<Ui::BoxContentDivider>(
container,
st::passportFormDividerHeight)));
list.divider->toggle(list.files.empty(), anim::type::instant);
}
list.header = container->add(
object_ptr<Ui::SlideWrap<Ui::FlatLabel>>(
container,
object_ptr<Ui::FlatLabel>(
container,
header,
st::passportFormHeader),
st::passportUploadHeaderPadding));
list.header->toggle(
!list.divider || !list.files.empty(),
anim::type::instant);
if (!list.errorMissing.isEmpty()) {
list.uploadMoreError = container->add(
object_ptr<Ui::SlideWrap<Ui::FlatLabel>>(
container,
object_ptr<Ui::FlatLabel>(
container,
list.errorMissing,
st::passportVerifyErrorLabel),
st::passportUploadErrorPadding));
list.uploadMoreError->toggle(true, anim::type::instant);
}
list.wrap = container->add(object_ptr<Ui::VerticalLayout>(container));
for (const auto &scan : list.files) {
list.pushScan(scan);
list.rows.back()->show(anim::type::instant);
}
list.upload = container->add(
object_ptr<Ui::SettingsButton>(
container,
list.uploadTexts.events_starting_with(
list.uploadButtonText()
) | rpl::flatten_latest(),
st::passportUploadButton),
st::passportUploadButtonPadding);
list.upload->addClickHandler([=] {
chooseScan(type);
});
container->add(object_ptr<Ui::BoxContentDivider>(
container,
st::passportFormDividerHeight));
}
void EditScans::setupSpecialScans(
const QString &header,
std::map<FileType, ScanInfo> &&files) {
const auto requiresBothSides = files.find(FileType::ReverseSide)
!= end(files);
const auto title = [&](FileType type) {
switch (type) {
case FileType::FrontSide:
return requiresBothSides
? tr::lng_passport_front_side_title(tr::now)
: tr::lng_passport_main_page_title(tr::now);
case FileType::ReverseSide:
return tr::lng_passport_reverse_side_title(tr::now);
case FileType::Selfie:
return tr::lng_passport_selfie_title(tr::now);
}
Unexpected("Type in special row title.");
};
const auto uploadText = [=](FileType type, bool hasScan) {
switch (type) {
case FileType::FrontSide:
return requiresBothSides
? (hasScan
? tr::lng_passport_reupload_front_side
: tr::lng_passport_upload_front_side)
: (hasScan
? tr::lng_passport_reupload_main_page
: tr::lng_passport_upload_main_page);
case FileType::ReverseSide:
return hasScan
? tr::lng_passport_reupload_reverse_side
: tr::lng_passport_upload_reverse_side;
case FileType::Selfie:
return hasScan
? tr::lng_passport_reupload_selfie
: tr::lng_passport_upload_selfie;
}
Unexpected("Type in special row upload key.");
};
const auto description = [&](FileType type) {
switch (type) {
case FileType::FrontSide:
return requiresBothSides
? tr::lng_passport_front_side_description
: tr::lng_passport_main_page_description;
case FileType::ReverseSide:
return tr::lng_passport_reverse_side_description;
case FileType::Selfie:
return tr::lng_passport_selfie_description;
}
Unexpected("Type in special row upload key.");
};
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,
st::passportVerifyErrorLabel),
st::passportValueErrorPadding));
_commonError->toggle(true, anim::type::instant);
}
for (auto &[type, info] : files) {
const auto i = _specialScans.emplace(
type,
SpecialScan(std::move(info))).first;
auto &scan = i->second;
if (_specialScans.size() == 1) {
scan.header = inner->add(
object_ptr<Ui::SlideWrap<Ui::FlatLabel>>(
inner,
object_ptr<Ui::FlatLabel>(
inner,
header,
st::passportFormHeader),
st::passportUploadHeaderPadding));
scan.header->toggle(scan.file.key.id != 0, anim::type::instant);
}
scan.wrap = inner->add(object_ptr<Ui::VerticalLayout>(inner));
if (scan.file.key.id) {
createSpecialScanRow(scan, scan.file, requiresBothSides);
}
auto label = scan.rowCreated.value(
) | rpl::map([=, type = type](bool created) {
return uploadText(type, created)();
}) | rpl::flatten_latest(
) | Ui::Text::ToUpper();
scan.upload = inner->add(
object_ptr<Ui::SettingsButton>(
inner,
std::move(label),
st::passportUploadButton),
st::passportUploadButtonPadding);
scan.upload->addClickHandler([=, type = type] {
chooseScan(type);
});
inner->add(object_ptr<Ui::DividerLabel>(
inner,
object_ptr<Ui::FlatLabel>(
inner,
description(type)(tr::now),
st::boxDividerLabel),
st::passportFormLabelPadding));
}
setupList(inner, FileType::Translation, tr::lng_passport_translation(tr::now));
init();
}
void EditScans::init() {
_controller->scanUpdated(
) | rpl::start_with_next([=](ScanInfo &&info) {
updateScan(std::move(info));
}, lifetime());
widthValue(
) | rpl::start_with_next([=](int width) {
_content->resizeToWidth(width);
}, _content->lifetime());
_content->heightValue(
) | rpl::start_with_next([=](int height) {
resize(width(), height);
}, _content->lifetime());
}
void EditScans::updateScan(ScanInfo &&info) {
if (info.type != FileType::Scan && info.type != FileType::Translation) {
updateSpecialScan(std::move(info));
return;
}
list(info.type).updateScan(std::move(info), width());
updateErrorLabels();
}
void EditScans::scanFieldsChanged(bool changed) {
if (_scanFieldsChanged != changed) {
_scanFieldsChanged = changed;
updateErrorLabels();
}
}
void EditScans::updateErrorLabels() {
const auto updateList = [&](FileType type) {
auto &list = this->list(type);
if (list.uploadMoreError) {
list.uploadMoreError->toggle(
!list.uploadedSomeMore(),
anim::type::normal);
}
};
updateList(FileType::Scan);
updateList(FileType::Translation);
if (_commonError) {
_commonError->toggle(!somethingChanged(), anim::type::normal);
}
}
bool EditScans::somethingChanged() const {
return list(FileType::Scan).uploadedSomeMore()
|| list(FileType::Translation).uploadedSomeMore()
|| _scanFieldsChanged
|| _specialScanChanged;
}
void EditScans::updateSpecialScan(ScanInfo &&info) {
Expects(info.key.id != 0);
const auto type = info.type;
const auto i = _specialScans.find(type);
if (i == end(_specialScans)) {
return;
}
auto &scan = i->second;
if (scan.file.key.id) {
UpdateFileRow(scan.row->entity(), info);
scan.rowCreated = !info.deleted;
if (scan.file.key.id != info.key.id) {
specialScanChanged(type, true);
}
} else {
const auto requiresBothSides
= (_specialScans.find(FileType::ReverseSide)
!= end(_specialScans));
createSpecialScanRow(scan, info, requiresBothSides);
scan.wrap->resizeToWidth(width());
scan.row->show(anim::type::normal);
if (scan.header) {
scan.header->show(anim::type::normal);
}
specialScanChanged(type, true);
}
scan.file = std::move(info);
}
void EditScans::createSpecialScanRow(
SpecialScan &scan,
const ScanInfo &info,
bool requiresBothSides) {
Expects(scan.file.type != FileType::Scan
&& scan.file.type != FileType::Translation);
const auto type = scan.file.type;
const auto name = [&] {
switch (type) {
case FileType::FrontSide:
return requiresBothSides
? tr::lng_passport_front_side_title(tr::now)
: tr::lng_passport_main_page_title(tr::now);
case FileType::ReverseSide:
return tr::lng_passport_reverse_side_title(tr::now);
case FileType::Selfie:
return tr::lng_passport_selfie_title(tr::now);
}
Unexpected("Type in special file name.");
}();
scan.row = CreateScan(scan.wrap, info, name);
const auto row = scan.row->entity();
row->deleteClicks(
) | rpl::start_with_next([=] {
_controller->deleteScan(type, std::nullopt);
}, row->lifetime());
row->restoreClicks(
) | rpl::start_with_next([=] {
_controller->restoreScan(type, std::nullopt);
}, row->lifetime());
scan.rowCreated = !info.deleted;
}
void EditScans::chooseScan(FileType type) {
if (!_controller->canAddScan(type)) {
_controller->showToast(tr::lng_passport_scans_limit_reached(tr::now));
return;
}
ChooseScan(this, type, [=](QByteArray &&content) {
_controller->uploadScan(type, std::move(content));
}, [=](ReadScanError error) {
_controller->readScanError(error);
});
}
void EditScans::ChooseScan(
QPointer<QWidget> parent,
FileType type,
Fn<void(QByteArray&&)> doneCallback,
Fn<void(ReadScanError)> errorCallback) {
Expects(parent != nullptr);
const auto filter = FileDialog::AllFilesFilter()
+ qsl(";;Image files (*")
+ cImgExtensions().join(qsl(" *"))
+ qsl(")");
const auto guardedCallback = crl::guard(parent, doneCallback);
const auto guardedError = crl::guard(parent, errorCallback);
const auto onMainError = [=](ReadScanError error) {
crl::on_main([=] {
guardedError(error);
});
};
const auto processFiles = [=](
QStringList &&files,
const auto &handleImage) -> void {
while (!files.isEmpty()) {
auto file = files.front();
files.removeAt(0);
auto content = [&] {
QFile f(file);
if (f.size() > App::kImageSizeLimit) {
guardedError(ReadScanError::FileTooLarge);
return QByteArray();
} else if (!f.open(QIODevice::ReadOnly)) {
guardedError(ReadScanError::CantReadImage);
return QByteArray();
}
return f.readAll();
}();
if (!content.isEmpty()) {
handleImage(
std::move(content),
std::move(files),
handleImage);
return;
}
}
};
const auto processImage = [=](
QByteArray &&content,
QStringList &&remainingFiles,
const auto &repeatProcessImage) -> void {
crl::async([
=,
bytes = std::move(content),
remainingFiles = std::move(remainingFiles)
]() mutable {
auto result = ProcessImage(std::move(bytes));
if (const auto error = std::get_if<ReadScanError>(&result)) {
onMainError(*error);
} else {
auto content = std::get_if<QByteArray>(&result);
Assert(content != nullptr);
crl::on_main([
=,
bytes = std::move(*content),
remainingFiles = std::move(remainingFiles)
]() mutable {
guardedCallback(std::move(bytes));
processFiles(
std::move(remainingFiles),
repeatProcessImage);
});
}
});
};
const auto processOpened = [=](FileDialog::OpenResult &&result) {
if (result.paths.size() > 0) {
processFiles(std::move(result.paths), processImage);
} else if (!result.remoteContent.isEmpty()) {
processImage(std::move(result.remoteContent), {}, processImage);
}
};
const auto allowMany = (type == FileType::Scan)
|| (type == FileType::Translation);
(allowMany ? FileDialog::GetOpenPaths : FileDialog::GetOpenPath)(
parent,
tr::lng_passport_choose_image(tr::now),
filter,
processOpened,
nullptr);
}
void EditScans::hideSpecialScanError(FileType type) {
toggleSpecialScanError(type, false);
}
void EditScans::specialScanChanged(FileType type, bool changed) {
hideSpecialScanError(type);
if (_specialScanChanged != changed) {
_specialScanChanged = changed;
updateErrorLabels();
}
}
auto EditScans::findSpecialScan(FileType type) -> SpecialScan& {
const auto i = _specialScans.find(type);
Assert(i != end(_specialScans));
return i->second;
}
void EditScans::toggleSpecialScanError(FileType type, bool shown) {
auto &scan = findSpecialScan(type);
if (scan.errorShown != shown) {
scan.errorShown = shown;
scan.errorAnimation.start(
[=] { specialScanErrorAnimationCallback(type); },
scan.errorShown ? 0. : 1.,
scan.errorShown ? 1. : 0.,
st::passportDetailsField.duration);
}
}
void EditScans::specialScanErrorAnimationCallback(FileType type) {
auto &scan = findSpecialScan(type);
const auto error = scan.errorAnimation.value(
scan.errorShown ? 1. : 0.);
if (error == 0.) {
scan.upload->setColorOverride(std::nullopt);
} else {
scan.upload->setColorOverride(anim::color(
st::passportUploadButton.textFg,
st::boxTextFgError,
error));
}
}
EditScans::~EditScans() = default;
} // namespace Passport