passport_details_row.cpp 28 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "passport/ui/passport_details_row.h"
  8. #include "lang/lang_keys.h"
  9. #include "base/platform/base_platform_info.h"
  10. #include "ui/widgets/fields/input_field.h"
  11. #include "ui/widgets/fields/masked_input_field.h"
  12. #include "ui/widgets/labels.h"
  13. #include "ui/widgets/buttons.h"
  14. #include "ui/widgets/checkbox.h"
  15. #include "ui/wrap/slide_wrap.h"
  16. #include "ui/layers/box_content.h"
  17. #include "ui/boxes/country_select_box.h"
  18. #include "ui/painter.h"
  19. #include "countries/countries_instance.h"
  20. #include "styles/style_layers.h"
  21. #include "styles/style_passport.h"
  22. #include "base/qt/qt_common_adapters.h"
  23. #include <QtCore/QRegularExpression>
  24. namespace Passport::Ui {
  25. namespace {
  26. class PostcodeInput : public MaskedInputField {
  27. public:
  28. PostcodeInput(
  29. QWidget *parent,
  30. const style::InputField &st,
  31. rpl::producer<QString> placeholder,
  32. const QString &val);
  33. protected:
  34. void correctValue(
  35. const QString &was,
  36. int wasCursor,
  37. QString &now,
  38. int &nowCursor) override;
  39. };
  40. PostcodeInput::PostcodeInput(
  41. QWidget *parent,
  42. const style::InputField &st,
  43. rpl::producer<QString> placeholder,
  44. const QString &val)
  45. : MaskedInputField(parent, st, std::move(placeholder), val) {
  46. static const auto RegExp = QRegularExpression("^[a-zA-Z0-9\\-]+$");
  47. if (!RegExp.match(val).hasMatch()) {
  48. setText(QString());
  49. }
  50. }
  51. void PostcodeInput::correctValue(
  52. const QString &was,
  53. int wasCursor,
  54. QString &now,
  55. int &nowCursor) {
  56. QString newText;
  57. newText.reserve(now.size());
  58. auto newPos = nowCursor;
  59. for (auto i = 0, l = int(now.size()); i < l; ++i) {
  60. const auto ch = now[i];
  61. if ((ch >= '0' && ch <= '9')
  62. || (ch >= 'a' && ch <= 'z')
  63. || (ch >= 'A' && ch <= 'Z')
  64. || (ch == '-')) {
  65. newText.append(ch);
  66. } else if (i < nowCursor) {
  67. --newPos;
  68. }
  69. }
  70. setCorrectedText(now, nowCursor, newText, newPos);
  71. }
  72. template <typename Input>
  73. class AbstractTextRow : public PanelDetailsRow {
  74. public:
  75. AbstractTextRow(
  76. QWidget *parent,
  77. const QString &label,
  78. int maxLabelWidth,
  79. const QString &value,
  80. int limit);
  81. bool setFocusFast() override;
  82. rpl::producer<QString> value() const override;
  83. QString valueCurrent() const override;
  84. private:
  85. int resizeInner(int left, int top, int width) override;
  86. void showInnerError() override;
  87. void finishInnerAnimating() override;
  88. object_ptr<Input> _field;
  89. rpl::variable<QString> _value;
  90. };
  91. class CountryRow : public PanelDetailsRow {
  92. public:
  93. CountryRow(
  94. QWidget *parent,
  95. Fn<void(object_ptr<BoxContent>)> showBox,
  96. const QString &defaultCountry,
  97. const QString &label,
  98. int maxLabelWidth,
  99. const QString &value);
  100. rpl::producer<QString> value() const override;
  101. QString valueCurrent() const override;
  102. private:
  103. int resizeInner(int left, int top, int width) override;
  104. void showInnerError() override;
  105. void finishInnerAnimating() override;
  106. void chooseCountry();
  107. void hideCountryError();
  108. void toggleError(bool shown);
  109. void errorAnimationCallback();
  110. QString _defaultCountry;
  111. Fn<void(object_ptr<BoxContent>)> _showBox;
  112. object_ptr<LinkButton> _link;
  113. rpl::variable<QString> _value;
  114. bool _errorShown = false;
  115. Animations::Simple _errorAnimation;
  116. };
  117. class DateInput final : public MaskedInputField {
  118. public:
  119. using MaskedInputField::MaskedInputField;
  120. void setMaxValue(int value);
  121. rpl::producer<> erasePrevious() const;
  122. rpl::producer<QChar> putNext() const;
  123. protected:
  124. void keyPressEvent(QKeyEvent *e) override;
  125. void correctValue(
  126. const QString &was,
  127. int wasCursor,
  128. QString &now,
  129. int &nowCursor) override;
  130. private:
  131. int _maxValue = 0;
  132. int _maxDigits = 0;
  133. rpl::event_stream<> _erasePrevious;
  134. rpl::event_stream<QChar> _putNext;
  135. };
  136. class DateRow : public PanelDetailsRow {
  137. public:
  138. DateRow(
  139. QWidget *parent,
  140. const QString &label,
  141. int maxLabelWidth,
  142. const QString &value);
  143. bool setFocusFast() override;
  144. rpl::producer<QString> value() const override;
  145. QString valueCurrent() const override;
  146. protected:
  147. void paintEvent(QPaintEvent *e) override;
  148. void mousePressEvent(QMouseEvent *e) override;
  149. void mouseMoveEvent(QMouseEvent *e) override;
  150. private:
  151. void setInnerFocus();
  152. void putNext(const object_ptr<DateInput> &field, QChar ch);
  153. void erasePrevious(const object_ptr<DateInput> &field);
  154. int resizeInner(int left, int top, int width) override;
  155. void showInnerError() override;
  156. void finishInnerAnimating() override;
  157. void setErrorShown(bool error);
  158. void setFocused(bool focused);
  159. void startBorderAnimation();
  160. template <typename Widget>
  161. bool insideSeparator(QPoint position, const Widget &widget) const;
  162. int day() const;
  163. int month() const;
  164. int year() const;
  165. int number(const object_ptr<DateInput> &field) const;
  166. object_ptr<DateInput> _day;
  167. object_ptr<PaddingWrap<FlatLabel>> _separator1;
  168. object_ptr<DateInput> _month;
  169. object_ptr<PaddingWrap<FlatLabel>> _separator2;
  170. object_ptr<DateInput> _year;
  171. rpl::variable<QString> _value;
  172. style::cursor _cursor = style::cur_default;
  173. Animations::Simple _a_borderShown;
  174. int _borderAnimationStart = 0;
  175. Animations::Simple _a_borderOpacity;
  176. bool _borderVisible = false;
  177. Animations::Simple _a_error;
  178. bool _error = false;
  179. Animations::Simple _a_focused;
  180. bool _focused = false;
  181. };
  182. class GenderRow : public PanelDetailsRow {
  183. public:
  184. GenderRow(
  185. QWidget *parent,
  186. const QString &label,
  187. int maxLabelWidth,
  188. const QString &value);
  189. rpl::producer<QString> value() const override;
  190. QString valueCurrent() const override;
  191. private:
  192. enum class Gender {
  193. Male,
  194. Female,
  195. };
  196. static std::optional<Gender> StringToGender(const QString &value);
  197. static QString GenderToString(Gender gender);
  198. int resizeInner(int left, int top, int width) override;
  199. void showInnerError() override;
  200. void finishInnerAnimating() override;
  201. void toggleError(bool shown);
  202. void hideGenderError();
  203. void errorAnimationCallback();
  204. std::unique_ptr<AbstractCheckView> createRadioView(
  205. RadioView* &weak) const;
  206. std::shared_ptr<RadioenumGroup<Gender>> _group;
  207. RadioView *_maleRadio = nullptr;
  208. RadioView *_femaleRadio = nullptr;
  209. object_ptr<Radioenum<Gender>> _male;
  210. object_ptr<Radioenum<Gender>> _female;
  211. rpl::variable<QString> _value;
  212. bool _errorShown = false;
  213. Animations::Simple _errorAnimation;
  214. };
  215. template <typename Input>
  216. AbstractTextRow<Input>::AbstractTextRow(
  217. QWidget *parent,
  218. const QString &label,
  219. int maxLabelWidth,
  220. const QString &value,
  221. int limit)
  222. : PanelDetailsRow(parent, label, maxLabelWidth)
  223. , _field(this, st::passportDetailsField, nullptr, value)
  224. , _value(value) {
  225. _field->setMaxLength(limit);
  226. if constexpr (std::is_same<Input, Ui::InputField>::value) {
  227. _field->changes(
  228. ) | rpl::start_with_next([=] {
  229. _value = valueCurrent();
  230. }, _field->lifetime());
  231. } else {
  232. connect(_field, &Input::changed, [=] {
  233. _value = valueCurrent();
  234. });
  235. }
  236. }
  237. template <typename Input>
  238. bool AbstractTextRow<Input>::setFocusFast() {
  239. _field->setFocusFast();
  240. return true;
  241. }
  242. template <typename Input>
  243. QString AbstractTextRow<Input>::valueCurrent() const {
  244. return _field->getLastText();
  245. }
  246. template <typename Input>
  247. rpl::producer<QString> AbstractTextRow<Input>::value() const {
  248. return _value.value();
  249. }
  250. template <typename Input>
  251. int AbstractTextRow<Input>::resizeInner(int left, int top, int width) {
  252. _field->setGeometry(left, top, width, _field->height());
  253. return st::semiboldFont->height;
  254. }
  255. template <typename Input>
  256. void AbstractTextRow<Input>::showInnerError() {
  257. _field->showError();
  258. }
  259. template <typename Input>
  260. void AbstractTextRow<Input>::finishInnerAnimating() {
  261. _field->finishAnimating();
  262. }
  263. QString CountryString(const QString &code) {
  264. const auto name = Countries::Instance().countryNameByISO2(code);
  265. return name.isEmpty() ? tr::lng_passport_country_choose(tr::now) : name;
  266. }
  267. CountryRow::CountryRow(
  268. QWidget *parent,
  269. Fn<void(object_ptr<BoxContent>)> showBox,
  270. const QString &defaultCountry,
  271. const QString &label,
  272. int maxLabelWidth,
  273. const QString &value)
  274. : PanelDetailsRow(parent, label, maxLabelWidth)
  275. , _defaultCountry(defaultCountry)
  276. , _showBox(std::move(showBox))
  277. , _link(this, CountryString(value), st::boxLinkButton)
  278. , _value(value) {
  279. _value.changes(
  280. ) | rpl::start_with_next([=] {
  281. hideCountryError();
  282. }, lifetime());
  283. _link->addClickHandler([=] {
  284. chooseCountry();
  285. });
  286. }
  287. QString CountryRow::valueCurrent() const {
  288. return _value.current();
  289. }
  290. rpl::producer<QString> CountryRow::value() const {
  291. return _value.value();
  292. }
  293. int CountryRow::resizeInner(int left, int top, int width) {
  294. _link->move(left, st::passportDetailsField.textMargins.top() + top);
  295. return st::semiboldFont->height;
  296. }
  297. void CountryRow::showInnerError() {
  298. toggleError(true);
  299. }
  300. void CountryRow::finishInnerAnimating() {
  301. if (_errorAnimation.animating()) {
  302. _errorAnimation.stop();
  303. errorAnimationCallback();
  304. }
  305. }
  306. void CountryRow::hideCountryError() {
  307. toggleError(false);
  308. }
  309. void CountryRow::toggleError(bool shown) {
  310. if (_errorShown != shown) {
  311. _errorShown = shown;
  312. _errorAnimation.start(
  313. [=] { errorAnimationCallback(); },
  314. _errorShown ? 0. : 1.,
  315. _errorShown ? 1. : 0.,
  316. st::passportDetailsField.duration);
  317. }
  318. }
  319. void CountryRow::errorAnimationCallback() {
  320. const auto error = _errorAnimation.value(_errorShown ? 1. : 0.);
  321. if (error == 0.) {
  322. _link->setColorOverride(std::nullopt);
  323. } else {
  324. _link->setColorOverride(anim::color(
  325. st::boxLinkButton.color,
  326. st::boxTextFgError,
  327. error));
  328. }
  329. }
  330. void CountryRow::chooseCountry() {
  331. const auto top = _value.current();
  332. const auto name = Countries::Instance().countryNameByISO2(top);
  333. const auto country = !name.isEmpty()
  334. ? top
  335. : !_defaultCountry.isEmpty()
  336. ? _defaultCountry
  337. : Platform::SystemCountry();
  338. auto box = Box<CountrySelectBox>(
  339. country,
  340. CountrySelectBox::Type::Countries);
  341. const auto raw = box.data();
  342. raw->countryChosen(
  343. ) | rpl::start_with_next([=](QString iso) {
  344. _value = iso;
  345. _link->setText(CountryString(iso));
  346. hideCountryError();
  347. raw->closeBox();
  348. }, lifetime());
  349. _showBox(std::move(box));
  350. }
  351. QDate ValidateDate(const QString &value) {
  352. static const auto RegExp = QRegularExpression(
  353. "^([0-9]{2})\\.([0-9]{2})\\.([0-9]{4})$");
  354. const auto match = RegExp.match(value);
  355. if (!match.hasMatch()) {
  356. return QDate();
  357. }
  358. auto result = QDate();
  359. const auto readInt = [](const QString &value) {
  360. auto view = QStringView(value);
  361. while (!view.isEmpty() && view.at(0) == '0') {
  362. view = base::StringViewMid(view, 1);
  363. }
  364. return view.toInt();
  365. };
  366. result.setDate(
  367. readInt(match.captured(3)),
  368. readInt(match.captured(2)),
  369. readInt(match.captured(1)));
  370. return result;
  371. }
  372. QString GetDay(const QString &value) {
  373. if (const auto date = ValidateDate(value); date.isValid()) {
  374. return QString("%1").arg(date.day(), 2, 10, QChar('0'));
  375. }
  376. return QString();
  377. }
  378. QString GetMonth(const QString &value) {
  379. if (const auto date = ValidateDate(value); date.isValid()) {
  380. return QString("%1").arg(date.month(), 2, 10, QChar('0'));
  381. }
  382. return QString();
  383. }
  384. QString GetYear(const QString &value) {
  385. if (const auto date = ValidateDate(value); date.isValid()) {
  386. return QString("%1").arg(date.year(), 4, 10, QChar('0'));
  387. }
  388. return QString();
  389. }
  390. void DateInput::setMaxValue(int value) {
  391. _maxValue = value;
  392. _maxDigits = 0;
  393. while (value > 0) {
  394. ++_maxDigits;
  395. value /= 10;
  396. }
  397. }
  398. rpl::producer<> DateInput::erasePrevious() const {
  399. return _erasePrevious.events();
  400. }
  401. rpl::producer<QChar> DateInput::putNext() const {
  402. return _putNext.events();
  403. }
  404. void DateInput::keyPressEvent(QKeyEvent *e) {
  405. const auto isBackspace = (e->key() == Qt::Key_Backspace);
  406. const auto isBeginning = (cursorPosition() == 0);
  407. if (isBackspace && isBeginning && !hasSelectedText()) {
  408. _erasePrevious.fire({});
  409. } else {
  410. MaskedInputField::keyPressEvent(e);
  411. }
  412. }
  413. void DateInput::correctValue(
  414. const QString &was,
  415. int wasCursor,
  416. QString &now,
  417. int &nowCursor) {
  418. auto newText = QString();
  419. auto newCursor = -1;
  420. const auto oldCursor = nowCursor;
  421. const auto oldLength = now.size();
  422. auto accumulated = 0;
  423. auto limit = 0;
  424. for (; limit != oldLength; ++limit) {
  425. if (now[limit].isDigit()) {
  426. accumulated *= 10;
  427. accumulated += (now[limit].unicode() - '0');
  428. if (accumulated > _maxValue || limit == _maxDigits) {
  429. break;
  430. }
  431. }
  432. }
  433. for (auto i = 0; i != limit;) {
  434. if (now[i].isDigit()) {
  435. newText += now[i];
  436. }
  437. if (++i == oldCursor) {
  438. newCursor = newText.size();
  439. }
  440. }
  441. if (newCursor < 0) {
  442. newCursor = newText.size();
  443. }
  444. if (newText != now) {
  445. now = newText;
  446. setText(now);
  447. startPlaceholderAnimation();
  448. }
  449. if (newCursor != nowCursor) {
  450. nowCursor = newCursor;
  451. setCursorPosition(nowCursor);
  452. }
  453. if (accumulated > _maxValue
  454. || (limit == _maxDigits && oldLength > _maxDigits)) {
  455. if (oldCursor > limit) {
  456. _putNext.fire(QChar('0' + (accumulated % 10)));
  457. } else {
  458. _putNext.fire(QChar(0));
  459. }
  460. }
  461. }
  462. DateRow::DateRow(
  463. QWidget *parent,
  464. const QString &label,
  465. int maxLabelWidth,
  466. const QString &value)
  467. : PanelDetailsRow(parent, label, maxLabelWidth)
  468. , _day(
  469. this,
  470. st::passportDetailsDateField,
  471. tr::lng_date_input_day(),
  472. GetDay(value))
  473. , _separator1(
  474. this,
  475. object_ptr<FlatLabel>(
  476. this,
  477. QString(" / "),
  478. st::passportDetailsSeparator),
  479. st::passportDetailsSeparatorPadding)
  480. , _month(
  481. this,
  482. st::passportDetailsDateField,
  483. tr::lng_date_input_month(),
  484. GetMonth(value))
  485. , _separator2(
  486. this,
  487. object_ptr<FlatLabel>(
  488. this,
  489. QString(" / "),
  490. st::passportDetailsSeparator),
  491. st::passportDetailsSeparatorPadding)
  492. , _year(
  493. this,
  494. st::passportDetailsDateField,
  495. tr::lng_date_input_year(),
  496. GetYear(value))
  497. , _value(valueCurrent()) {
  498. const auto focused = [=](const object_ptr<DateInput> &field) {
  499. return [this, pointer = MakeWeak(field.data())]{
  500. _borderAnimationStart = pointer->borderAnimationStart()
  501. + pointer->x()
  502. - _day->x();
  503. setFocused(true);
  504. };
  505. };
  506. const auto blurred = [=] {
  507. setFocused(false);
  508. };
  509. const auto changed = [=] {
  510. _value = valueCurrent();
  511. };
  512. connect(_day, &MaskedInputField::focused, focused(_day));
  513. connect(_month, &MaskedInputField::focused, focused(_month));
  514. connect(_year, &MaskedInputField::focused, focused(_year));
  515. connect(_day, &MaskedInputField::blurred, blurred);
  516. connect(_month, &MaskedInputField::blurred, blurred);
  517. connect(_year, &MaskedInputField::blurred, blurred);
  518. connect(_day, &MaskedInputField::changed, changed);
  519. connect(_month, &MaskedInputField::changed, changed);
  520. connect(_year, &MaskedInputField::changed, changed);
  521. _day->setMaxValue(31);
  522. _day->putNext() | rpl::start_with_next([=](QChar ch) {
  523. putNext(_month, ch);
  524. }, lifetime());
  525. _month->setMaxValue(12);
  526. _month->putNext() | rpl::start_with_next([=](QChar ch) {
  527. putNext(_year, ch);
  528. }, lifetime());
  529. _month->erasePrevious() | rpl::start_with_next([=] {
  530. erasePrevious(_day);
  531. }, lifetime());
  532. _year->setMaxValue(2999);
  533. _year->erasePrevious() | rpl::start_with_next([=] {
  534. erasePrevious(_month);
  535. }, lifetime());
  536. _separator1->setAttribute(Qt::WA_TransparentForMouseEvents);
  537. _separator2->setAttribute(Qt::WA_TransparentForMouseEvents);
  538. setMouseTracking(true);
  539. _value.changes(
  540. ) | rpl::start_with_next([=] {
  541. setErrorShown(false);
  542. }, lifetime());
  543. }
  544. void DateRow::putNext(const object_ptr<DateInput> &field, QChar ch) {
  545. field->setCursorPosition(0);
  546. if (ch.unicode()) {
  547. field->setText(ch + field->getLastText());
  548. field->setCursorPosition(1);
  549. }
  550. field->setFocus();
  551. }
  552. void DateRow::erasePrevious(const object_ptr<DateInput> &field) {
  553. const auto text = field->getLastText();
  554. if (!text.isEmpty()) {
  555. field->setCursorPosition(text.size() - 1);
  556. field->setText(text.mid(0, text.size() - 1));
  557. }
  558. field->setFocus();
  559. }
  560. bool DateRow::setFocusFast() {
  561. if (day()) {
  562. if (month()) {
  563. _year->setFocusFast();
  564. } else {
  565. _month->setFocusFast();
  566. }
  567. } else {
  568. _day->setFocusFast();
  569. }
  570. return true;
  571. }
  572. int DateRow::number(const object_ptr<DateInput> &field) const {
  573. const auto text = field->getLastText();
  574. auto view = QStringView(text);
  575. while (!view.isEmpty() && view.at(0) == '0') {
  576. view = base::StringViewMid(view, 1);
  577. }
  578. return view.toInt();
  579. }
  580. int DateRow::day() const {
  581. return number(_day);
  582. }
  583. int DateRow::month() const {
  584. return number(_month);
  585. }
  586. int DateRow::year() const {
  587. return number(_year);
  588. }
  589. QString DateRow::valueCurrent() const {
  590. const auto result = QString("%1.%2.%3"
  591. ).arg(day(), 2, 10, QChar('0')
  592. ).arg(month(), 2, 10, QChar('0')
  593. ).arg(year(), 4, 10, QChar('0'));
  594. return ValidateDate(result).isValid() ? result : QString();
  595. }
  596. rpl::producer<QString> DateRow::value() const {
  597. return _value.value();
  598. }
  599. void DateRow::paintEvent(QPaintEvent *e) {
  600. PanelDetailsRow::paintEvent(e);
  601. Painter p(this);
  602. const auto &_st = st::passportDetailsField;
  603. const auto height = _st.heightMin;
  604. const auto width = _year->x() + _year->width() - _day->x();
  605. p.translate(_day->x(), _day->y());
  606. if (_st.border) {
  607. p.fillRect(0, height - _st.border, width, _st.border, _st.borderFg);
  608. }
  609. auto errorDegree = _a_error.value(_error ? 1. : 0.);
  610. auto borderShownDegree = _a_borderShown.value(1.);
  611. auto borderOpacity = _a_borderOpacity.value(_borderVisible ? 1. : 0.);
  612. if (_st.borderActive && (borderOpacity > 0.)) {
  613. auto borderStart = std::clamp(_borderAnimationStart, 0, width);
  614. auto borderFrom = qRound(borderStart * (1. - borderShownDegree));
  615. auto borderTo = borderStart + qRound((width - borderStart) * borderShownDegree);
  616. if (borderTo > borderFrom) {
  617. auto borderFg = anim::brush(_st.borderFgActive, _st.borderFgError, errorDegree);
  618. p.setOpacity(borderOpacity);
  619. p.fillRect(borderFrom, height - _st.borderActive, borderTo - borderFrom, _st.borderActive, borderFg);
  620. p.setOpacity(1);
  621. }
  622. }
  623. }
  624. template <typename Widget>
  625. bool DateRow::insideSeparator(QPoint position, const Widget &widget) const {
  626. const auto x = position.x();
  627. const auto y = position.y();
  628. return (x >= widget->x() && x < widget->x() + widget->width())
  629. && (y >= _day->y() && y < _day->y() + _day->height());
  630. }
  631. void DateRow::mouseMoveEvent(QMouseEvent *e) {
  632. const auto cursor = (insideSeparator(e->pos(), _separator1)
  633. || insideSeparator(e->pos(), _separator2))
  634. ? style::cur_text
  635. : style::cur_default;
  636. if (_cursor != cursor) {
  637. _cursor = cursor;
  638. setCursor(_cursor);
  639. }
  640. }
  641. void DateRow::mousePressEvent(QMouseEvent *e) {
  642. const auto x = e->pos().x();
  643. const auto focus1 = [&] {
  644. if (_day->getLastText().size() > 1) {
  645. _month->setFocus();
  646. } else {
  647. _day->setFocus();
  648. }
  649. };
  650. if (insideSeparator(e->pos(), _separator1)) {
  651. focus1();
  652. _borderAnimationStart = x - _day->x();
  653. } else if (insideSeparator(e->pos(), _separator2)) {
  654. if (_month->getLastText().size() > 1) {
  655. _year->setFocus();
  656. } else {
  657. focus1();
  658. }
  659. _borderAnimationStart = x - _day->x();
  660. }
  661. }
  662. int DateRow::resizeInner(int left, int top, int width) {
  663. const auto right = left + width;
  664. const auto &_st = st::passportDetailsDateField;
  665. const auto &font = _st.placeholderFont;
  666. const auto addToWidth = st::passportDetailsSeparatorPadding.left();
  667. const auto dayWidth = _st.textMargins.left()
  668. + _st.placeholderMargins.left()
  669. + font->width(tr::lng_date_input_day(tr::now))
  670. + _st.placeholderMargins.right()
  671. + _st.textMargins.right()
  672. + addToWidth;
  673. const auto monthWidth = _st.textMargins.left()
  674. + _st.placeholderMargins.left()
  675. + font->width(tr::lng_date_input_month(tr::now))
  676. + _st.placeholderMargins.right()
  677. + _st.textMargins.right()
  678. + addToWidth;
  679. _day->setGeometry(left, top, dayWidth, _day->height());
  680. left += dayWidth - addToWidth;
  681. _separator1->resizeToNaturalWidth(width);
  682. _separator1->move(left, top);
  683. left += _separator1->width();
  684. _month->setGeometry(left, top, monthWidth, _month->height());
  685. left += monthWidth - addToWidth;
  686. _separator2->resizeToNaturalWidth(width);
  687. _separator2->move(left, top);
  688. left += _separator2->width();
  689. _year->setGeometry(left, top, right - left, _year->height());
  690. return st::semiboldFont->height;
  691. }
  692. void DateRow::showInnerError() {
  693. setErrorShown(true);
  694. if (_year->getLastText().size() == 2) {
  695. // We don't support year 95 for 1995 or 03 for 2003.
  696. // Let's give a hint to our user what is wrong.
  697. _year->setFocus();
  698. _year->selectAll();
  699. } else if (!_focused) {
  700. setInnerFocus();
  701. }
  702. }
  703. void DateRow::setInnerFocus() {
  704. if (day()) {
  705. if (month()) {
  706. _year->setFocus();
  707. } else {
  708. _month->setFocus();
  709. }
  710. } else {
  711. _day->setFocus();
  712. }
  713. }
  714. void DateRow::setErrorShown(bool error) {
  715. if (_error != error) {
  716. _error = error;
  717. _a_error.start(
  718. [=] { update(); },
  719. _error ? 0. : 1.,
  720. _error ? 1. : 0.,
  721. st::passportDetailsField.duration);
  722. startBorderAnimation();
  723. }
  724. }
  725. void DateRow::setFocused(bool focused) {
  726. if (_focused != focused) {
  727. _focused = focused;
  728. _a_focused.start(
  729. [=] { update(); },
  730. _focused ? 0. : 1.,
  731. _focused ? 1. : 0.,
  732. st::passportDetailsField.duration);
  733. startBorderAnimation();
  734. }
  735. }
  736. void DateRow::finishInnerAnimating() {
  737. _day->finishAnimating();
  738. _month->finishAnimating();
  739. _year->finishAnimating();
  740. _a_borderOpacity.stop();
  741. _a_borderShown.stop();
  742. _a_error.stop();
  743. }
  744. void DateRow::startBorderAnimation() {
  745. auto borderVisible = (_error || _focused);
  746. if (_borderVisible != borderVisible) {
  747. _borderVisible = borderVisible;
  748. const auto duration = st::passportDetailsField.duration;
  749. if (_borderVisible) {
  750. if (_a_borderOpacity.animating()) {
  751. _a_borderOpacity.start([=] { update(); }, 0., 1., duration);
  752. } else {
  753. _a_borderShown.start([=] { update(); }, 0., 1., duration);
  754. }
  755. } else {
  756. _a_borderOpacity.start([=] { update(); }, 1., 0., duration);
  757. }
  758. }
  759. }
  760. GenderRow::GenderRow(
  761. QWidget *parent,
  762. const QString &label,
  763. int maxLabelWidth,
  764. const QString &value)
  765. : PanelDetailsRow(parent, label, maxLabelWidth)
  766. , _group(StringToGender(value).has_value()
  767. ? std::make_shared<RadioenumGroup<Gender>>(*StringToGender(value))
  768. : std::make_shared<RadioenumGroup<Gender>>())
  769. , _male(
  770. this,
  771. _group,
  772. Gender::Male,
  773. tr::lng_passport_gender_male(tr::now),
  774. st::defaultCheckbox,
  775. createRadioView(_maleRadio))
  776. , _female(
  777. this,
  778. _group,
  779. Gender::Female,
  780. tr::lng_passport_gender_female(tr::now),
  781. st::defaultCheckbox,
  782. createRadioView(_femaleRadio))
  783. , _value(StringToGender(value) ? value : QString()) {
  784. _group->setChangedCallback([=](Gender gender) {
  785. _value = GenderToString(gender);
  786. hideGenderError();
  787. });
  788. }
  789. std::unique_ptr<AbstractCheckView> GenderRow::createRadioView(
  790. RadioView* &weak) const {
  791. auto result = std::make_unique<RadioView>(st::defaultRadio, false);
  792. weak = result.get();
  793. return result;
  794. }
  795. auto GenderRow::StringToGender(const QString &value)
  796. -> std::optional<Gender> {
  797. if (value == u"male"_q) {
  798. return Gender::Male;
  799. } else if (value == u"female"_q) {
  800. return Gender::Female;
  801. }
  802. return std::nullopt;
  803. }
  804. QString GenderRow::GenderToString(Gender gender) {
  805. return (gender == Gender::Male) ? "male" : "female";
  806. }
  807. QString GenderRow::valueCurrent() const {
  808. return _value.current();
  809. }
  810. rpl::producer<QString> GenderRow::value() const {
  811. return _value.value();
  812. }
  813. int GenderRow::resizeInner(int left, int top, int width) {
  814. top += st::passportDetailsField.textMargins.top();
  815. top -= st::defaultCheckbox.textPosition.y();
  816. _male->moveToLeft(left, top);
  817. left += _male->widthNoMargins() + st::passportDetailsGenderSkip;
  818. _female->moveToLeft(left, top);
  819. return st::semiboldFont->height;
  820. }
  821. void GenderRow::showInnerError() {
  822. toggleError(true);
  823. }
  824. void GenderRow::finishInnerAnimating() {
  825. if (_errorAnimation.animating()) {
  826. _errorAnimation.stop();
  827. errorAnimationCallback();
  828. }
  829. }
  830. void GenderRow::hideGenderError() {
  831. toggleError(false);
  832. }
  833. void GenderRow::toggleError(bool shown) {
  834. if (_errorShown != shown) {
  835. _errorShown = shown;
  836. _errorAnimation.start(
  837. [=] { errorAnimationCallback(); },
  838. _errorShown ? 0. : 1.,
  839. _errorShown ? 1. : 0.,
  840. st::passportDetailsField.duration);
  841. }
  842. }
  843. void GenderRow::errorAnimationCallback() {
  844. const auto error = _errorAnimation.value(_errorShown ? 1. : 0.);
  845. if (error == 0.) {
  846. _maleRadio->setUntoggledOverride(std::nullopt);
  847. _femaleRadio->setUntoggledOverride(std::nullopt);
  848. } else {
  849. const auto color = anim::color(
  850. st::defaultRadio.untoggledFg,
  851. st::boxTextFgError,
  852. error);
  853. _maleRadio->setUntoggledOverride(color);
  854. _femaleRadio->setUntoggledOverride(color);
  855. }
  856. }
  857. } // namespace
  858. PanelDetailsRow::PanelDetailsRow(
  859. QWidget *parent,
  860. const QString &label,
  861. int maxLabelWidth)
  862. : _label(label)
  863. , _maxLabelWidth(maxLabelWidth) {
  864. }
  865. object_ptr<PanelDetailsRow> PanelDetailsRow::Create(
  866. QWidget *parent,
  867. Fn<void(object_ptr<BoxContent>)> showBox,
  868. const QString &defaultCountry,
  869. Type type,
  870. const QString &label,
  871. int maxLabelWidth,
  872. const QString &value,
  873. const QString &error,
  874. int limit) {
  875. auto result = [&]() -> object_ptr<PanelDetailsRow> {
  876. switch (type) {
  877. case Type::Text:
  878. return object_ptr<AbstractTextRow<InputField>>(
  879. parent,
  880. label,
  881. maxLabelWidth,
  882. value,
  883. limit);
  884. case Type::Postcode:
  885. return object_ptr<AbstractTextRow<PostcodeInput>>(
  886. parent,
  887. label,
  888. maxLabelWidth,
  889. value,
  890. limit);
  891. case Type::Country:
  892. return object_ptr<CountryRow>(
  893. parent,
  894. showBox,
  895. defaultCountry,
  896. label,
  897. maxLabelWidth,
  898. value);
  899. case Type::Gender:
  900. return object_ptr<GenderRow>(
  901. parent,
  902. label,
  903. maxLabelWidth,
  904. value);
  905. case Type::Date:
  906. return object_ptr<DateRow>(
  907. parent,
  908. label,
  909. maxLabelWidth,
  910. value);
  911. default:
  912. Unexpected("Type in PanelDetailsRow::Create.");
  913. }
  914. }();
  915. if (!error.isEmpty()) {
  916. result->showError(error);
  917. result->finishAnimating();
  918. }
  919. return result;
  920. }
  921. int PanelDetailsRow::LabelWidth(const QString &label) {
  922. return st::semiboldFont->width(label);
  923. }
  924. bool PanelDetailsRow::setFocusFast() {
  925. return false;
  926. }
  927. int PanelDetailsRow::resizeGetHeight(int newWidth) {
  928. const auto padding = st::passportDetailsPadding;
  929. const auto inputLeft = padding.left() + std::max(
  930. st::passportDetailsFieldLeft,
  931. _maxLabelWidth + st::passportDetailsFieldSkipMin);
  932. const auto inputTop = st::passportDetailsFieldTop;
  933. const auto inputRight = padding.right();
  934. const auto inputWidth = std::max(newWidth - inputLeft - inputRight, 0);
  935. const auto innerHeight = resizeInner(inputLeft, inputTop, inputWidth);
  936. const auto result = padding.top()
  937. + innerHeight
  938. + (_error ? _error->height() : 0)
  939. + padding.bottom();
  940. if (_error) {
  941. _error->resizeToWidth(inputWidth);
  942. _error->moveToLeft(inputLeft, result - _error->height());
  943. }
  944. return result;
  945. }
  946. void PanelDetailsRow::showError(std::optional<QString> error) {
  947. if (!_errorHideSubscription) {
  948. _errorHideSubscription = true;
  949. value(
  950. ) | rpl::start_with_next([=] {
  951. hideError();
  952. }, lifetime());
  953. }
  954. showInnerError();
  955. startErrorAnimation(true);
  956. if (!error.has_value()) {
  957. return;
  958. }
  959. if (error->isEmpty()) {
  960. if (_error) {
  961. _error->hide(anim::type::normal);
  962. }
  963. } else {
  964. if (!_error) {
  965. _error.create(
  966. this,
  967. object_ptr<FlatLabel>(
  968. this,
  969. *error,
  970. st::passportVerifyErrorLabel));
  971. } else {
  972. _error->entity()->setText(*error);
  973. }
  974. _error->heightValue(
  975. ) | rpl::start_with_next([=] {
  976. resizeToWidth(width());
  977. }, _error->lifetime());
  978. _error->show(anim::type::normal);
  979. }
  980. }
  981. bool PanelDetailsRow::errorShown() const {
  982. return _errorShown;
  983. }
  984. void PanelDetailsRow::hideError() {
  985. startErrorAnimation(false);
  986. if (_error) {
  987. _error->hide(anim::type::normal);
  988. }
  989. }
  990. void PanelDetailsRow::startErrorAnimation(bool shown) {
  991. if (_errorShown != shown) {
  992. _errorShown = shown;
  993. _errorAnimation.start(
  994. [=] { update(); },
  995. _errorShown ? 0. : 1.,
  996. _errorShown ? 1. : 0.,
  997. st::passportDetailsField.duration);
  998. }
  999. }
  1000. void PanelDetailsRow::finishAnimating() {
  1001. if (_error) {
  1002. _error->finishAnimating();
  1003. }
  1004. if (_errorAnimation.animating()) {
  1005. _errorAnimation.stop();
  1006. update();
  1007. }
  1008. }
  1009. void PanelDetailsRow::paintEvent(QPaintEvent *e) {
  1010. Painter p(this);
  1011. const auto error = _errorAnimation.value(_errorShown ? 1. : 0.);
  1012. p.setFont(st::semiboldFont);
  1013. p.setPen(anim::pen(
  1014. st::passportDetailsField.placeholderFg,
  1015. st::passportDetailsField.placeholderFgError,
  1016. error));
  1017. const auto padding = st::passportDetailsPadding;
  1018. p.drawTextLeft(padding.left(), padding.top(), width(), _label);
  1019. }
  1020. } // namespace Passport::Ui