diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 27e9dda9b..b512ce2dc 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1512,10 +1512,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_passport_identity_title" = "Identity document"; "lng_passport_identity_description" = "Upload a scan of your passport or other ID"; "lng_passport_identity_passport" = "Passport"; +"lng_passport_identity_passport_upload" = "Upload a scan of your passport"; "lng_passport_identity_card" = "Identity card"; +"lng_passport_identity_card_upload" = "Upload a scan of your identity card"; "lng_passport_identity_license" = "Driver's license"; +"lng_passport_identity_license_upload" = "Upload a scan of your driver's license"; +"lng_passport_identity_about" = "Your document must contain your photograph, name and surname, date of birth, citizenship, document issue date and document number."; "lng_passport_address_title" = "Residential address"; "lng_passport_address_description" = "Upload a proof of your address"; +"lng_passport_address_bill" = "Utility bill"; +"lng_passport_address_bill_upload" = "Upload a scan of your utility bill"; +"lng_passport_address_statement" = "Bank statement"; +"lng_passport_address_statement_upload" = "Upload a scan of your bank statement"; +"lng_passport_address_agreement" = "Tenancy agreement"; +"lng_passport_address_agreement_upload" = "Upload a scan of your tenancy agreement"; +"lng_passport_address_about" = "To confirm your address please upload a scan or photo of the selected document (all pages)."; +"lng_passport_document_type" = "Please choose the type of your document:"; +"lng_passport_upload_document" = "Upload document"; "lng_passport_phone_title" = "Phone number"; "lng_passport_phone_description" = "Enter your phone number"; "lng_passport_email_title" = "Email"; @@ -1528,7 +1541,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_passport_save_value" = "Save"; "lng_passport_saving" = "Saving..."; "lng_passport_uploading" = "Uploading..."; -"lng_passport_upload_header" = "Scans"; "lng_passport_scan_index" = "Scan {index}"; "lng_passport_upload_scans" = "Upload scans"; "lng_passport_upload_more" = "Upload additional scans"; diff --git a/Telegram/SourceFiles/passport/passport.style b/Telegram/SourceFiles/passport/passport.style index 5f1cf07d1..30163f2b8 100644 --- a/Telegram/SourceFiles/passport/passport.style +++ b/Telegram/SourceFiles/passport/passport.style @@ -119,7 +119,7 @@ passportFormUserpic: UserpicButton(passportPasswordUserpic) { passportFormUserpicPadding: margins(0px, 5px, 0px, 10px); passportFormDividerHeight: 13px; passportFormLabel: FlatLabel(defaultFlatLabel) { - minWidth: 285px; + minWidth: 245px; align: align(topleft); textFg: windowSubTextFg; } @@ -194,3 +194,5 @@ passportContactField: InputField(defaultInputField) { passportDetailsFieldLeft: 116px; passportDetailsFieldTop: 2px; passportDetailsFieldSkipMin: 12px; + +passportRequestTypeSkip: 16px; diff --git a/Telegram/SourceFiles/passport/passport_panel_controller.cpp b/Telegram/SourceFiles/passport/passport_panel_controller.cpp index 1d06c106f..c0647b523 100644 --- a/Telegram/SourceFiles/passport/passport_panel_controller.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_controller.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "passport/passport_panel_edit_document.h" #include "passport/passport_panel_edit_contact.h" +#include "passport/passport_panel_edit_scans.h" #include "passport/passport_panel.h" #include "boxes/confirm_box.h" #include "layout.h" @@ -17,7 +18,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Passport { namespace { -PanelEditDocument::Scheme GetDocumentScheme(Scope::Type type) { +PanelEditDocument::Scheme GetDocumentScheme( + Scope::Type type, + base::optional scansType = base::none) { using Scheme = PanelEditDocument::Scheme; const auto DontValidate = nullptr; @@ -43,6 +46,21 @@ PanelEditDocument::Scheme GetDocumentScheme(Scope::Type type) { case Scope::Type::Identity: { auto result = Scheme(); result.rowsHeader = lang(lng_passport_personal_details); + if (scansType) { + switch (*scansType) { + case Value::Type::Passport: + result.scansHeader = lang(lng_passport_identity_passport); + break; + case Value::Type::DriverLicense: + result.scansHeader = lang(lng_passport_identity_license); + break; + case Value::Type::IdentityCard: + result.scansHeader = lang(lng_passport_identity_card); + break; + default: + Unexpected("scansType in GetDocumentScheme:Identity."); + } + } result.rows = { { Scheme::ValueType::Fields, @@ -93,6 +111,21 @@ PanelEditDocument::Scheme GetDocumentScheme(Scope::Type type) { case Scope::Type::Address: { auto result = Scheme(); result.rowsHeader = lang(lng_passport_address); + if (scansType) { + switch (*scansType) { + case Value::Type::UtilityBill: + result.scansHeader = lang(lng_passport_address_bill); + break; + case Value::Type::BankStatement: + result.scansHeader = lang(lng_passport_address_statement); + break; + case Value::Type::RentalAgreement: + result.scansHeader = lang(lng_passport_address_agreement); + break; + default: + Unexpected("scansType in GetDocumentScheme:Identity."); + } + } result.rows = { { Scheme::ValueType::Fields, @@ -413,14 +446,121 @@ void PanelController::ensurePanelCreated() { } } +int PanelController::findNonEmptyIndex( + const std::vector> &files) const { + const auto i = ranges::find_if(files, [](not_null file) { + return !file->files.empty(); + }); + if (i != end(files)) { + return (i - begin(files)); + } + // Only an uploaded scan counts as non-empty value. + //const auto j = ranges::find_if(files, [](not_null file) { + // return !file->data.parsed.fields.empty(); + //}); + //if (j != end(files)) { + // return (j - begin(files)); + //} + return -1; +} + + void PanelController::editScope(int index) { Expects(_panel != nullptr); Expects(index >= 0 && index < _scopes.size()); - _editScope = &_scopes[index]; + if (_scopes[index].files.size() > 1) { + const auto filesIndex = findNonEmptyIndex(_scopes[index].files); + if (filesIndex >= 0) { + editScope(index, filesIndex); + } else { + requestScopeFilesType(index); + } + } else if (_scopes[index].files.empty()) { + editScope(index, -1); + } else { + editWithUpload(index, 0); + } +} - // #TODO select type for files index - _editScopeFilesIndex = _scopes[index].files.empty() ? -1 : 0; +void PanelController::requestScopeFilesType(int index) { + Expects(_panel != nullptr); + Expects(index >= 0 && index < _scopes.size()); + + const auto type = _scopes[index].type; + const auto box = std::make_shared>(); + *box = [&] { + if (type == Scope::Type::Identity) { + return show(RequestIdentityType( + [=](int filesIndex) { + editWithUpload(index, filesIndex); + (*box)->closeBox(); + }, + ranges::view::all( + _scopes[index].files + ) | ranges::view::transform([](auto value) { + return value->type; + }) | ranges::view::transform([](Value::Type type) { + switch (type) { + case Value::Type::Passport: + return lang(lng_passport_identity_passport); + case Value::Type::IdentityCard: + return lang(lng_passport_identity_card); + case Value::Type::DriverLicense: + return lang(lng_passport_identity_license); + default: + Unexpected("IdentityType in requestScopeFilesType"); + } + }) | ranges::to_vector)); + } else if (type == Scope::Type::Address) { + return show(RequestAddressType( + [=](int filesIndex) { + editWithUpload(index, filesIndex); + (*box)->closeBox(); + }, + ranges::view::all( + _scopes[index].files + ) | ranges::view::transform([](auto value) { + return value->type; + }) | ranges::view::transform([](Value::Type type) { + switch (type) { + case Value::Type::UtilityBill: + return lang(lng_passport_address_bill); + case Value::Type::BankStatement: + return lang(lng_passport_address_statement); + case Value::Type::RentalAgreement: + return lang(lng_passport_address_agreement); + default: + Unexpected("AddressType in requestScopeFilesType"); + } + }) | ranges::to_vector)); + } else { + Unexpected("Type in processVerificationNeeded."); + } + }(); +} + +void PanelController::editWithUpload(int index, int filesIndex) { + Expects(_panel != nullptr); + Expects(index >= 0 && index < _scopes.size()); + Expects(filesIndex >= 0 && filesIndex < _scopes[index].files.size()); + + EditScans::ChooseScan( + base::lambda_guarded(_panel.get(), + [=](QByteArray &&content) { + editScope(index, filesIndex); + uploadScan(std::move(content)); + })); +} + +void PanelController::editScope(int index, int filesIndex) { + Expects(_panel != nullptr); + Expects(index >= 0 && index < _scopes.size()); + Expects((filesIndex < 0) + || (filesIndex >= 0 && filesIndex < _scopes[index].files.size())); + + _editScope = &_scopes[index]; + _editScopeFilesIndex = filesIndex; _form->startValueEdit(_editScope->fields); if (_editScopeFilesIndex >= 0) { @@ -435,14 +575,16 @@ void PanelController::editScope(int index) { ? object_ptr( _panel.get(), this, - std::move(GetDocumentScheme(_editScope->type)), + GetDocumentScheme( + _editScope->type, + _editScope->files[_editScopeFilesIndex]->type), _editScope->fields->data.parsedInEdit, _editScope->files[_editScopeFilesIndex]->data.parsedInEdit, valueFiles(*_editScope->files[_editScopeFilesIndex])) : object_ptr( _panel.get(), this, - std::move(GetDocumentScheme(_editScope->type)), + GetDocumentScheme(_editScope->type), _editScope->fields->data.parsedInEdit); const auto weak = make_weak(result.data()); _panelHasUnsavedChanges = [=] { diff --git a/Telegram/SourceFiles/passport/passport_panel_controller.h b/Telegram/SourceFiles/passport/passport_panel_controller.h index 995fd460d..44939e7ad 100644 --- a/Telegram/SourceFiles/passport/passport_panel_controller.h +++ b/Telegram/SourceFiles/passport/passport_panel_controller.h @@ -85,6 +85,11 @@ public: private: void ensurePanelCreated(); + void editScope(int index, int filesIndex); + void editWithUpload(int index, int filesIndex); + int findNonEmptyIndex( + const std::vector> &files) const; + void requestScopeFilesType(int index); void cancelValueEdit(); std::vector valueFiles(const Value &value) const; void processValueSaveFinished(not_null value); diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_contact.cpp b/Telegram/SourceFiles/passport/passport_panel_edit_contact.cpp index 7977ffeb6..807e7e682 100644 --- a/Telegram/SourceFiles/passport/passport_panel_edit_contact.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_edit_contact.cpp @@ -29,7 +29,7 @@ namespace { class VerifyBox : public BoxContent { public: VerifyBox( - QWidget *parent, + QWidget*, const QString &title, const QString &text, int codeLength, @@ -58,7 +58,7 @@ private: }; VerifyBox::VerifyBox( - QWidget *parent, + QWidget*, const QString &title, const QString &text, int codeLength, @@ -124,6 +124,7 @@ void VerifyBox::setupControls( waiter->resizeToWidth(innerWidth); waiter->moveToLeft(st::boxPadding.left(), y); y += waiter->height() + st::boxPadding.bottom(); + _height = y; _submit = [=] { submit(_code->getLastText()); @@ -136,7 +137,6 @@ void VerifyBox::setupControls( connect(_code, &SentCodeField::changed, [=] { problem->hide(anim::type::normal); }); - _height = y; } void VerifyBox::setInnerFocus() { diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp b/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp index 0ff97dd87..cc9153e51 100644 --- a/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/labels.h" #include "ui/widgets/buttons.h" #include "ui/widgets/shadow.h" +#include "ui/widgets/checkbox.h" #include "ui/wrap/vertical_layout.h" #include "ui/wrap/fade_wrap.h" #include "boxes/abstract_box.h" @@ -25,6 +26,103 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_passport.h" namespace Passport { +namespace { + +class RequestTypeBox : public BoxContent { +public: + RequestTypeBox( + QWidget*, + const QString &title, + const QString &about, + std::vector labels, + base::lambda submit); + +protected: + void prepare() override; + +private: + void setupControls( + const QString &about, + std::vector labels, + base::lambda submit); + + QString _title; + base::lambda _submit; + int _height = 0; + +}; + +RequestTypeBox::RequestTypeBox( + QWidget*, + const QString &title, + const QString &about, + std::vector labels, + base::lambda submit) +: _title(title) { + setupControls(about, std::move(labels), submit); +} + +void RequestTypeBox::prepare() { + setTitle([=] { return _title; }); + addButton(langFactory(lng_passport_upload_document), [=] { _submit(); }); + addButton(langFactory(lng_cancel), [=] { closeBox(); }); + setDimensions(st::boxWidth, _height); +} + +void RequestTypeBox::setupControls( + const QString &about, + std::vector labels, + base::lambda submit) { + const auto header = Ui::CreateChild( + this, + lang(lng_passport_document_type), + Ui::FlatLabel::InitType::Simple, + st::passportFormLabel); + + const auto group = std::make_shared(0); + auto buttons = std::vector>(); + auto index = 0; + for (const auto &label : labels) { + buttons.push_back(Ui::CreateChild( + this, + group, + index++, + label, + st::defaultBoxCheckbox)); + } + + const auto description = Ui::CreateChild( + this, + about, + Ui::FlatLabel::InitType::Simple, + st::passportFormLabel); + + 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->value() : -1; + if (value >= 0) { + submit(value); + } + }; +} + +} // namespace struct PanelEditDocument::Result { ValueMap data; @@ -97,7 +195,11 @@ not_null PanelEditDocument::setupContent( if (scanData) { _editScans = inner->add( - object_ptr(inner, _controller, std::move(files))); + object_ptr( + inner, + _controller, + _scheme.scansHeader, + std::move(files))); } inner->add(object_ptr( @@ -187,4 +289,24 @@ void PanelEditDocument::save() { std::move(result.filesData)); } +object_ptr RequestIdentityType( + base::lambda submit, + std::vector labels) { + return Box( + lang(lng_passport_identity_title), + lang(lng_passport_identity_about), + std::move(labels), + submit); +} + +object_ptr RequestAddressType( + base::lambda submit, + std::vector labels) { + return Box( + lang(lng_passport_address_title), + lang(lng_passport_address_about), + std::move(labels), + submit); +} + } // namespace Passport diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_document.h b/Telegram/SourceFiles/passport/passport_panel_edit_document.h index 8a598da5e..cada69c39 100644 --- a/Telegram/SourceFiles/passport/passport_panel_edit_document.h +++ b/Telegram/SourceFiles/passport/passport_panel_edit_document.h @@ -40,6 +40,7 @@ public: }; std::vector rows; QString rowsHeader; + QString scansHeader; }; @@ -91,4 +92,11 @@ private: }; +object_ptr RequestIdentityType( + base::lambda submit, + std::vector labels); +object_ptr RequestAddressType( + base::lambda submit, + std::vector labels); + } // namespace Passport diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_scans.cpp b/Telegram/SourceFiles/passport/passport_panel_edit_scans.cpp index 8082ee77a..989cc7407 100644 --- a/Telegram/SourceFiles/passport/passport_panel_edit_scans.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_edit_scans.cpp @@ -184,15 +184,16 @@ void ScanButton::paintEvent(QPaintEvent *e) { EditScans::EditScans( QWidget *parent, not_null controller, + const QString &header, std::vector &&files) : RpWidget(parent) , _controller(controller) , _files(std::move(files)) , _content(this) { - setupContent(); + setupContent(header); } -void EditScans::setupContent() { +void EditScans::setupContent(const QString &header) { const auto inner = _content.data(); inner->move(0, 0); @@ -209,7 +210,7 @@ void EditScans::setupContent() { inner, object_ptr( inner, - lang(lng_passport_upload_header), + header, Ui::FlatLabel::InitType::Simple, st::passportFormHeader), st::passportUploadHeaderPadding)); @@ -296,35 +297,36 @@ void EditScans::pushScan(const ScanInfo &info) { } void EditScans::chooseScan() { + ChooseScan(base::lambda_guarded(this, [=](QByteArray &&content) { + _controller->uploadScan(std::move(content)); + })); +} + +void EditScans::ChooseScan(base::lambda callback) { const auto filter = FileDialog::AllFilesFilter() + qsl(";;Image files (*") + cImgExtensions().join(qsl(" *")) + qsl(")"); - const auto callback = [=](FileDialog::OpenResult &&result) { + const auto processFile = [=](FileDialog::OpenResult &&result) { if (result.paths.size() == 1) { - encryptScan(result.paths.front()); + auto content = [&] { + QFile f(result.paths.front()); + if (!f.open(QIODevice::ReadOnly)) { + return QByteArray(); + } + return f.readAll(); + }(); + if (!content.isEmpty()) { + callback(std::move(content)); + } } else if (!result.remoteContent.isEmpty()) { - encryptScanContent(std::move(result.remoteContent)); + callback(std::move(result.remoteContent)); } }; FileDialog::GetOpenPath( lang(lng_passport_choose_image), filter, - base::lambda_guarded(this, callback)); -} - -void EditScans::encryptScan(const QString &path) { - encryptScanContent([&] { - QFile f(path); - if (!f.open(QIODevice::ReadOnly)) { - return QByteArray(); - } - return f.readAll(); - }()); -} - -void EditScans::encryptScanContent(QByteArray &&content) { - _controller->uploadScan(std::move(content)); + processFile); } rpl::producer EditScans::uploadButtonText() const { diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_scans.h b/Telegram/SourceFiles/passport/passport_panel_edit_scans.h index 074610b1d..f8771e9f9 100644 --- a/Telegram/SourceFiles/passport/passport_panel_edit_scans.h +++ b/Telegram/SourceFiles/passport/passport_panel_edit_scans.h @@ -35,13 +35,14 @@ public: EditScans( QWidget *parent, not_null controller, + const QString &header, std::vector &&files); + static void ChooseScan(base::lambda callback); + private: - void setupContent(); + void setupContent(const QString &header); void chooseScan(); - void encryptScan(const QString &path); - void encryptScanContent(QByteArray &&content); void updateScan(ScanInfo &&info); void pushScan(const ScanInfo &info);