Improve name/postcode validation.

This commit is contained in:
John Preston 2018-05-15 16:35:59 +03:00
parent 6aecb81c23
commit 308fb19da4
8 changed files with 165 additions and 57 deletions

View File

@ -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";

View File

@ -839,23 +839,22 @@ bool Messenger::openLocalUrl(const QString &url) {
auto command = urlTrimmed.midRef(qstr("tg://").size());
const auto showPassportForm = [](const QMap<QString, QString> &params) {
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;

View File

@ -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);

View File

@ -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<QString>;
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,

View File

@ -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<QString()> 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<QString()> 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 <typename Input>
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<Ui::InputField> _field;
object_ptr<Input> _field;
rpl::variable<QString> _value;
};
@ -192,7 +243,8 @@ private:
};
TextRow::TextRow(
template <typename Input>
AbstractTextRow<Input>::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 <typename Input>
bool AbstractTextRow<Input>::setFocusFast() {
_field->setFocusFast();
return true;
}
QString TextRow::valueCurrent() const {
template <typename Input>
QString AbstractTextRow<Input>::valueCurrent() const {
return _field->getLastText();
}
rpl::producer<QString> TextRow::value() const {
template <typename Input>
rpl::producer<QString> AbstractTextRow<Input>::value() const {
return _value.value();
}
int TextRow::resizeInner(int left, int top, int width) {
template <typename Input>
int AbstractTextRow<Input>::resizeInner(int left, int top, int width) {
_field->setGeometry(left, top, width, _field->height());
return st::semiboldFont->height;
}
void TextRow::showInnerError() {
template <typename Input>
void AbstractTextRow<Input>::showInnerError() {
_field->showError();
}
void TextRow::finishInnerAnimating() {
template <typename Input>
void AbstractTextRow<Input>::finishInnerAnimating() {
_field->finishAnimating();
}
@ -905,7 +963,17 @@ object_ptr<PanelDetailsRow> PanelDetailsRow::Create(
auto result = [&]() -> object_ptr<PanelDetailsRow> {
switch (type) {
case Type::Text:
return object_ptr<TextRow>(parent, label, value, limit);
return object_ptr<AbstractTextRow<Ui::InputField>>(
parent,
label,
value,
limit);
case Type::Postcode:
return object_ptr<AbstractTextRow<PostcodeInput>>(
parent,
label,
value,
limit);
case Type::Country:
return object_ptr<CountryRow>(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<QString> 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<Ui::FlatLabel>(
this,
error,
*error,
Ui::FlatLabel::InitType::Simple,
st::passportVerifyErrorLabel));
} else {
_error->entity()->setText(error);
_error->entity()->setText(*error);
}
_error->heightValue(
) | rpl::start_with_next([=] {

View File

@ -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<QString> value() const = 0;
virtual QString valueCurrent() const = 0;
void showError(const QString &error);
void showError(base::optional<QString> error = base::none);
bool errorShown() const;
void hideError();
void finishAnimating();

View File

@ -419,10 +419,14 @@ bool PanelEditDocument::validate() {
auto first = QPointer<PanelDetailsRow>();
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) {

View File

@ -43,7 +43,7 @@ struct EditDocumentScheme {
PanelDetailsType inputType = PanelDetailsType();
QString key;
QString label;
base::lambda<bool(const QString &value)> validate;
base::lambda<base::optional<QString>(const QString &value)> error;
base::lambda<QString(const QString &value)> format;
int lengthLimit = 0;
};