passport_panel_edit_contact.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  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/passport_panel_edit_contact.h"
  8. #include "core/file_utilities.h"
  9. #include "passport/passport_panel_controller.h"
  10. #include "passport/ui/passport_details_row.h"
  11. #include "ui/widgets/fields/input_field.h"
  12. #include "ui/widgets/labels.h"
  13. #include "ui/widgets/buttons.h"
  14. #include "ui/widgets/shadow.h"
  15. #include "ui/widgets/box_content_divider.h"
  16. #include "ui/widgets/sent_code_field.h"
  17. #include "ui/wrap/vertical_layout.h"
  18. #include "ui/wrap/slide_wrap.h"
  19. #include "ui/wrap/fade_wrap.h"
  20. #include "ui/text/format_values.h" // Ui::FormatPhone
  21. #include "ui/text/text_utilities.h" // Ui::Text::ToUpper
  22. #include "ui/widgets/fields/special_fields.h"
  23. #include "boxes/abstract_box.h"
  24. #include "data/data_user.h"
  25. #include "countries/countries_instance.h" // Countries::ExtractPhoneCode.
  26. #include "main/main_session.h"
  27. #include "lang/lang_keys.h"
  28. #include "styles/style_boxes.h"
  29. #include "styles/style_passport.h"
  30. #include "styles/style_layers.h"
  31. namespace Passport {
  32. namespace {
  33. class VerifyBox : public Ui::BoxContent {
  34. public:
  35. VerifyBox(
  36. QWidget*,
  37. rpl::producer<QString> title,
  38. const QString &text,
  39. int codeLength,
  40. const QString &openUrl,
  41. Fn<void(QString code)> submit,
  42. Fn<void()> resend,
  43. rpl::producer<QString> call,
  44. rpl::producer<QString> error,
  45. rpl::producer<QString> resent);
  46. void setInnerFocus() override;
  47. protected:
  48. void prepare() override;
  49. private:
  50. void setupControls(
  51. const QString &text,
  52. int codeLength,
  53. const QString &openUrl,
  54. Fn<void(QString code)> submit,
  55. Fn<void()> resend,
  56. rpl::producer<QString> call,
  57. rpl::producer<QString> error,
  58. rpl::producer<QString> resent);
  59. rpl::producer<QString> _title;
  60. Fn<void()> _submit;
  61. QPointer<Ui::SentCodeField> _code;
  62. QPointer<Ui::VerticalLayout> _content;
  63. };
  64. VerifyBox::VerifyBox(
  65. QWidget*,
  66. rpl::producer<QString> title,
  67. const QString &text,
  68. int codeLength,
  69. const QString &openUrl,
  70. Fn<void(QString code)> submit,
  71. Fn<void()> resend,
  72. rpl::producer<QString> call,
  73. rpl::producer<QString> error,
  74. rpl::producer<QString> resent)
  75. : _title(std::move(title)) {
  76. setupControls(
  77. text,
  78. codeLength,
  79. openUrl,
  80. submit,
  81. resend,
  82. std::move(call),
  83. std::move(error),
  84. std::move(resent));
  85. }
  86. void VerifyBox::setupControls(
  87. const QString &text,
  88. int codeLength,
  89. const QString &openUrl,
  90. Fn<void(QString code)> submit,
  91. Fn<void()> resend,
  92. rpl::producer<QString> call,
  93. rpl::producer<QString> error,
  94. rpl::producer<QString> resent) {
  95. _content = Ui::CreateChild<Ui::VerticalLayout>(this);
  96. const auto small = style::margins(
  97. st::boxPadding.left(),
  98. 0,
  99. st::boxPadding.right(),
  100. st::boxPadding.bottom());
  101. _content->add(
  102. object_ptr<Ui::FlatLabel>(
  103. _content,
  104. text,
  105. st::boxLabel),
  106. small);
  107. _code = _content->add(
  108. object_ptr<Ui::SentCodeField>(
  109. _content,
  110. st::defaultInputField,
  111. tr::lng_change_phone_code_title()),
  112. small);
  113. const auto problem = _content->add(
  114. object_ptr<Ui::FadeWrap<Ui::FlatLabel>>(
  115. _content,
  116. object_ptr<Ui::FlatLabel>(
  117. _content,
  118. QString(),
  119. st::passportVerifyErrorLabel)),
  120. small);
  121. _content->add(
  122. object_ptr<Ui::FlatLabel>(
  123. _content,
  124. std::move(call),
  125. st::boxDividerLabel),
  126. small);
  127. if (!openUrl.isEmpty()) {
  128. const auto button = _content->add(
  129. object_ptr<Ui::RoundButton>(
  130. _content,
  131. tr::lng_intro_fragment_button(),
  132. st::fragmentBoxButton),
  133. small);
  134. _content->widthValue(
  135. ) | rpl::start_with_next([=](int w) {
  136. button->setFullWidth(w - small.left() - small.right());
  137. }, button->lifetime());
  138. button->setClickedCallback([=] { ::File::OpenUrl(openUrl); });
  139. button->setTextTransform(
  140. Ui::RoundButton::TextTransform::NoTransform);
  141. }
  142. if (resend) {
  143. auto link = TextWithEntities{ tr::lng_cloud_password_resend(tr::now) };
  144. link.entities.push_back({
  145. EntityType::CustomUrl,
  146. 0,
  147. int(link.text.size()),
  148. QString("internal:resend") });
  149. const auto label = _content->add(
  150. object_ptr<Ui::FlatLabel>(
  151. _content,
  152. rpl::single(
  153. link
  154. ) | rpl::then(rpl::duplicate(
  155. resent
  156. ) | rpl::map(TextWithEntities::Simple)),
  157. st::boxDividerLabel),
  158. small);
  159. std::move(
  160. resent
  161. ) | rpl::start_with_next([=] {
  162. _content->resizeToWidth(st::boxWidth);
  163. }, _content->lifetime());
  164. label->overrideLinkClickHandler(resend);
  165. }
  166. std::move(
  167. error
  168. ) | rpl::start_with_next([=](const QString &error) {
  169. if (error.isEmpty()) {
  170. problem->hide(anim::type::normal);
  171. } else {
  172. problem->entity()->setText(error);
  173. _content->resizeToWidth(st::boxWidth);
  174. problem->show(anim::type::normal);
  175. _code->showError();
  176. }
  177. }, lifetime());
  178. _submit = [=] {
  179. submit(_code->getDigitsOnly());
  180. };
  181. if (codeLength > 0) {
  182. _code->setAutoSubmit(codeLength, _submit);
  183. } else {
  184. _code->submits() | rpl::start_with_next(_submit, _code->lifetime());
  185. }
  186. _code->changes(
  187. ) | rpl::start_with_next([=] {
  188. problem->hide(anim::type::normal);
  189. }, _code->lifetime());
  190. }
  191. void VerifyBox::setInnerFocus() {
  192. _code->setFocusFast();
  193. }
  194. void VerifyBox::prepare() {
  195. setTitle(std::move(_title));
  196. addButton(tr::lng_change_phone_new_submit(), _submit);
  197. addButton(tr::lng_cancel(), [=] { closeBox(); });
  198. _content->resizeToWidth(st::boxWidth);
  199. _content->heightValue(
  200. ) | rpl::start_with_next([=](int height) {
  201. setDimensions(st::boxWidth, height);
  202. }, _content->lifetime());
  203. }
  204. } // namespace
  205. EditContactScheme::EditContactScheme(ValueType type) : type(type) {
  206. }
  207. PanelEditContact::PanelEditContact(
  208. QWidget*,
  209. not_null<PanelController*> controller,
  210. Scheme scheme,
  211. const QString &data,
  212. const QString &existing)
  213. : _controller(controller)
  214. , _scheme(std::move(scheme))
  215. , _content(this)
  216. , _bottomShadow(this)
  217. , _done(
  218. this,
  219. tr::lng_passport_save_value(),
  220. st::passportPanelSaveValue) {
  221. setupControls(data, existing);
  222. }
  223. void PanelEditContact::setupControls(
  224. const QString &data,
  225. const QString &existing) {
  226. widthValue(
  227. ) | rpl::start_with_next([=](int width) {
  228. _content->resizeToWidth(width);
  229. }, _content->lifetime());
  230. _content->add(object_ptr<Ui::BoxContentDivider>(
  231. _content,
  232. st::passportFormDividerHeight));
  233. if (!existing.isEmpty()) {
  234. _content->add(
  235. object_ptr<Ui::SettingsButton>(
  236. _content,
  237. tr::lng_passport_use_existing(
  238. lt_existing,
  239. rpl::single(_scheme.format
  240. ? _scheme.format(existing)
  241. : existing)),
  242. st::passportUploadButton),
  243. st::passportUploadButtonPadding
  244. )->addClickHandler([=] {
  245. save(existing);
  246. });
  247. _content->add(
  248. object_ptr<Ui::DividerLabel>(
  249. _content,
  250. object_ptr<Ui::FlatLabel>(
  251. _content,
  252. _scheme.aboutExisting,
  253. st::boxDividerLabel),
  254. st::passportFormLabelPadding));
  255. _content->add(
  256. object_ptr<Ui::FlatLabel>(
  257. _content,
  258. _scheme.newHeader,
  259. st::passportFormHeader),
  260. st::passportDetailsHeaderPadding);
  261. }
  262. const auto &fieldStyle = existing.isEmpty()
  263. ? st::passportContactField
  264. : st::passportDetailsField;
  265. const auto fieldPadding = existing.isEmpty()
  266. ? st::passportContactFieldPadding
  267. : st::passportContactNewFieldPadding;
  268. auto fieldPlaceholder = existing.isEmpty()
  269. ? rpl::duplicate(_scheme.newPlaceholder)
  270. : nullptr;
  271. auto wrap = object_ptr<Ui::RpWidget>(_content);
  272. if (_scheme.type == Scheme::ValueType::Phone) {
  273. _field = Ui::CreateChild<Ui::PhoneInput>(
  274. wrap.data(),
  275. fieldStyle,
  276. std::move(fieldPlaceholder),
  277. Countries::ExtractPhoneCode(
  278. _controller->bot()->session().user()->phone()),
  279. data,
  280. [](const QString &s) { return Countries::Groups(s); });
  281. } else {
  282. _field = Ui::CreateChild<Ui::MaskedInputField>(
  283. wrap.data(),
  284. fieldStyle,
  285. std::move(fieldPlaceholder),
  286. data);
  287. }
  288. _field->move(0, 0);
  289. _field->heightValue(
  290. ) | rpl::start_with_next([=, pointer = wrap.data()](int height) {
  291. pointer->resize(pointer->width(), height);
  292. }, _field->lifetime());
  293. wrap->widthValue(
  294. ) | rpl::start_with_next([=](int width) {
  295. _field->resize(width, _field->height());
  296. }, _field->lifetime());
  297. _content->add(std::move(wrap), fieldPadding);
  298. const auto errorWrap = _content->add(
  299. object_ptr<Ui::SlideWrap<Ui::FlatLabel>>(
  300. _content,
  301. object_ptr<Ui::FlatLabel>(
  302. _content,
  303. QString(),
  304. st::passportVerifyErrorLabel),
  305. st::passportContactErrorPadding),
  306. st::passportContactErrorMargin);
  307. errorWrap->hide(anim::type::instant);
  308. _content->add(
  309. object_ptr<Ui::DividerLabel>(
  310. _content,
  311. object_ptr<Ui::FlatLabel>(
  312. _content,
  313. _scheme.aboutNew,
  314. st::boxDividerLabel),
  315. st::passportFormLabelPadding));
  316. if (auto text = _controller->deleteValueLabel()) {
  317. _content->add(
  318. object_ptr<Ui::SettingsButton>(
  319. _content,
  320. std::move(*text) | Ui::Text::ToUpper(),
  321. st::passportDeleteButton),
  322. st::passportUploadButtonPadding
  323. )->addClickHandler([=] {
  324. _controller->deleteValue();
  325. });
  326. }
  327. _controller->saveErrors(
  328. ) | rpl::start_with_next([=](const ScopeError &error) {
  329. if (error.key == QString("value")) {
  330. _field->showError();
  331. errorWrap->entity()->setText(error.text);
  332. _content->resizeToWidth(width());
  333. errorWrap->show(anim::type::normal);
  334. }
  335. }, lifetime());
  336. const auto submit = [=] {
  337. crl::on_main(this, [=] {
  338. save();
  339. });
  340. };
  341. connect(_field, &Ui::MaskedInputField::submitted, submit);
  342. connect(_field, &Ui::MaskedInputField::changed, [=] {
  343. errorWrap->hide(anim::type::normal);
  344. });
  345. _done->addClickHandler(submit);
  346. }
  347. void PanelEditContact::focusInEvent(QFocusEvent *e) {
  348. crl::on_main(this, [=] {
  349. _field->setFocusFast();
  350. });
  351. }
  352. void PanelEditContact::resizeEvent(QResizeEvent *e) {
  353. updateControlsGeometry();
  354. }
  355. void PanelEditContact::updateControlsGeometry() {
  356. const auto submitTop = height() - _done->height();
  357. _bottomShadow->resizeToWidth(width());
  358. _bottomShadow->moveToLeft(0, submitTop - st::lineWidth);
  359. _done->resizeToWidth(width());
  360. _done->moveToLeft(0, submitTop);
  361. }
  362. void PanelEditContact::save() {
  363. const auto result = _field->getLastText();
  364. const auto processed = _scheme.postprocess
  365. ? _scheme.postprocess(result)
  366. : result;
  367. if (_scheme.validate && !_scheme.validate(processed)) {
  368. _field->showError();
  369. return;
  370. }
  371. save(processed);
  372. }
  373. void PanelEditContact::save(const QString &value) {
  374. auto data = ValueMap();
  375. data.fields["value"].text = value;
  376. _controller->saveScope(std::move(data), {});
  377. }
  378. object_ptr<Ui::BoxContent> VerifyPhoneBox(
  379. const QString &phone,
  380. int codeLength,
  381. const QString &openUrl,
  382. Fn<void(QString code)> submit,
  383. rpl::producer<QString> call,
  384. rpl::producer<QString> error) {
  385. return Box<VerifyBox>(
  386. tr::lng_passport_phone_title(),
  387. tr::lng_passport_confirm_phone(
  388. tr::now,
  389. lt_phone,
  390. Ui::FormatPhone(phone)),
  391. codeLength,
  392. openUrl,
  393. submit,
  394. nullptr,
  395. std::move(call),
  396. std::move(error),
  397. nullptr);
  398. }
  399. object_ptr<Ui::BoxContent> VerifyEmailBox(
  400. const QString &email,
  401. int codeLength,
  402. Fn<void(QString code)> submit,
  403. Fn<void()> resend,
  404. rpl::producer<QString> error,
  405. rpl::producer<QString> resent) {
  406. return Box<VerifyBox>(
  407. tr::lng_passport_email_title(),
  408. tr::lng_passport_confirm_email(tr::now, lt_email, email),
  409. codeLength,
  410. QString(),
  411. submit,
  412. resend,
  413. rpl::single(QString()),
  414. std::move(error),
  415. std::move(resent));
  416. }
  417. } // namespace Passport