| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719 |
- /*
- 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_document.h"
- #include "passport/passport_panel_controller.h"
- #include "passport/passport_panel_edit_scans.h"
- #include "passport/ui/passport_details_row.h"
- #include "ui/effects/scroll_content_shadow.h"
- #include "ui/widgets/fields/input_field.h"
- #include "ui/widgets/scroll_area.h"
- #include "ui/widgets/labels.h"
- #include "ui/widgets/buttons.h"
- #include "ui/widgets/checkbox.h"
- #include "ui/wrap/vertical_layout.h"
- #include "ui/wrap/fade_wrap.h"
- #include "ui/wrap/slide_wrap.h"
- #include "countries/countries_instance.h"
- #include "data/data_user.h" // ->bot()->session()
- #include "main/main_session.h" // ->session().user()
- #include "ui/text/text_utilities.h" // Ui::Text::ToUpper
- #include "boxes/abstract_box.h"
- #include "ui/boxes/confirm_box.h"
- #include "lang/lang_keys.h"
- #include "styles/style_widgets.h"
- #include "styles/style_layers.h"
- #include "styles/style_passport.h"
- namespace Passport {
- namespace {
- class RequestTypeBox : public Ui::BoxContent {
- public:
- RequestTypeBox(
- QWidget*,
- rpl::producer<QString> title,
- const QString &about,
- std::vector<QString> labels,
- Fn<void(int index)> submit);
- protected:
- void prepare() override;
- private:
- void setupControls(
- const QString &about,
- std::vector<QString> labels,
- Fn<void(int index)> submit);
- rpl::producer<QString> _title;
- Fn<void()> _submit;
- int _height = 0;
- };
- class DeleteDocumentBox : public Ui::BoxContent {
- public:
- DeleteDocumentBox(
- QWidget*,
- const QString &text,
- const QString &detailsCheckbox,
- Fn<void(bool withDetails)> submit);
- protected:
- void prepare() override;
- private:
- void setupControls(
- const QString &text,
- const QString &detailsCheckbox,
- Fn<void(bool withDetails)> submit);
- Fn<void()> _submit;
- int _height = 0;
- };
- RequestTypeBox::RequestTypeBox(
- QWidget*,
- rpl::producer<QString> title,
- const QString &about,
- std::vector<QString> labels,
- Fn<void(int index)> submit)
- : _title(std::move(title)) {
- setupControls(about, std::move(labels), submit);
- }
- void RequestTypeBox::prepare() {
- setTitle(std::move(_title));
- addButton(tr::lng_passport_upload_document(), _submit);
- addButton(tr::lng_cancel(), [=] { closeBox(); });
- setDimensions(st::boxWidth, _height);
- }
- void RequestTypeBox::setupControls(
- const QString &about,
- std::vector<QString> labels,
- Fn<void(int index)> submit) {
- const auto header = Ui::CreateChild<Ui::FlatLabel>(
- this,
- tr::lng_passport_document_type(tr::now),
- st::boxDividerLabel);
- const auto group = std::make_shared<Ui::RadiobuttonGroup>(0);
- auto buttons = std::vector<QPointer<Ui::Radiobutton>>();
- auto index = 0;
- for (const auto &label : labels) {
- buttons.emplace_back(Ui::CreateChild<Ui::Radiobutton>(
- this,
- group,
- index++,
- label,
- st::defaultBoxCheckbox));
- }
- const auto description = Ui::CreateChild<Ui::FlatLabel>(
- this,
- about,
- st::boxDividerLabel);
- auto y = 0;
- const auto innerWidth = st::boxWidth
- - st::boxPadding.left()
- - st::boxPadding.right();
- header->resizeToWidth(innerWidth);
- header->moveToLeft(st::boxPadding.left(), y);
- y += header->height() + st::passportRequestTypeSkip;
- for (const auto &button : buttons) {
- button->resizeToNaturalWidth(innerWidth);
- button->moveToLeft(st::boxPadding.left(), y);
- y += button->heightNoMargins() + st::passportRequestTypeSkip;
- }
- description->resizeToWidth(innerWidth);
- description->moveToLeft(st::boxPadding.left(), y);
- y += description->height() + st::passportRequestTypeSkip;
- _height = y;
- _submit = [=] {
- const auto value = group->hasValue() ? group->current() : -1;
- if (value >= 0) {
- submit(value);
- }
- };
- }
- DeleteDocumentBox::DeleteDocumentBox(
- QWidget*,
- const QString &text,
- const QString &detailsCheckbox,
- Fn<void(bool withDetails)> submit) {
- setupControls(text, detailsCheckbox, submit);
- }
- void DeleteDocumentBox::prepare() {
- addButton(tr::lng_box_delete(), _submit);
- addButton(tr::lng_cancel(), [=] { closeBox(); });
- setDimensions(st::boxWidth, _height);
- }
- void DeleteDocumentBox::setupControls(
- const QString &text,
- const QString &detailsCheckbox,
- Fn<void(bool withDetails)> submit) {
- const auto label = Ui::CreateChild<Ui::FlatLabel>(
- this,
- text,
- st::boxLabel);
- const auto details = !detailsCheckbox.isEmpty()
- ? Ui::CreateChild<Ui::Checkbox>(
- this,
- detailsCheckbox,
- false,
- st::defaultBoxCheckbox)
- : nullptr;
- _height = st::boxPadding.top();
- const auto availableWidth = st::boxWidth
- - st::boxPadding.left()
- - st::boxPadding.right();
- label->resizeToWidth(availableWidth);
- label->moveToLeft(st::boxPadding.left(), _height);
- _height += label->height();
- if (details) {
- _height += st::boxPadding.bottom();
- details->moveToLeft(st::boxPadding.left(), _height);
- _height += details->heightNoMargins();
- }
- _height += st::boxPadding.bottom();
- _submit = [=] {
- submit(details ? details->checked() : false);
- };
- }
- } // namespace
- struct PanelEditDocument::Result {
- ValueMap data;
- ValueMap filesData;
- };
- PanelEditDocument::PanelEditDocument(
- QWidget*,
- not_null<PanelController*> controller,
- Scheme scheme,
- const QString &error,
- const ValueMap &data,
- const QString &scansError,
- const ValueMap &scansData,
- ScanListData &&scans,
- std::optional<ScanListData> &&translations,
- std::map<FileType, ScanInfo> &&specialFiles)
- : _controller(controller)
- , _scheme(std::move(scheme))
- , _scroll(this, st::passportPanelScroll)
- , _done(
- this,
- tr::lng_passport_save_value(),
- st::passportPanelSaveValue) {
- setupControls(
- &error,
- &data,
- &scansError,
- &scansData,
- std::move(scans),
- std::move(translations),
- std::move(specialFiles));
- }
- PanelEditDocument::PanelEditDocument(
- QWidget*,
- not_null<PanelController*> controller,
- Scheme scheme,
- const QString &scansError,
- const ValueMap &scansData,
- ScanListData &&scans,
- std::optional<ScanListData> &&translations,
- std::map<FileType, ScanInfo> &&specialFiles)
- : _controller(controller)
- , _scheme(std::move(scheme))
- , _scroll(this, st::passportPanelScroll)
- , _done(
- this,
- tr::lng_passport_save_value(),
- st::passportPanelSaveValue) {
- setupControls(
- nullptr,
- nullptr,
- &scansError,
- &scansData,
- std::move(scans),
- std::move(translations),
- std::move(specialFiles));
- }
- PanelEditDocument::PanelEditDocument(
- QWidget*,
- not_null<PanelController*> controller,
- Scheme scheme,
- const QString &error,
- const ValueMap &data)
- : _controller(controller)
- , _scheme(std::move(scheme))
- , _scroll(this, st::passportPanelScroll)
- , _done(
- this,
- tr::lng_passport_save_value(),
- st::passportPanelSaveValue) {
- setupControls(&error, &data, nullptr, nullptr, {}, {}, {});
- }
- void PanelEditDocument::setupControls(
- const QString *error,
- const ValueMap *data,
- const QString *scansError,
- const ValueMap *scansData,
- ScanListData &&scans,
- std::optional<ScanListData> &&translations,
- std::map<FileType, ScanInfo> &&specialFiles) {
- const auto inner = setupContent(
- error,
- data,
- scansError,
- scansData,
- std::move(scans),
- std::move(translations),
- std::move(specialFiles));
- Ui::SetupShadowsToScrollContent(this, _scroll, inner->heightValue());
- _done->addClickHandler([=] {
- crl::on_main(this, [=] {
- save();
- });
- });
- }
- not_null<Ui::RpWidget*> PanelEditDocument::setupContent(
- const QString *error,
- const ValueMap *data,
- const QString *scansError,
- const ValueMap *scansData,
- ScanListData &&scans,
- std::optional<ScanListData> &&translations,
- std::map<FileType, ScanInfo> &&specialFiles) {
- const auto inner = _scroll->setOwnedWidget(
- object_ptr<Ui::VerticalLayout>(this));
- _scroll->widthValue(
- ) | rpl::start_with_next([=](int width) {
- inner->resizeToWidth(width);
- }, inner->lifetime());
- if (!specialFiles.empty()) {
- _editScans = inner->add(
- object_ptr<EditScans>(
- inner,
- _controller,
- _scheme.scansHeader,
- *scansError,
- std::move(specialFiles),
- std::move(translations)));
- } else if (scansData) {
- _editScans = inner->add(
- object_ptr<EditScans>(
- inner,
- _controller,
- _scheme.scansHeader,
- *scansError,
- std::move(scans),
- std::move(translations)));
- }
- const auto enumerateRows = [&](auto &&callback) {
- for (auto i = 0, count = int(_scheme.rows.size()); i != count; ++i) {
- const auto &row = _scheme.rows[i];
- Assert(row.valueClass != Scheme::ValueClass::Additional
- || !_scheme.additionalDependencyKey.isEmpty());
- auto fields = (row.valueClass == Scheme::ValueClass::Scans)
- ? scansData
- : data;
- if (!fields) {
- continue;
- }
- callback(i, row, *fields);
- }
- };
- auto maxLabelWidth = 0;
- enumerateRows([&](
- int i,
- const EditDocumentScheme::Row &row,
- const ValueMap &fields) {
- accumulate_max(
- maxLabelWidth,
- Ui::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,
- st::passportVerifyErrorLabel),
- st::passportValueErrorPadding));
- _commonError->toggle(true, anim::type::instant);
- }
- inner->add(
- object_ptr<Ui::FlatLabel>(
- inner,
- data ? _scheme.detailsHeader : _scheme.fieldsHeader,
- st::passportFormHeader),
- st::passportDetailsHeaderPadding);
- enumerateRows([&](
- int i,
- const Scheme::Row &row,
- const ValueMap &fields) {
- if (row.valueClass != Scheme::ValueClass::Additional) {
- createDetailsRow(inner, i, row, fields, maxLabelWidth);
- }
- });
- if (data && !_scheme.additionalDependencyKey.isEmpty()) {
- const auto row = findRow(_scheme.additionalDependencyKey);
- const auto wrap = inner->add(
- object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
- inner,
- object_ptr<Ui::VerticalLayout>(inner)));
- const auto added = wrap->entity();
- auto showIfError = false;
- enumerateRows([&](
- int i,
- const Scheme::Row &row,
- const ValueMap &fields) {
- if (row.valueClass != Scheme::ValueClass::Additional) {
- return;
- }
- const auto it = fields.fields.find(row.key);
- if (it == end(fields.fields)) {
- return;
- } else if (!it->second.error.isEmpty()) {
- showIfError = true;
- } else if (it->second.text.isEmpty()) {
- return;
- }
- const auto fallbackIt = fields.fields.find(
- row.additionalFallbackKey);
- if (fallbackIt != end(fields.fields)
- && fallbackIt->second.text != it->second.text) {
- showIfError = true;
- }
- });
- const auto shown = [=](const Scheme::CountryInfo &info) {
- using Result = Scheme::AdditionalVisibility;
- const auto value = _scheme.additionalShown(info);
- return (value == Result::Shown)
- || (value == Result::OnlyIfError && showIfError);
- };
- auto langValue = row->value(
- ) | rpl::map(
- _scheme.preferredLanguage
- ) | rpl::flatten_latest();
- auto title = rpl::duplicate(langValue) | rpl::filter(
- shown
- ) | rpl::map([=](const Scheme::CountryInfo &info) {
- return _scheme.additionalHeader(info);
- });
- const auto headerLabel = added->add(
- object_ptr<Ui::FlatLabel>(
- added,
- rpl::duplicate(title),
- st::passportFormHeader),
- st::passportNativeNameHeaderPadding);
- std::move(
- title
- ) | rpl::start_with_next([=] {
- const auto &padding = st::passportNativeNameHeaderPadding;
- const auto available = added->width()
- - padding.left()
- - padding.right();
- headerLabel->resizeToNaturalWidth(available);
- headerLabel->moveToLeft(
- padding.left(),
- padding.top(),
- available);
- }, headerLabel->lifetime());
- enumerateRows([&](
- int i,
- const Scheme::Row &row,
- const ValueMap &fields) {
- if (row.valueClass == Scheme::ValueClass::Additional) {
- createDetailsRow(added, i, row, fields, maxLabelWidth);
- }
- });
- auto description = rpl::duplicate(langValue) | rpl::filter(
- shown
- ) | rpl::map([=](const Scheme::CountryInfo &info) {
- return _scheme.additionalDescription(info);
- });
- added->add(
- object_ptr<Ui::DividerLabel>(
- added,
- object_ptr<Ui::FlatLabel>(
- added,
- std::move(description),
- st::boxDividerLabel),
- st::passportFormLabelPadding),
- st::passportNativeNameAboutMargin);
- wrap->toggleOn(rpl::duplicate(langValue) | rpl::map(shown));
- wrap->finishAnimating();
- std::move(langValue) | rpl::map(
- shown
- ) | rpl::start_with_next([=](bool visible) {
- _additionalShown = visible;
- }, lifetime());
- }
- inner->add(
- object_ptr<Ui::FixedHeightWidget>(inner, st::passportDetailsSkip));
- }
- if (auto text = _controller->deleteValueLabel()) {
- inner->add(
- object_ptr<Ui::SettingsButton>(
- inner,
- std::move(*text) | Ui::Text::ToUpper(),
- st::passportDeleteButton),
- st::passportUploadButtonPadding
- )->addClickHandler([=] {
- _controller->deleteValue();
- });
- }
- return inner;
- }
- void PanelEditDocument::createDetailsRow(
- not_null<Ui::VerticalLayout*> container,
- int i,
- const Scheme::Row &row,
- const ValueMap &fields,
- int maxLabelWidth) {
- const auto valueOrEmpty = [&](
- const ValueMap &values,
- const QString &key) {
- const auto &fields = values.fields;
- if (const auto i = fields.find(key); i != fields.end()) {
- return i->second;
- }
- return ValueField();
- };
- const auto current = valueOrEmpty(fields, row.key);
- const auto showBox = [controller = _controller](
- object_ptr<Ui::BoxContent> box) {
- controller->show(std::move(box));
- };
- const auto isoByPhone = Countries::Instance().countryISO2ByPhone(
- _controller->bot()->session().user()->phone());
- const auto &[it, ok] = _details.emplace(
- i,
- container->add(Ui::PanelDetailsRow::Create(
- container,
- showBox,
- isoByPhone,
- row.inputType,
- row.label,
- maxLabelWidth,
- current.text,
- current.error,
- row.lengthLimit)));
- const bool details = (row.valueClass != Scheme::ValueClass::Scans);
- 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());
- }
- not_null<Ui::PanelDetailsRow*> PanelEditDocument::findRow(
- const QString &key) const {
- for (auto i = 0, count = int(_scheme.rows.size()); i != count; ++i) {
- const auto &row = _scheme.rows[i];
- if (row.key == key) {
- const auto it = _details.find(i);
- Assert(it != end(_details));
- return it->second.data();
- }
- }
- Unexpected("Row not found in PanelEditDocument::findRow.");
- }
- 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) {
- if (row->setFocusFast()) {
- return;
- }
- }
- });
- }
- void PanelEditDocument::resizeEvent(QResizeEvent *e) {
- updateControlsGeometry();
- }
- bool PanelEditDocument::hasUnsavedChanges() const {
- const auto result = collect();
- return _controller->editScopeChanged(result.data, result.filesData);
- }
- void PanelEditDocument::updateControlsGeometry() {
- const auto submitTop = height() - _done->height();
- _scroll->setGeometry(0, 0, width(), submitTop);
- _done->resizeToWidth(width());
- _done->moveToLeft(0, submitTop);
- _scroll->updateBars();
- }
- PanelEditDocument::Result PanelEditDocument::collect() const {
- auto result = Result();
- for (const auto &[i, field] : _details) {
- const auto &row = _scheme.rows[i];
- auto &fields = (row.valueClass == Scheme::ValueClass::Scans)
- ? result.filesData
- : result.data;
- if (row.valueClass == Scheme::ValueClass::Additional
- && !_additionalShown) {
- continue;
- }
- fields.fields[row.key].text = field->valueCurrent();
- }
- if (!_additionalShown) {
- fillAdditionalFromFallbacks(result);
- }
- return result;
- }
- void PanelEditDocument::fillAdditionalFromFallbacks(Result &result) const {
- for (const auto &row : _scheme.rows) {
- if (row.valueClass != Scheme::ValueClass::Additional) {
- continue;
- }
- Assert(!row.additionalFallbackKey.isEmpty());
- auto &fields = result.data;
- const auto j = fields.fields.find(row.additionalFallbackKey);
- Assert(j != end(fields.fields));
- fields.fields[row.key] = j->second;
- }
- }
- bool PanelEditDocument::validate() {
- auto error = _editScans
- ? _editScans->validateGetErrorTop()
- : std::nullopt;
- if (error) {
- const auto errortop = _editScans->mapToGlobal(QPoint(0, *error));
- 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<Ui::PanelDetailsRow>();
- for (const auto &[i, field] : ranges::views::reverse(_details)) {
- const auto &row = _scheme.rows[i];
- if (row.valueClass == Scheme::ValueClass::Additional
- && !_additionalShown) {
- continue;
- }
- if (field->errorShown()) {
- field->showError();
- first = field;
- } else if (row.error) {
- if (const auto error = row.error(field->valueCurrent())) {
- field->showError(error);
- first = field;
- }
- }
- }
- if (error) {
- return false;
- } else if (!first) {
- return true;
- }
- const auto firsttop = first->mapToGlobal(QPoint(0, 0));
- const auto scrolltop = _scroll->mapToGlobal(QPoint(0, 0));
- const auto scrolldelta = firsttop.y() - scrolltop.y();
- _scroll->scrollToY(_scroll->scrollTop() + scrolldelta);
- return false;
- }
- void PanelEditDocument::save() {
- if (!validate()) {
- return;
- }
- auto result = collect();
- _controller->saveScope(
- std::move(result.data),
- std::move(result.filesData));
- }
- object_ptr<Ui::BoxContent> RequestIdentityType(
- Fn<void(int index)> submit,
- std::vector<QString> labels) {
- return Box<RequestTypeBox>(
- tr::lng_passport_identity_title(),
- tr::lng_passport_identity_about(tr::now),
- std::move(labels),
- submit);
- }
- object_ptr<Ui::BoxContent> RequestAddressType(
- Fn<void(int index)> submit,
- std::vector<QString> labels) {
- return Box<RequestTypeBox>(
- tr::lng_passport_address_title(),
- tr::lng_passport_address_about(tr::now),
- std::move(labels),
- submit);
- }
- object_ptr<Ui::BoxContent> ConfirmDeleteDocument(
- Fn<void(bool withDetails)> submit,
- const QString &text,
- const QString &detailsCheckbox) {
- return Box<DeleteDocumentBox>(text, detailsCheckbox, submit);
- }
- } // namespace Passport
|