diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index b512ce2dc..680f140bf 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1552,7 +1552,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_passport_last_name" = "Surname"; "lng_passport_birth_date" = "Date of birth"; "lng_passport_gender" = "Gender"; +"lng_passport_gender_male" = "Male"; +"lng_passport_gender_female" = "Female"; "lng_passport_country" = "Country"; +"lng_passport_country_choose" = "Choose country"; "lng_passport_document_number" = "Card Number"; "lng_passport_expiry_date" = "Expiry date"; "lng_passport_address" = "Address"; diff --git a/Telegram/SourceFiles/passport/passport.style b/Telegram/SourceFiles/passport/passport.style index 30163f2b8..5912cc833 100644 --- a/Telegram/SourceFiles/passport/passport.style +++ b/Telegram/SourceFiles/passport/passport.style @@ -183,7 +183,7 @@ passportScanDeletedOpacity: stickersRowDisabledOpacity; passportDetailsHeaderPadding: margins(22px, 20px, 33px, 10px); passportDetailsPadding: margins(22px, 10px, 28px, 10px); passportDetailsField: InputField(defaultInputField) { - textMargins: margins(2px, 7px, 2px, 0px); + textMargins: margins(2px, 8px, 2px, 0px); placeholderScale: 0.; heightMin: 32px; font: normalFont; diff --git a/Telegram/SourceFiles/passport/passport_panel_controller.cpp b/Telegram/SourceFiles/passport/passport_panel_controller.cpp index c0647b523..57bc1a413 100644 --- a/Telegram/SourceFiles/passport/passport_panel_controller.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_controller.cpp @@ -9,6 +9,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_details_row.h" #include "passport/passport_panel_edit_contact.h" #include "passport/passport_panel_edit_scans.h" #include "passport/passport_panel.h" @@ -64,42 +65,49 @@ PanelEditDocument::Scheme GetDocumentScheme( result.rows = { { Scheme::ValueType::Fields, + PanelDetailsType::Text, qsl("first_name"), lang(lng_passport_first_name), NotEmptyValidate }, { Scheme::ValueType::Fields, + PanelDetailsType::Text, qsl("last_name"), lang(lng_passport_last_name), DontValidate }, { Scheme::ValueType::Fields, + PanelDetailsType::Date, qsl("birth_date"), lang(lng_passport_birth_date), DateValidate, }, { Scheme::ValueType::Fields, + PanelDetailsType::Gender, qsl("gender"), lang(lng_passport_gender), GenderValidate, }, { Scheme::ValueType::Fields, + PanelDetailsType::Country, qsl("country_code"), lang(lng_passport_country), CountryValidate, }, { Scheme::ValueType::Scans, + PanelDetailsType::Text, qsl("document_no"), lang(lng_passport_document_number), NotEmptyValidate, }, { Scheme::ValueType::Scans, + PanelDetailsType::Date, qsl("expiry_date"), lang(lng_passport_expiry_date), DateOrEmptyValidate, @@ -129,36 +137,42 @@ PanelEditDocument::Scheme GetDocumentScheme( result.rows = { { Scheme::ValueType::Fields, + PanelDetailsType::Text, qsl("street_line1"), lang(lng_passport_street), NotEmptyValidate }, { Scheme::ValueType::Fields, + PanelDetailsType::Text, qsl("street_line2"), lang(lng_passport_street), DontValidate }, { Scheme::ValueType::Fields, + PanelDetailsType::Text, qsl("city"), lang(lng_passport_city), NotEmptyValidate }, { Scheme::ValueType::Fields, + PanelDetailsType::Text, qsl("state"), lang(lng_passport_state), DontValidate }, { Scheme::ValueType::Fields, + PanelDetailsType::Country, qsl("country_code"), lang(lng_passport_country), CountryValidate }, { Scheme::ValueType::Fields, + PanelDetailsType::Text, qsl("post_code"), lang(lng_passport_postcode), NotEmptyValidate @@ -768,7 +782,10 @@ void PanelController::cancelEditScope() { _confirmForgetChangesBox = BoxPointer(show(Box( lang(lng_passport_sure_cancel), lang(lng_continue), - [=] { _panel->showForm(); })).data()); + [=] { + _panel->showForm(); + base::take(_confirmForgetChangesBox); + })).data()); } } else { _panel->showForm(); diff --git a/Telegram/SourceFiles/passport/passport_panel_details_row.cpp b/Telegram/SourceFiles/passport/passport_panel_details_row.cpp index abc674ce4..27201a795 100644 --- a/Telegram/SourceFiles/passport/passport_panel_details_row.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_details_row.cpp @@ -7,10 +7,207 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "passport/passport_panel_details_row.h" +#include "passport/passport_panel_controller.h" +#include "lang/lang_keys.h" +#include "platform/platform_specific.h" #include "ui/widgets/input_fields.h" +#include "ui/widgets/labels.h" +#include "ui/widgets/buttons.h" +#include "ui/wrap/slide_wrap.h" +#include "ui/countryinput.h" +#include "styles/style_boxes.h" #include "styles/style_passport.h" namespace Passport { +namespace { + +class TextRow : public PanelDetailsRow { +public: + TextRow(QWidget *parent, const QString &label, const QString &value); + + bool setFocusFast() override; + rpl::producer value() const override; + QString valueCurrent() const override; + +private: + int resizeInner(int left, int top, int width) override; + void showInnerError() override; + void finishInnerAnimating() override; + + object_ptr _field; + rpl::variable _value; + +}; + +class CountryRow : public PanelDetailsRow { +public: + CountryRow( + QWidget *parent, + not_null controller, + const QString &label, + const QString &value); + + rpl::producer value() const override; + QString valueCurrent() const override; + +private: + int resizeInner(int left, int top, int width) override; + void showInnerError() override; + void finishInnerAnimating() override; + + void chooseCountry(); + void hideCountryError(); + void toggleError(bool shown); + void errorAnimationCallback(); + + not_null _controller; + object_ptr _link; + rpl::variable _value; + bool _errorShown = false; + Animation _errorAnimation; + +}; + +class DateRow : public TextRow { +public: + using TextRow::TextRow; + +}; + +class GenderRow : public TextRow { +public: + using TextRow::TextRow; + +}; + +TextRow::TextRow( + QWidget *parent, + const QString &label, + const QString &value) +: PanelDetailsRow(parent, label) +, _field(this, st::passportDetailsField, nullptr, value) +, _value(value) { + connect(_field, &Ui::InputField::changed, [=] { + _value = valueCurrent(); + }); +} + +bool TextRow::setFocusFast() { + _field->setFocusFast(); + return true; +} + +QString TextRow::valueCurrent() const { + return _field->getLastText(); +} + +rpl::producer TextRow::value() const { + return _value.value(); +} + +int TextRow::resizeInner(int left, int top, int width) { + _field->setGeometry(left, top, width, _field->height()); + return st::semiboldFont->height; +} + +void TextRow::showInnerError() { + _field->showError(); +} + +void TextRow::finishInnerAnimating() { + _field->finishAnimating(); +} + +QString CountryString(const QString &code) { + const auto name = CountrySelectBox::NameByISO(code); + return name.isEmpty() ? lang(lng_passport_country_choose) : name; +} + +CountryRow::CountryRow( + QWidget *parent, + not_null controller, + const QString &label, + const QString &value) +: PanelDetailsRow(parent, label) +, _controller(controller) +, _link(this, CountryString(value), st::boxLinkButton) +, _value(value) { + _value.changes( + ) | rpl::start_with_next([=] { + hideCountryError(); + }, lifetime()); + + _link->addClickHandler([=] { + chooseCountry(); + }); +} + +QString CountryRow::valueCurrent() const { + return _value.current(); +} + +rpl::producer CountryRow::value() const { + return _value.value(); +} + +int CountryRow::resizeInner(int left, int top, int width) { + _link->move(left, st::passportDetailsField.textMargins.top() + top); + return st::semiboldFont->height; +} + +void CountryRow::showInnerError() { + toggleError(true); +} + +void CountryRow::finishInnerAnimating() { + if (_errorAnimation.animating()) { + _errorAnimation.finish(); + errorAnimationCallback(); + } +} + +void CountryRow::hideCountryError() { + toggleError(false); +} + +void CountryRow::toggleError(bool shown) { + if (_errorShown != shown) { + _errorShown = shown; + _errorAnimation.start( + [=] { errorAnimationCallback(); }, + _errorShown ? 0. : 1., + _errorShown ? 1. : 0., + st::passportDetailsField.duration); + } +} + +void CountryRow::errorAnimationCallback() { + const auto error = _errorAnimation.current(_errorShown ? 1. : 0.); + if (error == 0.) { + _link->setColorOverride(nullptr); + } else { + _link->setColorOverride(anim::color( + st::boxLinkButton.color, + st::boxTextFgError, + error)); + } +} + +void CountryRow::chooseCountry() { + const auto top = _value.current(); + const auto name = CountrySelectBox::NameByISO(top); + const auto box = _controller->show(Box( + (name.isEmpty() ? Platform::SystemCountry() : top), + CountrySelectBox::Type::Countries)); + connect(box, &CountrySelectBox::countryChosen, this, [=](QString iso) { + _value = iso; + _link->setText(CountryString(iso)); + hideCountryError(); + box->closeBox(); + }); +} + +} // namespace int PanelLabel::naturalWidth() const { return -1; @@ -24,19 +221,40 @@ void PanelLabel::resizeEvent(QResizeEvent *e) { PanelDetailsRow::PanelDetailsRow( QWidget *parent, - const QString &label, - const QString &value) -: _label(label) -, _field(this, st::passportDetailsField, nullptr, value) { + const QString &label) +: _label(label) { +} + +object_ptr PanelDetailsRow::Create( + QWidget *parent, + Type type, + not_null controller, + const QString &label, + const QString &value, + const QString &error) { + auto result = [&]() -> object_ptr { + switch (type) { + case Type::Text: + return object_ptr(parent, label, value); + case Type::Country: + return object_ptr(parent, controller, label, value); + case Type::Gender: + return object_ptr(parent, label, value); + case Type::Date: + return object_ptr(parent, label, value); + default: + Unexpected("Type in PanelDetailsRow::Create."); + } + }(); + if (!error.isEmpty()) { + result->showError(error); + result->finishAnimating(); + } + return result; } bool PanelDetailsRow::setFocusFast() { - _field->setFocusFast(); - return true; -} - -QString PanelDetailsRow::getValue() const { - return _field->getLastText(); + return false; } int PanelDetailsRow::resizeGetHeight(int newWidth) { @@ -45,15 +263,76 @@ int PanelDetailsRow::resizeGetHeight(int newWidth) { const auto inputTop = st::passportDetailsFieldTop; const auto inputRight = padding.right(); const auto inputWidth = std::max(newWidth - inputLeft - inputRight, 0); - _field->setGeometry(inputLeft, inputTop, inputWidth, _field->height()); - return padding.top() + st::semiboldFont->height + padding.bottom(); + const auto innerHeight = resizeInner(inputLeft, inputTop, inputWidth); + return padding.top() + + innerHeight + + (_error ? _error->height() : 0) + + padding.bottom(); +} + +void PanelDetailsRow::showError(const QString &error) { + showInnerError(); + startErrorAnimation(true); + if (!error.isEmpty()) { + if (!_error) { + _error.create( + this, + object_ptr( + this, + error, + Ui::FlatLabel::InitType::Simple, + st::passportVerifyErrorLabel)); + value( + ) | rpl::start_with_next([=] { + hideError(); + }, lifetime()); + } else { + _error->entity()->setText(error); + } + _error->show(anim::type::normal); + } else if (_error) { + _error->hide(anim::type::normal); + } +} + +void PanelDetailsRow::hideError() { + startErrorAnimation(false); + if (_error) { + _error->hide(anim::type::normal); + } +} + +void PanelDetailsRow::startErrorAnimation(bool shown) { + if (_errorShown != shown) { + _errorShown = shown; + _errorAnimation.start( + [=] { update(); }, + _errorShown ? 0. : 1., + _errorShown ? 1. : 0., + st::passportDetailsField.duration); + } +} + +void PanelDetailsRow::finishAnimating() { + if (_error) { + _error->finishAnimating(); + } + if (_errorAnimation.animating()) { + _errorAnimation.finish(); + update(); + } } void PanelDetailsRow::paintEvent(QPaintEvent *e) { Painter p(this); + const auto ms = getms(); + const auto error = _errorAnimation.current(ms, _errorShown ? 1. : 0.); p.setFont(st::semiboldFont); - p.setPen(st::passportDetailsField.placeholderFg); + p.setPen(anim::pen( + st::passportDetailsField.placeholderFg, + st::passportDetailsField.placeholderFgError, + error)); const auto padding = st::passportDetailsPadding; p.drawTextLeft(padding.left(), padding.top(), width(), _label); } diff --git a/Telegram/SourceFiles/passport/passport_panel_details_row.h b/Telegram/SourceFiles/passport/passport_panel_details_row.h index de9cb1fdc..e9fcef6f9 100644 --- a/Telegram/SourceFiles/passport/passport_panel_details_row.h +++ b/Telegram/SourceFiles/passport/passport_panel_details_row.h @@ -14,10 +14,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Ui { class InputField; +class FlatLabel; +template +class SlideWrap; } // namespace Ui namespace Passport { +class PanelController; + +enum class PanelDetailsType { + Text, + Country, + Date, + Gender, +}; + class PanelLabel : public Ui::PaddingWrap { public: using PaddingWrap::PaddingWrap; @@ -34,13 +46,26 @@ private: class PanelDetailsRow : public Ui::RpWidget { public: + using Type = PanelDetailsType; + PanelDetailsRow( QWidget *parent, - const QString &label, - const QString &value); + const QString &label); - bool setFocusFast(); - QString getValue() const; + static object_ptr Create( + QWidget *parent, + Type type, + not_null controller, + const QString &label, + const QString &value, + const QString &error); + + virtual bool setFocusFast(); + virtual rpl::producer value() const = 0; + virtual QString valueCurrent() const = 0; + void showError(const QString &error); + void hideError(); + void finishAnimating(); protected: int resizeGetHeight(int newWidth) override; @@ -48,8 +73,16 @@ protected: void paintEvent(QPaintEvent *e) override; private: + virtual int resizeInner(int left, int top, int width) = 0; + virtual void showInnerError() = 0; + virtual void finishInnerAnimating() = 0; + + void startErrorAnimation(bool shown); + QString _label; - object_ptr _field; + object_ptr> _error = { nullptr }; + bool _errorShown = false; + Animation _errorAnimation; }; diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp b/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp index cc9153e51..d2e77d6ea 100644 --- a/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp @@ -231,10 +231,13 @@ not_null PanelEditDocument::setupContent( if (!fields) { continue; } - _details.emplace(i, inner->add(object_ptr( + _details.emplace(i, inner->add(PanelDetailsRow::Create( inner, + row.inputType, + _controller, row.label, - valueOrEmpty(*fields, row.key)))); + valueOrEmpty(*fields, row.key), + QString()))); } return inner; @@ -277,7 +280,7 @@ PanelEditDocument::Result PanelEditDocument::collect() const { auto &fields = (row.type == Scheme::ValueType::Fields) ? result.data : result.filesData; - fields.fields[row.key] = field->getValue(); + fields.fields[row.key] = field->valueCurrent(); } return result; } diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_document.h b/Telegram/SourceFiles/passport/passport_panel_edit_document.h index cada69c39..1d9b40f83 100644 --- a/Telegram/SourceFiles/passport/passport_panel_edit_document.h +++ b/Telegram/SourceFiles/passport/passport_panel_edit_document.h @@ -24,6 +24,7 @@ struct ValueMap; struct ScanInfo; class EditScans; class PanelDetailsRow; +enum class PanelDetailsType; class PanelEditDocument : public Ui::RpWidget { public: @@ -34,6 +35,7 @@ public: }; struct Row { ValueType type = ValueType::Fields; + PanelDetailsType inputType = PanelDetailsType(); QString key; QString label; base::lambda validate; diff --git a/Telegram/SourceFiles/ui/countryinput.cpp b/Telegram/SourceFiles/ui/countryinput.cpp index caf29431d..acf02c1ae 100644 --- a/Telegram/SourceFiles/ui/countryinput.cpp +++ b/Telegram/SourceFiles/ui/countryinput.cpp @@ -203,6 +203,22 @@ CountrySelectBox::CountrySelectBox(QWidget*) : _select(this, st::contactsMultiSelect, langFactory(lng_country_ph)) { } +CountrySelectBox::CountrySelectBox(QWidget*, const QString &iso, Type type) +: _type(type) +, _select(this, st::contactsMultiSelect, langFactory(lng_country_ph)) { + lastValidISO = iso; +} + +QString CountrySelectBox::NameByISO(const QString &iso) { + if (_countriesByISO2.isEmpty()) { + initCountries(); + } + const auto i = _countriesByISO2.constFind(iso); + return (i == _countriesByISO2.cend()) + ? QString() + : QString::fromUtf8((*i)->name); +} + void CountrySelectBox::prepare() { setTitle(langFactory(lng_country_select)); @@ -210,7 +226,10 @@ void CountrySelectBox::prepare() { _select->setQueryChangedCallback([this](const QString &query) { onFilterUpdate(query); }); _select->setSubmittedCallback([this](Qt::KeyboardModifiers) { onSubmit(); }); - _inner = setInnerWidget(object_ptr(this), st::countriesScroll, _select->height()); + _inner = setInnerWidget( + object_ptr(this, _type), + st::countriesScroll, + _select->height()); addButton(langFactory(lng_close), [this] { closeBox(); }); @@ -256,10 +275,16 @@ void CountrySelectBox::setInnerFocus() { _select->setInnerFocus(); } -CountrySelectBox::Inner::Inner(QWidget *parent) : TWidget(parent) +CountrySelectBox::Inner::Inner(QWidget *parent, Type type) +: TWidget(parent) +, _type(type) , _rowHeight(st::countryRowHeight) { setAttribute(Qt::WA_OpaquePaintEvent); + if (countriesNames.isEmpty()) { + initCountries(); + } + CountriesByISO2::const_iterator l = _countriesByISO2.constFind(lastValidISO); bool seenLastValid = false; int already = countriesAll.size(); @@ -341,9 +366,11 @@ void CountrySelectBox::Inner::paintEvent(QPaintEvent *e) { p.setPen(st::countryRowNameFg); p.drawTextLeft(st::countryRowPadding.left(), y + st::countryRowPadding.top(), width(), name); - p.setFont(st::countryRowCodeFont); - p.setPen(selected ? st::countryRowCodeFgOver : st::countryRowCodeFg); - p.drawTextLeft(st::countryRowPadding.left() + nameWidth + st::countryRowPadding.right(), y + st::countryRowPadding.top(), width(), code); + if (_type == Type::Phones) { + p.setFont(st::countryRowCodeFont); + p.setPen(selected ? st::countryRowCodeFgOver : st::countryRowCodeFg); + p.drawTextLeft(st::countryRowPadding.left() + nameWidth + st::countryRowPadding.right(), y + st::countryRowPadding.top(), width(), code); + } } } else { p.fillRect(r, st::boxBg); diff --git a/Telegram/SourceFiles/ui/countryinput.h b/Telegram/SourceFiles/ui/countryinput.h index f62abac10..bc2f941c6 100644 --- a/Telegram/SourceFiles/ui/countryinput.h +++ b/Telegram/SourceFiles/ui/countryinput.h @@ -56,7 +56,15 @@ class CountrySelectBox : public BoxContent { Q_OBJECT public: + enum class Type { + Phones, + Countries, + }; + CountrySelectBox(QWidget*); + CountrySelectBox(QWidget*, const QString &iso, Type type); + + static QString NameByISO(const QString &iso); signals: void countryChosen(const QString &iso); @@ -74,6 +82,7 @@ private slots: private: void onFilterUpdate(const QString &query); + Type _type = Type::Phones; object_ptr _select; class Inner; @@ -86,7 +95,7 @@ class CountrySelectBox::Inner : public TWidget { Q_OBJECT public: - Inner(QWidget *parent); + Inner(QWidget *parent, Type type); void updateFilter(QString filter = QString()); @@ -120,7 +129,8 @@ private: void updateRow(int index); void setPressed(int pressed); - int _rowHeight; + Type _type = Type::Phones; + int _rowHeight = 0; int _selected = -1; int _pressed = -1; diff --git a/Telegram/SourceFiles/ui/widgets/buttons.cpp b/Telegram/SourceFiles/ui/widgets/buttons.cpp index be9cc9c76..ec9dda8a5 100644 --- a/Telegram/SourceFiles/ui/widgets/buttons.cpp +++ b/Telegram/SourceFiles/ui/widgets/buttons.cpp @@ -35,8 +35,12 @@ int LinkButton::naturalWidth() const { void LinkButton::paintEvent(QPaintEvent *e) { Painter p(this); - auto &font = (isOver() ? _st.overFont : _st.font); - auto &pen = (isOver() ? _st.overColor : _st.color); + const auto &font = (isOver() ? _st.overFont : _st.font); + const auto pen = _textFgOverride.has_value() + ? QPen(*_textFgOverride) + : isOver() + ? _st.overColor + : _st.color; p.setFont(font); p.setPen(pen); const auto left = _st.padding.left(); @@ -56,6 +60,11 @@ void LinkButton::setText(const QString &text) { update(); } +void LinkButton::setColorOverride(base::optional textFg) { + _textFgOverride = textFg; + update(); +} + void LinkButton::onStateChanged(State was, StateChangeSource source) { update(); } diff --git a/Telegram/SourceFiles/ui/widgets/buttons.h b/Telegram/SourceFiles/ui/widgets/buttons.h index a7fa51c7b..72a87b0fc 100644 --- a/Telegram/SourceFiles/ui/widgets/buttons.h +++ b/Telegram/SourceFiles/ui/widgets/buttons.h @@ -24,6 +24,7 @@ public: int naturalWidth() const override; void setText(const QString &text); + void setColorOverride(base::optional textFg); protected: void paintEvent(QPaintEvent *e) override; @@ -34,6 +35,7 @@ private: const style::LinkButton &_st; QString _text; int _textWidth = 0; + base::optional _textFgOverride; };