From f8b2e474b973311fec71f2cccf1f6923aded2dc4 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 13 Apr 2018 14:10:09 +0400 Subject: [PATCH] Validate passport values before saving. --- .../passport/passport_panel_details_row.cpp | 88 ++++++++++++-- .../passport/passport_panel_details_row.h | 1 + .../passport/passport_panel_edit_document.cpp | 22 ++++ .../passport/passport_panel_edit_document.h | 1 + Telegram/SourceFiles/ui/widgets/checkbox.cpp | 111 ++++++++++++++++-- Telegram/SourceFiles/ui/widgets/checkbox.h | 87 ++++++++++++-- 6 files changed, 284 insertions(+), 26 deletions(-) diff --git a/Telegram/SourceFiles/passport/passport_panel_details_row.cpp b/Telegram/SourceFiles/passport/passport_panel_details_row.cpp index 1cd21f44af..7cae5285b1 100644 --- a/Telegram/SourceFiles/passport/passport_panel_details_row.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_details_row.cpp @@ -166,14 +166,26 @@ private: static QString GenderToString(Gender gender); int resizeInner(int left, int top, int width) override; + void showInnerError() override; void finishInnerAnimating() override; + void toggleError(bool shown); + void hideGenderError(); + void errorAnimationCallback(); + + std::unique_ptr createRadioView( + Ui::RadioView* &weak) const; std::shared_ptr> _group; + Ui::RadioView *_maleRadio = nullptr; + Ui::RadioView *_femaleRadio = nullptr; object_ptr> _male; object_ptr> _female; rpl::variable _value; + bool _errorShown = false; + Animation _errorAnimation; + }; TextRow::TextRow( @@ -280,7 +292,7 @@ void CountryRow::toggleError(bool shown) { void CountryRow::errorAnimationCallback() { const auto error = _errorAnimation.current(_errorShown ? 1. : 0.); if (error == 0.) { - _link->setColorOverride(nullptr); + _link->setColorOverride(base::none); } else { _link->setColorOverride(anim::color( st::boxLinkButton.color, @@ -365,7 +377,7 @@ rpl::producer DateInput::putNext() const { void DateInput::keyPressEvent(QKeyEvent *e) { const auto isBackspace = (e->key() == Qt::Key_Backspace); const auto isBeginning = (cursorPosition() == 0); - if (isBackspace && isBeginning) { + if (isBackspace && isBeginning && !hasSelectedText()) { _erasePrevious.fire({}); } else { MaskedInputField::keyPressEvent(e); @@ -470,12 +482,18 @@ DateRow::DateRow( const auto blurred = [=] { setFocused(false); }; + const auto changed = [=] { + _value = valueCurrent(); + }; connect(_day, &Ui::MaskedInputField::focused, focused(_day)); connect(_month, &Ui::MaskedInputField::focused, focused(_month)); connect(_year, &Ui::MaskedInputField::focused, focused(_year)); connect(_day, &Ui::MaskedInputField::blurred, blurred); connect(_month, &Ui::MaskedInputField::blurred, blurred); connect(_year, &Ui::MaskedInputField::blurred, blurred); + connect(_day, &Ui::MaskedInputField::changed, changed); + connect(_month, &Ui::MaskedInputField::changed, changed); + connect(_year, &Ui::MaskedInputField::changed, changed); _day->setMaxValue(31); _day->putNext() | rpl::start_with_next([=](QChar ch) { putNext(_month, ch); @@ -494,6 +512,11 @@ DateRow::DateRow( _separator1->setAttribute(Qt::WA_TransparentForMouseEvents); _separator2->setAttribute(Qt::WA_TransparentForMouseEvents); setMouseTracking(true); + + _value.changes( + ) | rpl::start_with_next([=] { + setErrorShown(false); + }, lifetime()); } void DateRow::putNext(const object_ptr &field, QChar ch) { @@ -748,19 +771,29 @@ GenderRow::GenderRow( _group, Gender::Male, lang(lng_passport_gender_male), - st::defaultCheckbox) + st::defaultCheckbox, + createRadioView(_maleRadio)) , _female( this, _group, Gender::Female, lang(lng_passport_gender_female), - st::defaultCheckbox) + st::defaultCheckbox, + createRadioView(_femaleRadio)) , _value(StringToGender(value) ? value : QString()) { _group->setChangedCallback([=](Gender gender) { _value = GenderToString(gender); + hideGenderError(); }); } +std::unique_ptr GenderRow::createRadioView( + Ui::RadioView* &weak) const { + auto result = std::make_unique(st::defaultRadio, false); + weak = result.get(); + return result; +} + auto GenderRow::StringToGender(const QString &value) -> base::optional { if (value == qstr("male")) { @@ -793,9 +826,44 @@ int GenderRow::resizeInner(int left, int top, int width) { } void GenderRow::showInnerError() { + toggleError(true); } void GenderRow::finishInnerAnimating() { + if (_errorAnimation.animating()) { + _errorAnimation.finish(); + errorAnimationCallback(); + } +} + +void GenderRow::hideGenderError() { + toggleError(false); +} + +void GenderRow::toggleError(bool shown) { + if (_errorShown != shown) { + _errorShown = shown; + _errorAnimation.start( + [=] { errorAnimationCallback(); }, + _errorShown ? 0. : 1., + _errorShown ? 1. : 0., + st::passportDetailsField.duration); + } +} + +void GenderRow::errorAnimationCallback() { + const auto error = _errorAnimation.current(_errorShown ? 1. : 0.); + if (error == 0.) { + _maleRadio->setUntoggledOverride(base::none); + _femaleRadio->setUntoggledOverride(base::none); + } else { + const auto color = anim::color( + st::defaultRadio.untoggledFg, + st::boxTextFgError, + error); + _maleRadio->setUntoggledOverride(color); + _femaleRadio->setUntoggledOverride(color); + } } } // namespace @@ -862,6 +930,14 @@ int PanelDetailsRow::resizeGetHeight(int newWidth) { } void PanelDetailsRow::showError(const QString &error) { + if (!_errorHideSubscription) { + _errorHideSubscription = true; + + value( + ) | rpl::start_with_next([=] { + hideError(); + }, lifetime()); + } showInnerError(); startErrorAnimation(true); if (!error.isEmpty()) { @@ -873,10 +949,6 @@ void PanelDetailsRow::showError(const QString &error) { error, Ui::FlatLabel::InitType::Simple, st::passportVerifyErrorLabel)); - value( - ) | rpl::start_with_next([=] { - hideError(); - }, lifetime()); } else { _error->entity()->setText(error); } diff --git a/Telegram/SourceFiles/passport/passport_panel_details_row.h b/Telegram/SourceFiles/passport/passport_panel_details_row.h index bd4ff62833..b6216b6930 100644 --- a/Telegram/SourceFiles/passport/passport_panel_details_row.h +++ b/Telegram/SourceFiles/passport/passport_panel_details_row.h @@ -83,6 +83,7 @@ private: QString _label; object_ptr> _error = { nullptr }; bool _errorShown = false; + bool _errorHideSubscription = false; Animation _errorAnimation; }; diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp b/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp index c909ae8fee..47e888552d 100644 --- a/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp @@ -299,7 +299,29 @@ PanelEditDocument::Result PanelEditDocument::collect() const { return result; } +bool PanelEditDocument::validate() { + auto first = QPointer(); + for (const auto [i, field] : base::reversed(_details)) { + const auto &row = _scheme.rows[i]; + if (row.validate && !row.validate(field->valueCurrent())) { + field->showError(QString()); + first = field; + } + } + 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), diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_document.h b/Telegram/SourceFiles/passport/passport_panel_edit_document.h index 0ff4f1c421..693db926db 100644 --- a/Telegram/SourceFiles/passport/passport_panel_edit_document.h +++ b/Telegram/SourceFiles/passport/passport_panel_edit_document.h @@ -84,6 +84,7 @@ private: void updateControlsGeometry(); Result collect() const; + bool validate(); void save(); not_null _controller; diff --git a/Telegram/SourceFiles/ui/widgets/checkbox.cpp b/Telegram/SourceFiles/ui/widgets/checkbox.cpp index 9c6adf3da6..d1fc354d6a 100644 --- a/Telegram/SourceFiles/ui/widgets/checkbox.cpp +++ b/Telegram/SourceFiles/ui/widgets/checkbox.cpp @@ -43,6 +43,12 @@ void AbstractCheckView::setUpdateCallback(base::lambda updateCallback) { } } +void AbstractCheckView::update() { + if (_updateCallback) { + _updateCallback(); + } +} + void AbstractCheckView::setCheckedAnimated(bool checked) { if (_checked != checked) { _checked = checked; @@ -212,10 +218,17 @@ void CheckView::setStyle(const style::Check &st) { void CheckView::paint(Painter &p, int left, int top, int outerWidth, TimeMs ms) { auto toggled = currentAnimationValue(ms); - auto pen = anim::pen(_st->untoggledFg, _st->toggledFg, toggled); + auto pen = _untoggledOverride + ? anim::pen(*_untoggledOverride, _st->toggledFg, toggled) + : anim::pen(_st->untoggledFg, _st->toggledFg, toggled); pen.setWidth(_st->thickness); p.setPen(pen); - p.setBrush(anim::brush(_st->bg, anim::color(_st->untoggledFg, _st->toggledFg, toggled), toggled)); + p.setBrush(anim::brush( + _st->bg, + (_untoggledOverride + ? anim::color(*_untoggledOverride, _st->toggledFg, toggled) + : anim::color(_st->untoggledFg, _st->toggledFg, toggled)), + toggled)); { PainterHighQualityEnabler hq(p); @@ -239,7 +252,17 @@ bool CheckView::checkRippleStartPosition(QPoint position) const { return QRect(QPoint(0, 0), rippleSize()).contains(position); } -RadioView::RadioView(const style::Radio &st, bool checked, base::lambda updateCallback) : AbstractCheckView(st.duration, checked, std::move(updateCallback)) +void CheckView::setUntoggledOverride( + base::optional untoggledOverride) { + _untoggledOverride = untoggledOverride; + update(); +} + +RadioView::RadioView( + const style::Radio &st, + bool checked, + base::lambda updateCallback) +: AbstractCheckView(st.duration, checked, std::move(updateCallback)) , _st(&st) { } @@ -255,7 +278,9 @@ void RadioView::paint(Painter &p, int left, int top, int outerWidth, TimeMs ms) PainterHighQualityEnabler hq(p); auto toggled = currentAnimationValue(ms); - auto pen = anim::pen(_st->untoggledFg, _st->toggledFg, toggled); + auto pen = _untoggledOverride + ? anim::pen(*_untoggledOverride, _st->toggledFg, toggled) + : anim::pen(_st->untoggledFg, _st->toggledFg, toggled); pen.setWidth(_st->thickness); p.setPen(pen); p.setBrush(_st->bg); @@ -265,7 +290,9 @@ void RadioView::paint(Painter &p, int left, int top, int outerWidth, TimeMs ms) if (toggled > 0) { p.setPen(Qt::NoPen); - p.setBrush(anim::brush(_st->untoggledFg, _st->toggledFg, toggled)); + p.setBrush(_untoggledOverride + ? anim::brush(*_untoggledOverride, _st->toggledFg, toggled) + : anim::brush(_st->untoggledFg, _st->toggledFg, toggled)); auto skip0 = _st->diameter / 2., skip1 = _st->skip / 10., checkSkip = skip0 * (1. - toggled) + skip1 * toggled; p.drawEllipse(rtlrect(QRectF(left, top, _st->diameter, _st->diameter).marginsRemoved(QMarginsF(checkSkip, checkSkip, checkSkip, checkSkip)), outerWidth)); @@ -295,16 +322,52 @@ bool RadioView::checkRippleStartPosition(QPoint position) const { return QRect(QPoint(0, 0), rippleSize()).contains(position); } -Checkbox::Checkbox(QWidget *parent, const QString &text, bool checked, const style::Checkbox &st, const style::Check &checkSt) : Checkbox(parent, text, st, std::make_unique(checkSt, checked, [this] { updateCheck(); })) { +void RadioView::setUntoggledOverride( + base::optional untoggledOverride) { + _untoggledOverride = untoggledOverride; + update(); } -Checkbox::Checkbox(QWidget *parent, const QString &text, bool checked, const style::Checkbox &st, const style::Toggle &toggleSt) : Checkbox(parent, text, st, std::make_unique(toggleSt, checked, [this] { updateCheck(); })) { +Checkbox::Checkbox( + QWidget *parent, + const QString &text, + bool checked, + const style::Checkbox &st, + const style::Check &checkSt) +: Checkbox( + parent, + text, + st, + std::make_unique( + checkSt, + checked)) { } -Checkbox::Checkbox(QWidget *parent, const QString &text, const style::Checkbox &st, std::unique_ptr check) : RippleButton(parent, st.ripple) +Checkbox::Checkbox( + QWidget *parent, + const QString &text, + bool checked, + const style::Checkbox &st, + const style::Toggle &toggleSt) +: Checkbox( + parent, + text, + st, + std::make_unique( + toggleSt, + checked)) { +} + +Checkbox::Checkbox( + QWidget *parent, + const QString &text, + const style::Checkbox &st, + std::unique_ptr check) +: RippleButton(parent, st.ripple) , _st(st) , _check(std::move(check)) , _text(_st.style, text, _checkboxOptions) { + _check->setUpdateCallback([=] { updateCheck(); }); resizeToText(); setCursor(style::cur_pointer); } @@ -502,9 +565,39 @@ void RadiobuttonGroup::setValue(int value) { } } -Radiobutton::Radiobutton(QWidget *parent, const std::shared_ptr &group, int value, const QString &text, const style::Checkbox &st, const style::Radio &radioSt) : Checkbox(parent, text, st, std::make_unique(radioSt, (group->hasValue() && group->value() == value), [this] { updateCheck(); })) +Radiobutton::Radiobutton( + QWidget *parent, + const std::shared_ptr &group, + int value, + const QString &text, + const style::Checkbox &st, + const style::Radio &radioSt) +: Radiobutton( + parent, + group, + value, + text, + st, + std::make_unique( + radioSt, + (group->hasValue() && group->value() == value))) { +} + +Radiobutton::Radiobutton( + QWidget *parent, + const std::shared_ptr &group, + int value, + const QString &text, + const style::Checkbox &st, + std::unique_ptr check) +: Checkbox( + parent, + text, + st, + std::move(check)) , _group(group) , _value(value) { + checkbox()->setChecked(group->hasValue() && group->value() == value); _group->registerButton(this); subscribe(checkbox()->checkedChanged, [this](bool checked) { if (checked) { diff --git a/Telegram/SourceFiles/ui/widgets/checkbox.h b/Telegram/SourceFiles/ui/widgets/checkbox.h index f24a27160b..c5fc35cfb3 100644 --- a/Telegram/SourceFiles/ui/widgets/checkbox.h +++ b/Telegram/SourceFiles/ui/widgets/checkbox.h @@ -24,6 +24,7 @@ public: bool checked() const { return _checked; } + void update(); float64 currentAnimationValue(TimeMs ms); auto checkedValue() const { @@ -57,7 +58,10 @@ private: class CheckView : public AbstractCheckView { public: - CheckView(const style::Check &st, bool checked, base::lambda updateCallback); + CheckView( + const style::Check &st, + bool checked, + base::lambda updateCallback = nullptr); void setStyle(const style::Check &st); @@ -66,19 +70,28 @@ public: QImage prepareRippleMask() const override; bool checkRippleStartPosition(QPoint position) const override; + void setUntoggledOverride( + base::optional untoggledOverride); + private: QSize rippleSize() const; not_null _st; + base::optional _untoggledOverride; }; class RadioView : public AbstractCheckView { public: - RadioView(const style::Radio &st, bool checked, base::lambda updateCallback); + RadioView( + const style::Radio &st, + bool checked, + base::lambda updateCallback = nullptr); void setStyle(const style::Radio &st); + void setUntoggledOverride(base::optional untoggledOverride); + QSize getSize() const override; void paint(Painter &p, int left, int top, int outerWidth, TimeMs ms) override; QImage prepareRippleMask() const override; @@ -88,12 +101,16 @@ private: QSize rippleSize() const; not_null _st; + base::optional _untoggledOverride; }; class ToggleView : public AbstractCheckView { public: - ToggleView(const style::Toggle &st, bool checked, base::lambda updateCallback); + ToggleView( + const style::Toggle &st, + bool checked, + base::lambda updateCallback = nullptr); void setStyle(const style::Toggle &st); @@ -112,9 +129,23 @@ private: class Checkbox : public RippleButton { public: - Checkbox(QWidget *parent, const QString &text, bool checked = false, const style::Checkbox &st = st::defaultCheckbox, const style::Check &checkSt = st::defaultCheck); - Checkbox(QWidget *parent, const QString &text, bool checked, const style::Checkbox &st, const style::Toggle &toggleSt); - Checkbox(QWidget *parent, const QString &text, const style::Checkbox &st, std::unique_ptr check); + Checkbox( + QWidget *parent, + const QString &text, + bool checked = false, + const style::Checkbox &st = st::defaultCheckbox, + const style::Check &checkSt = st::defaultCheck); + Checkbox( + QWidget *parent, + const QString &text, + bool checked, + const style::Checkbox &st, + const style::Toggle &toggleSt); + Checkbox( + QWidget *parent, + const QString &text, + const style::Checkbox &st, + std::unique_ptr check); void setText(const QString &text); void setCheckAlignment(style::align alignment); @@ -203,7 +234,20 @@ private: class Radiobutton : public Checkbox, private base::Subscriber { public: - Radiobutton(QWidget *parent, const std::shared_ptr &group, int value, const QString &text, const style::Checkbox &st = st::defaultCheckbox, const style::Radio &radioSt = st::defaultRadio); + Radiobutton( + QWidget *parent, + const std::shared_ptr &group, + int value, + const QString &text, + const style::Checkbox &st = st::defaultCheckbox, + const style::Radio &radioSt = st::defaultRadio); + Radiobutton( + QWidget *parent, + const std::shared_ptr &group, + int value, + const QString &text, + const style::Checkbox &st, + std::unique_ptr check); ~Radiobutton(); protected: @@ -267,8 +311,33 @@ private: template class Radioenum : public Radiobutton { public: - Radioenum(QWidget *parent, const std::shared_ptr> &group, Enum value, const QString &text, const style::Checkbox &st = st::defaultCheckbox) - : Radiobutton(parent, std::shared_ptr(group, &group->_group), static_cast(value), text, st) { + Radioenum( + QWidget *parent, + const std::shared_ptr> &group, + Enum value, + const QString &text, + const style::Checkbox &st = st::defaultCheckbox) + : Radiobutton( + parent, + std::shared_ptr(group, &group->_group), + static_cast(value), + text, + st) { + } + Radioenum( + QWidget *parent, + const std::shared_ptr> &group, + Enum value, + const QString &text, + const style::Checkbox &st, + std::unique_ptr check) + : Radiobutton( + parent, + std::shared_ptr(group, &group->_group), + static_cast(value), + text, + st, + std::move(check)) { } };