Validate passport values before saving.

This commit is contained in:
John Preston 2018-04-13 14:10:09 +04:00
parent 8969a7d929
commit f8b2e474b9
6 changed files with 284 additions and 26 deletions

View File

@ -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<Ui::AbstractCheckView> createRadioView(
Ui::RadioView* &weak) const;
std::shared_ptr<Ui::RadioenumGroup<Gender>> _group;
Ui::RadioView *_maleRadio = nullptr;
Ui::RadioView *_femaleRadio = nullptr;
object_ptr<Ui::Radioenum<Gender>> _male;
object_ptr<Ui::Radioenum<Gender>> _female;
rpl::variable<QString> _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<QChar> 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<DateInput> &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<Ui::AbstractCheckView> GenderRow::createRadioView(
Ui::RadioView* &weak) const {
auto result = std::make_unique<Ui::RadioView>(st::defaultRadio, false);
weak = result.get();
return result;
}
auto GenderRow::StringToGender(const QString &value)
-> base::optional<Gender> {
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);
}

View File

@ -83,6 +83,7 @@ private:
QString _label;
object_ptr<Ui::SlideWrap<Ui::FlatLabel>> _error = { nullptr };
bool _errorShown = false;
bool _errorHideSubscription = false;
Animation _errorAnimation;
};

View File

@ -299,7 +299,29 @@ PanelEditDocument::Result PanelEditDocument::collect() const {
return result;
}
bool PanelEditDocument::validate() {
auto first = QPointer<PanelDetailsRow>();
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),

View File

@ -84,6 +84,7 @@ private:
void updateControlsGeometry();
Result collect() const;
bool validate();
void save();
not_null<PanelController*> _controller;

View File

@ -43,6 +43,12 @@ void AbstractCheckView::setUpdateCallback(base::lambda<void()> 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<void()> updateCallback) : AbstractCheckView(st.duration, checked, std::move(updateCallback))
void CheckView::setUntoggledOverride(
base::optional<QColor> untoggledOverride) {
_untoggledOverride = untoggledOverride;
update();
}
RadioView::RadioView(
const style::Radio &st,
bool checked,
base::lambda<void()> 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<CheckView>(checkSt, checked, [this] { updateCheck(); })) {
void RadioView::setUntoggledOverride(
base::optional<QColor> 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<ToggleView>(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<CheckView>(
checkSt,
checked)) {
}
Checkbox::Checkbox(QWidget *parent, const QString &text, const style::Checkbox &st, std::unique_ptr<AbstractCheckView> 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<ToggleView>(
toggleSt,
checked)) {
}
Checkbox::Checkbox(
QWidget *parent,
const QString &text,
const style::Checkbox &st,
std::unique_ptr<AbstractCheckView> 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<RadiobuttonGroup> &group, int value, const QString &text, const style::Checkbox &st, const style::Radio &radioSt) : Checkbox(parent, text, st, std::make_unique<RadioView>(radioSt, (group->hasValue() && group->value() == value), [this] { updateCheck(); }))
Radiobutton::Radiobutton(
QWidget *parent,
const std::shared_ptr<RadiobuttonGroup> &group,
int value,
const QString &text,
const style::Checkbox &st,
const style::Radio &radioSt)
: Radiobutton(
parent,
group,
value,
text,
st,
std::make_unique<RadioView>(
radioSt,
(group->hasValue() && group->value() == value))) {
}
Radiobutton::Radiobutton(
QWidget *parent,
const std::shared_ptr<RadiobuttonGroup> &group,
int value,
const QString &text,
const style::Checkbox &st,
std::unique_ptr<AbstractCheckView> 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) {

View File

@ -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<void()> updateCallback);
CheckView(
const style::Check &st,
bool checked,
base::lambda<void()> 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<QColor> untoggledOverride);
private:
QSize rippleSize() const;
not_null<const style::Check*> _st;
base::optional<QColor> _untoggledOverride;
};
class RadioView : public AbstractCheckView {
public:
RadioView(const style::Radio &st, bool checked, base::lambda<void()> updateCallback);
RadioView(
const style::Radio &st,
bool checked,
base::lambda<void()> updateCallback = nullptr);
void setStyle(const style::Radio &st);
void setUntoggledOverride(base::optional<QColor> 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<const style::Radio*> _st;
base::optional<QColor> _untoggledOverride;
};
class ToggleView : public AbstractCheckView {
public:
ToggleView(const style::Toggle &st, bool checked, base::lambda<void()> updateCallback);
ToggleView(
const style::Toggle &st,
bool checked,
base::lambda<void()> 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<AbstractCheckView> 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<AbstractCheckView> 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<RadiobuttonGroup> &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<RadiobuttonGroup> &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<RadiobuttonGroup> &group,
int value,
const QString &text,
const style::Checkbox &st,
std::unique_ptr<AbstractCheckView> check);
~Radiobutton();
protected:
@ -267,8 +311,33 @@ private:
template <typename Enum>
class Radioenum : public Radiobutton {
public:
Radioenum(QWidget *parent, const std::shared_ptr<RadioenumGroup<Enum>> &group, Enum value, const QString &text, const style::Checkbox &st = st::defaultCheckbox)
: Radiobutton(parent, std::shared_ptr<RadiobuttonGroup>(group, &group->_group), static_cast<int>(value), text, st) {
Radioenum(
QWidget *parent,
const std::shared_ptr<RadioenumGroup<Enum>> &group,
Enum value,
const QString &text,
const style::Checkbox &st = st::defaultCheckbox)
: Radiobutton(
parent,
std::shared_ptr<RadiobuttonGroup>(group, &group->_group),
static_cast<int>(value),
text,
st) {
}
Radioenum(
QWidget *parent,
const std::shared_ptr<RadioenumGroup<Enum>> &group,
Enum value,
const QString &text,
const style::Checkbox &st,
std::unique_ptr<AbstractCheckView> check)
: Radiobutton(
parent,
std::shared_ptr<RadiobuttonGroup>(group, &group->_group),
static_cast<int>(value),
text,
st,
std::move(check)) {
}
};