diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 67b0fbcbf..1723b3372 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1591,6 +1591,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_passport_gender_male" = "Male"; "lng_passport_gender_female" = "Female"; "lng_passport_country" = "Country"; +"lng_passport_residence_country" = "Residence"; "lng_passport_country_choose" = "Choose country"; "lng_passport_document_number" = "Card Number"; "lng_passport_expiry_date" = "Expiry date"; diff --git a/Telegram/SourceFiles/messenger.cpp b/Telegram/SourceFiles/messenger.cpp index f1591bf46..686514b70 100644 --- a/Telegram/SourceFiles/messenger.cpp +++ b/Telegram/SourceFiles/messenger.cpp @@ -839,23 +839,22 @@ bool Messenger::openLocalUrl(const QString &url) { auto command = urlTrimmed.midRef(qstr("tg://").size()); const auto showPassportForm = [](const QMap ¶ms) { - if (const auto botId = params.value("bot_id", QString()).toInt()) { - const auto scope = params.value("scope", QString()); - const auto callback = params.value("callback_url", QString()); - const auto publicKey = params.value("public_key", QString()); - const auto payload = params.value("payload", QString()); - const auto errors = params.value("errors", QString()); - if (const auto window = App::wnd()) { - if (const auto controller = window->controller()) { - controller->showPassportForm(Passport::FormRequest( - botId, - scope, - callback, - publicKey, - payload, - errors)); - return true; - } + const auto botId = params.value("bot_id", QString()).toInt(); + const auto scope = params.value("scope", QString()); + const auto callback = params.value("callback_url", QString()); + const auto publicKey = params.value("public_key", QString()); + const auto payload = params.value("payload", QString()); + const auto errors = params.value("errors", QString()); + if (const auto window = App::wnd()) { + if (const auto controller = window->controller()) { + controller->showPassportForm(Passport::FormRequest( + botId, + scope, + callback, + publicKey, + payload, + errors)); + return true; } } return false; diff --git a/Telegram/SourceFiles/passport/passport_form_view_controller.cpp b/Telegram/SourceFiles/passport/passport_form_view_controller.cpp index 35fb183c9..44e61dda4 100644 --- a/Telegram/SourceFiles/passport/passport_form_view_controller.cpp +++ b/Telegram/SourceFiles/passport/passport_form_view_controller.cpp @@ -158,7 +158,7 @@ QString ComputeScopeRowReadyString(const Scope &scope) { return QString(); } const auto text = i->second.text; - if (row.validate && !row.validate(text)) { + if (row.error && row.error(text).has_value()) { return QString(); } pushListValue(format ? format(text) : text); @@ -170,7 +170,7 @@ QString ComputeScopeRowReadyString(const Scope &scope) { return QString(); } const auto text = i->second.text; - if (row.validate && !row.validate(text)) { + if (row.error && row.error(text).has_value()) { return QString(); } pushListValue(text); diff --git a/Telegram/SourceFiles/passport/passport_panel_controller.cpp b/Telegram/SourceFiles/passport/passport_panel_controller.cpp index 16366bc2d..4c5191a18 100644 --- a/Telegram/SourceFiles/passport/passport_panel_controller.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_controller.cpp @@ -28,8 +28,7 @@ constexpr auto kMaxDocumentSize = 24; constexpr auto kMaxStreetSize = 64; constexpr auto kMinCitySize = 2; constexpr auto kMaxCitySize = 64; -constexpr auto kMinPostcodeSize = 2; -constexpr auto kMaxPostcodeSize = 12; +constexpr auto kMaxPostcodeSize = 10; EditDocumentScheme GetDocumentScheme( Scope::Type type, @@ -50,32 +49,53 @@ EditDocumentScheme GetDocumentScheme( return value; }; const auto DontValidate = nullptr; - const auto LimitedValidate = [](int max, int min = 1) { + const auto FromBoolean = [](auto validation) { return [=](const QString &value) { - return (value.size() >= min) && (value.size() <= max); + return validation(value) + ? base::none + : base::make_optional(QString()); }; }; - const auto NameValidate = LimitedValidate(kMaxNameSize); + const auto LimitedValidate = [=](int max, int min = 1) { + return FromBoolean([=](const QString &value) { + return (value.size() >= min) && (value.size() <= max); + }); + }; + using Result = base::optional; + const auto NameValidate = [](const QString &value) -> Result { + if (value.isEmpty() || value.size() > kMaxNameSize) { + return QString(); + } else if (!QRegularExpression( + "^[a-zA-Z\\- ]+$" + ).match(value).hasMatch()) { + return "Use latin characters only.";// lang(lng_passport_bad_name); + } + return base::none; + }; + const auto DocumentValidate = LimitedValidate(kMaxDocumentSize); const auto StreetValidate = LimitedValidate(kMaxStreetSize); const auto CityValidate = LimitedValidate(kMaxCitySize, kMinCitySize); - const auto PostcodeValidate = LimitedValidate( - kMaxPostcodeSize, - kMinPostcodeSize); - const auto DateValidate = [](const QString &value) { + const auto PostcodeValidate = FromBoolean([](const QString &value) { + return QRegularExpression( + QString("^[a-zA-Z0-9\\-]{2,%1}$").arg(kMaxPostcodeSize) + ).match(value).hasMatch(); + }); + const auto DateValidateBoolean = [](const QString &value) { return QRegularExpression( "^\\d{2}\\.\\d{2}\\.\\d{4}$" ).match(value).hasMatch(); }; - const auto DateOrEmptyValidate = [=](const QString &value) { - return value.isEmpty() || DateValidate(value); - }; - const auto GenderValidate = [](const QString &value) { + const auto DateValidate = FromBoolean(DateValidateBoolean); + const auto DateOrEmptyValidate = FromBoolean([=](const QString &value) { + return value.isEmpty() || DateValidateBoolean(value); + }); + const auto GenderValidate = FromBoolean([](const QString &value) { return value == qstr("male") || value == qstr("female"); - }; - const auto CountryValidate = [=](const QString &value) { + }); + const auto CountryValidate = FromBoolean([=](const QString &value) { return !CountryFormat(value).isEmpty(); - }; + }); switch (type) { case Scope::Type::Identity: { @@ -142,6 +162,14 @@ EditDocumentScheme GetDocumentScheme( CountryValidate, CountryFormat, }, + { + ValueClass::Fields, + PanelDetailsType::Country, + qsl("residence_country_code"), + lang(lng_passport_residence_country), + CountryValidate, + CountryFormat, + }, { ValueClass::Scans, PanelDetailsType::Text, @@ -234,7 +262,7 @@ EditDocumentScheme GetDocumentScheme( }, { ValueClass::Fields, - PanelDetailsType::Text, + PanelDetailsType::Postcode, qsl("post_code"), lang(lng_passport_postcode), PostcodeValidate, diff --git a/Telegram/SourceFiles/passport/passport_panel_details_row.cpp b/Telegram/SourceFiles/passport/passport_panel_details_row.cpp index 2a41182b3..66cc2a302 100644 --- a/Telegram/SourceFiles/passport/passport_panel_details_row.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_details_row.cpp @@ -22,9 +22,60 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Passport { namespace { -class TextRow : public PanelDetailsRow { +class PostcodeInput : public Ui::MaskedInputField { public: - TextRow( + PostcodeInput( + QWidget *parent, + const style::InputField &st, + base::lambda placeholderFactory, + const QString &val); + +protected: + void correctValue( + const QString &was, + int wasCursor, + QString &now, + int &nowCursor) override; + +}; + +PostcodeInput::PostcodeInput( + QWidget *parent, + const style::InputField &st, + base::lambda placeholderFactory, + const QString &val) +: MaskedInputField(parent, st, std::move(placeholderFactory), val) { + if (!QRegularExpression("^[a-zA-Z0-9\\-]+$").match(val).hasMatch()) { + setText(QString()); + } +} + +void PostcodeInput::correctValue( + const QString &was, + int wasCursor, + QString &now, + int &nowCursor) { + QString newText; + newText.reserve(now.size()); + auto newPos = nowCursor; + for (auto i = 0, l = now.size(); i < l; ++i) { + const auto ch = now[i]; + if ((ch >= '0' && ch <= '9') + || (ch >= 'a' && ch <= 'z') + || (ch >= 'A' && ch <= 'Z') + || (ch == '-')) { + newText.append(ch); + } else if (i < nowCursor) { + --newPos; + } + } + setCorrectedText(now, nowCursor, newText, newPos); +} + +template +class AbstractTextRow : public PanelDetailsRow { +public: + AbstractTextRow( QWidget *parent, const QString &label, const QString &value, @@ -39,7 +90,7 @@ private: void showInnerError() override; void finishInnerAnimating() override; - object_ptr _field; + object_ptr _field; rpl::variable _value; }; @@ -192,7 +243,8 @@ private: }; -TextRow::TextRow( +template +AbstractTextRow::AbstractTextRow( QWidget *parent, const QString &label, const QString &value, @@ -201,34 +253,40 @@ TextRow::TextRow( , _field(this, st::passportDetailsField, nullptr, value) , _value(value) { _field->setMaxLength(limit); - connect(_field, &Ui::InputField::changed, [=] { + connect(_field, &Input::changed, [=] { _value = valueCurrent(); }); } -bool TextRow::setFocusFast() { +template +bool AbstractTextRow::setFocusFast() { _field->setFocusFast(); return true; } -QString TextRow::valueCurrent() const { +template +QString AbstractTextRow::valueCurrent() const { return _field->getLastText(); } -rpl::producer TextRow::value() const { +template +rpl::producer AbstractTextRow::value() const { return _value.value(); } -int TextRow::resizeInner(int left, int top, int width) { +template +int AbstractTextRow::resizeInner(int left, int top, int width) { _field->setGeometry(left, top, width, _field->height()); return st::semiboldFont->height; } -void TextRow::showInnerError() { +template +void AbstractTextRow::showInnerError() { _field->showError(); } -void TextRow::finishInnerAnimating() { +template +void AbstractTextRow::finishInnerAnimating() { _field->finishAnimating(); } @@ -905,7 +963,17 @@ object_ptr PanelDetailsRow::Create( auto result = [&]() -> object_ptr { switch (type) { case Type::Text: - return object_ptr(parent, label, value, limit); + return object_ptr>( + parent, + label, + value, + limit); + case Type::Postcode: + return object_ptr>( + parent, + label, + value, + limit); case Type::Country: return object_ptr(parent, controller, label, value); case Type::Gender: @@ -944,7 +1012,7 @@ int PanelDetailsRow::resizeGetHeight(int newWidth) { return result; } -void PanelDetailsRow::showError(const QString &error) { +void PanelDetailsRow::showError(base::optional error) { if (!_errorHideSubscription) { _errorHideSubscription = true; @@ -955,17 +1023,24 @@ void PanelDetailsRow::showError(const QString &error) { } showInnerError(); startErrorAnimation(true); - if (!error.isEmpty()) { + if (!error.has_value()) { + return; + } + if (error->isEmpty()) { + if (_error) { + _error->hide(anim::type::normal); + } + } else { if (!_error) { _error.create( this, object_ptr( this, - error, + *error, Ui::FlatLabel::InitType::Simple, st::passportVerifyErrorLabel)); } else { - _error->entity()->setText(error); + _error->entity()->setText(*error); } _error->heightValue( ) | rpl::start_with_next([=] { diff --git a/Telegram/SourceFiles/passport/passport_panel_details_row.h b/Telegram/SourceFiles/passport/passport_panel_details_row.h index 7d206c12b..f1dfc00dd 100644 --- a/Telegram/SourceFiles/passport/passport_panel_details_row.h +++ b/Telegram/SourceFiles/passport/passport_panel_details_row.h @@ -25,6 +25,7 @@ class PanelController; enum class PanelDetailsType { Text, + Postcode, Country, Date, Gender, @@ -65,7 +66,7 @@ public: virtual bool setFocusFast(); virtual rpl::producer value() const = 0; virtual QString valueCurrent() const = 0; - void showError(const QString &error); + void showError(base::optional error = base::none); bool errorShown() const; void hideError(); void finishAnimating(); diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp b/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp index bfb41bf33..aca7a1c43 100644 --- a/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp @@ -419,10 +419,14 @@ bool PanelEditDocument::validate() { auto first = QPointer(); for (const auto [i, field] : base::reversed(_details)) { const auto &row = _scheme.rows[i]; - if (field->errorShown() - || (row.validate && !row.validate(field->valueCurrent()))) { - field->showError(QString()); + 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) { diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_document.h b/Telegram/SourceFiles/passport/passport_panel_edit_document.h index ec1e496aa..cfd3c1fd5 100644 --- a/Telegram/SourceFiles/passport/passport_panel_edit_document.h +++ b/Telegram/SourceFiles/passport/passport_panel_edit_document.h @@ -43,7 +43,7 @@ struct EditDocumentScheme { PanelDetailsType inputType = PanelDetailsType(); QString key; QString label; - base::lambda validate; + base::lambda(const QString &value)> error; base::lambda format; int lengthLimit = 0; };