Allow selecting country in passport.

This commit is contained in:
John Preston 2018-04-10 15:26:21 +04:00
parent 62389f5ef7
commit e4e05a14b7
11 changed files with 417 additions and 32 deletions

View File

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

View File

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

View File

@ -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<ConfirmBox>(
lang(lng_passport_sure_cancel),
lang(lng_continue),
[=] { _panel->showForm(); })).data());
[=] {
_panel->showForm();
base::take(_confirmForgetChangesBox);
})).data());
}
} else {
_panel->showForm();

View File

@ -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<QString> 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<Ui::InputField> _field;
rpl::variable<QString> _value;
};
class CountryRow : public PanelDetailsRow {
public:
CountryRow(
QWidget *parent,
not_null<PanelController*> controller,
const QString &label,
const QString &value);
rpl::producer<QString> 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<PanelController*> _controller;
object_ptr<Ui::LinkButton> _link;
rpl::variable<QString> _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<QString> 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<PanelController*> 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<QString> 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<CountrySelectBox>(
(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> PanelDetailsRow::Create(
QWidget *parent,
Type type,
not_null<PanelController*> controller,
const QString &label,
const QString &value,
const QString &error) {
auto result = [&]() -> object_ptr<PanelDetailsRow> {
switch (type) {
case Type::Text:
return object_ptr<TextRow>(parent, label, value);
case Type::Country:
return object_ptr<CountryRow>(parent, controller, label, value);
case Type::Gender:
return object_ptr<GenderRow>(parent, label, value);
case Type::Date:
return object_ptr<DateRow>(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<Ui::FlatLabel>(
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);
}

View File

@ -14,10 +14,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Ui {
class InputField;
class FlatLabel;
template <typename Widget>
class SlideWrap;
} // namespace Ui
namespace Passport {
class PanelController;
enum class PanelDetailsType {
Text,
Country,
Date,
Gender,
};
class PanelLabel : public Ui::PaddingWrap<Ui::FlatLabel> {
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<PanelDetailsRow> Create(
QWidget *parent,
Type type,
not_null<PanelController*> controller,
const QString &label,
const QString &value,
const QString &error);
virtual bool setFocusFast();
virtual rpl::producer<QString> 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<Ui::InputField> _field;
object_ptr<Ui::SlideWrap<Ui::FlatLabel>> _error = { nullptr };
bool _errorShown = false;
Animation _errorAnimation;
};

View File

@ -231,10 +231,13 @@ not_null<Ui::RpWidget*> PanelEditDocument::setupContent(
if (!fields) {
continue;
}
_details.emplace(i, inner->add(object_ptr<PanelDetailsRow>(
_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;
}

View File

@ -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<bool(const QString &value)> validate;

View File

@ -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<Inner>(this), st::countriesScroll, _select->height());
_inner = setInnerWidget(
object_ptr<Inner>(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);

View File

@ -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<Ui::MultiSelect> _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;

View File

@ -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<QColor> textFg) {
_textFgOverride = textFg;
update();
}
void LinkButton::onStateChanged(State was, StateChangeSource source) {
update();
}

View File

@ -24,6 +24,7 @@ public:
int naturalWidth() const override;
void setText(const QString &text);
void setColorOverride(base::optional<QColor> textFg);
protected:
void paintEvent(QPaintEvent *e) override;
@ -34,6 +35,7 @@ private:
const style::LinkButton &_st;
QString _text;
int _textWidth = 0;
base::optional<QColor> _textFgOverride;
};