passport_panel_controller.cpp 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480
  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_controller.h"
  8. #include "main/main_account.h"
  9. #include "main/main_session.h"
  10. #include "lang/lang_keys.h"
  11. #include "passport/passport_panel_edit_document.h"
  12. #include "passport/passport_panel_edit_contact.h"
  13. #include "passport/passport_panel_edit_scans.h"
  14. #include "passport/passport_panel.h"
  15. #include "passport/ui/passport_details_row.h"
  16. #include "base/unixtime.h"
  17. #include "boxes/passcode_box.h"
  18. #include "ui/boxes/confirm_box.h"
  19. #include "window/window_session_controller.h"
  20. #include "ui/toast/toast.h"
  21. #include "ui/rp_widget.h"
  22. #include "ui/countryinput.h"
  23. #include "ui/text/format_values.h"
  24. #include "ui/widgets/sent_code_field.h"
  25. #include "core/update_checker.h"
  26. #include "countries/countries_instance.h"
  27. #include "styles/style_layers.h"
  28. namespace Passport {
  29. namespace {
  30. constexpr auto kMaxNameSize = 255;
  31. constexpr auto kMaxDocumentSize = 24;
  32. constexpr auto kMaxStreetSize = 64;
  33. constexpr auto kMinCitySize = 2;
  34. constexpr auto kMaxCitySize = 64;
  35. constexpr auto kMaxPostcodeSize = 10;
  36. const auto kLanguageNamePrefix = "cloud_lng_passport_in_";
  37. ScanInfo CollectScanInfo(const EditFile &file) {
  38. const auto status = [&] {
  39. if (file.fields.accessHash) {
  40. switch (file.fields.downloadStatus.status()) {
  41. case LoadStatus::Status::Failed:
  42. return tr::lng_attach_failed(tr::now);
  43. case LoadStatus::Status::InProgress:
  44. return Ui::FormatDownloadText(
  45. file.fields.downloadStatus.offset(),
  46. file.fields.size);
  47. case LoadStatus::Status::Done:
  48. return tr::lng_passport_scan_uploaded(
  49. tr::now,
  50. lt_date,
  51. langDateTimeFull(
  52. base::unixtime::parse(file.fields.date)));
  53. }
  54. Unexpected("LoadStatus value in CollectScanInfo.");
  55. } else if (file.uploadData) {
  56. switch (file.uploadData->status.status()) {
  57. case LoadStatus::Status::Failed:
  58. return tr::lng_attach_failed(tr::now);
  59. case LoadStatus::Status::InProgress:
  60. return Ui::FormatDownloadText(
  61. file.uploadData->status.offset(),
  62. file.uploadData->bytes.size());
  63. case LoadStatus::Status::Done:
  64. return tr::lng_passport_scan_uploaded(
  65. tr::now,
  66. lt_date,
  67. langDateTimeFull(
  68. base::unixtime::parse(file.fields.date)));
  69. }
  70. Unexpected("LoadStatus value in CollectScanInfo.");
  71. } else {
  72. return Ui::FormatDownloadText(0, file.fields.size);
  73. }
  74. }();
  75. return {
  76. file.type,
  77. FileKey{ file.fields.id },
  78. !file.fields.error.isEmpty() ? file.fields.error : status,
  79. file.fields.image,
  80. file.deleted,
  81. file.fields.error };
  82. }
  83. ScanListData PrepareScanListData(const Value &value, FileType type) {
  84. auto result = ScanListData();
  85. for (const auto &scan : value.filesInEdit(type)) {
  86. result.files.push_back(CollectScanInfo(scan));
  87. }
  88. result.errorMissing = value.fileMissingError(type);
  89. return result;
  90. }
  91. std::map<FileType, ScanInfo> PrepareSpecialFiles(const Value &value) {
  92. auto result = std::map<FileType, ScanInfo>();
  93. const auto types = {
  94. FileType::FrontSide,
  95. FileType::ReverseSide,
  96. FileType::Selfie
  97. };
  98. for (const auto type : types) {
  99. if (value.requiresSpecialScan(type)) {
  100. const auto i = value.specialScansInEdit.find(type);
  101. result.emplace(
  102. type,
  103. (i != end(value.specialScansInEdit)
  104. ? CollectScanInfo(i->second)
  105. : ScanInfo(type)));
  106. }
  107. }
  108. return result;
  109. }
  110. } // namespace
  111. EditDocumentScheme GetDocumentScheme(
  112. Scope::Type type,
  113. std::optional<Value::Type> scansType,
  114. bool nativeNames,
  115. preferredLangCallback &&preferredLanguage) {
  116. using Scheme = EditDocumentScheme;
  117. using ValueClass = Scheme::ValueClass;
  118. const auto DontFormat = nullptr;
  119. const auto CountryFormat = [](const QString &value) {
  120. const auto result = Countries::Instance().countryNameByISO2(value);
  121. return result.isEmpty() ? value : result;
  122. };
  123. const auto GenderFormat = [](const QString &value) {
  124. if (value == u"male"_q) {
  125. return tr::lng_passport_gender_male(tr::now);
  126. } else if (value == u"female"_q) {
  127. return tr::lng_passport_gender_female(tr::now);
  128. }
  129. return value;
  130. };
  131. const auto DontValidate = nullptr;
  132. const auto FromBoolean = [](auto validation) {
  133. return [=](const QString &value) {
  134. return validation(value)
  135. ? std::nullopt
  136. : base::make_optional(QString());
  137. };
  138. };
  139. const auto LimitedValidate = [=](int max, int min = 1) {
  140. return FromBoolean([=](const QString &value) {
  141. return (value.size() >= min) && (value.size() <= max);
  142. });
  143. };
  144. using Result = std::optional<QString>;
  145. const auto NameValidate = [](const QString &value) -> Result {
  146. static const auto RegExp = QRegularExpression(
  147. "^[a-zA-Z0-9\\.,/&\\-' ]+$"
  148. );
  149. if (value.isEmpty() || value.size() > kMaxNameSize) {
  150. return QString();
  151. } else if (!RegExp.match(value).hasMatch()) {
  152. return tr::lng_passport_bad_name(tr::now);
  153. }
  154. return std::nullopt;
  155. };
  156. const auto NativeNameValidate = LimitedValidate(kMaxNameSize);
  157. const auto NativeNameOrEmptyValidate = LimitedValidate(kMaxNameSize, 0);
  158. const auto DocumentValidate = LimitedValidate(kMaxDocumentSize);
  159. const auto StreetValidate = LimitedValidate(kMaxStreetSize);
  160. const auto CityValidate = LimitedValidate(kMaxCitySize, kMinCitySize);
  161. const auto PostcodeValidate = FromBoolean([](const QString &value) {
  162. static const auto RegExp = QRegularExpression(
  163. QString("^[a-zA-Z0-9\\-]{2,%1}$").arg(kMaxPostcodeSize)
  164. );
  165. return RegExp.match(value).hasMatch();
  166. });
  167. const auto DateValidateBoolean = [](const QString &value) {
  168. static const auto RegExp = QRegularExpression(
  169. "^\\d{2}\\.\\d{2}\\.\\d{4}$"
  170. );
  171. return RegExp.match(value).hasMatch();
  172. };
  173. const auto DateValidate = FromBoolean(DateValidateBoolean);
  174. const auto DateOrEmptyValidate = FromBoolean([=](const QString &value) {
  175. return value.isEmpty() || DateValidateBoolean(value);
  176. });
  177. const auto GenderValidate = FromBoolean([](const QString &value) {
  178. return value == u"male"_q || value == u"female"_q;
  179. });
  180. const auto CountryValidate = FromBoolean([=](const QString &value) {
  181. return !CountryFormat(value).isEmpty();
  182. });
  183. const auto NameOrEmptyValidate = [=](const QString &value) -> Result {
  184. if (value.isEmpty()) {
  185. return std::nullopt;
  186. }
  187. return NameValidate(value);
  188. };
  189. switch (type) {
  190. case Scope::Type::PersonalDetails:
  191. case Scope::Type::Identity: {
  192. auto result = Scheme();
  193. result.detailsHeader = tr::lng_passport_personal_details(tr::now);
  194. result.fieldsHeader = tr::lng_passport_document_details(tr::now);
  195. if (scansType) {
  196. result.scansHeader = [&] {
  197. switch (*scansType) {
  198. case Value::Type::Passport:
  199. return tr::lng_passport_identity_passport(tr::now);
  200. case Value::Type::DriverLicense:
  201. return tr::lng_passport_identity_license(tr::now);
  202. case Value::Type::IdentityCard:
  203. return tr::lng_passport_identity_card(tr::now);
  204. case Value::Type::InternalPassport:
  205. return tr::lng_passport_identity_internal(tr::now);
  206. default:
  207. Unexpected("scansType in GetDocumentScheme:Identity.");
  208. }
  209. }();
  210. }
  211. result.rows = {
  212. {
  213. ValueClass::Fields,
  214. Ui::PanelDetailsType::Text,
  215. "first_name"_q,
  216. tr::lng_passport_first_name(tr::now),
  217. NameValidate,
  218. DontFormat,
  219. kMaxNameSize,
  220. },
  221. {
  222. ValueClass::Fields,
  223. Ui::PanelDetailsType::Text,
  224. "middle_name"_q,
  225. tr::lng_passport_middle_name(tr::now),
  226. NameOrEmptyValidate,
  227. DontFormat,
  228. kMaxNameSize,
  229. "first_name"_q,
  230. },
  231. {
  232. ValueClass::Fields,
  233. Ui::PanelDetailsType::Text,
  234. "last_name"_q,
  235. tr::lng_passport_last_name(tr::now),
  236. NameValidate,
  237. DontFormat,
  238. kMaxNameSize,
  239. "first_name"_q,
  240. },
  241. {
  242. ValueClass::Fields,
  243. Ui::PanelDetailsType::Date,
  244. "birth_date"_q,
  245. tr::lng_passport_birth_date(tr::now),
  246. DateValidate,
  247. DontFormat,
  248. },
  249. {
  250. ValueClass::Fields,
  251. Ui::PanelDetailsType::Gender,
  252. "gender"_q,
  253. tr::lng_passport_gender(tr::now),
  254. GenderValidate,
  255. GenderFormat,
  256. },
  257. {
  258. ValueClass::Fields,
  259. Ui::PanelDetailsType::Country,
  260. "country_code"_q,
  261. tr::lng_passport_country(tr::now),
  262. CountryValidate,
  263. CountryFormat,
  264. },
  265. {
  266. ValueClass::Fields,
  267. Ui::PanelDetailsType::Country,
  268. "residence_country_code"_q,
  269. tr::lng_passport_residence_country(tr::now),
  270. CountryValidate,
  271. CountryFormat,
  272. },
  273. {
  274. ValueClass::Scans,
  275. Ui::PanelDetailsType::Text,
  276. "document_no"_q,
  277. tr::lng_passport_document_number(tr::now),
  278. DocumentValidate,
  279. DontFormat,
  280. kMaxDocumentSize,
  281. },
  282. {
  283. ValueClass::Scans,
  284. Ui::PanelDetailsType::Date,
  285. "expiry_date"_q,
  286. tr::lng_passport_expiry_date(tr::now),
  287. DateOrEmptyValidate,
  288. DontFormat,
  289. },
  290. };
  291. if (nativeNames) {
  292. result.additionalDependencyKey = "residence_country_code"_q;
  293. result.preferredLanguage = preferredLanguage
  294. ? std::move(preferredLanguage)
  295. : [](const QString &) {
  296. return rpl::single(EditDocumentCountry());
  297. };
  298. const auto languageValue = [](const QString &langCode) {
  299. return Lang::GetNonDefaultValue(kLanguageNamePrefix
  300. + langCode.toUtf8());
  301. };
  302. result.additionalHeader = [=](const EditDocumentCountry &info) {
  303. const auto language = languageValue(info.languageCode);
  304. return language.isEmpty()
  305. ? tr::lng_passport_native_name_title(tr::now)
  306. : tr::lng_passport_native_name_language(
  307. tr::now,
  308. lt_language,
  309. language);
  310. };
  311. result.additionalDescription = [=](
  312. const EditDocumentCountry &info) {
  313. const auto language = languageValue(info.languageCode);
  314. if (!language.isEmpty()) {
  315. return tr::lng_passport_native_name_language_about(
  316. tr::now);
  317. }
  318. const auto name = Countries::Instance().countryNameByISO2(
  319. info.countryCode);
  320. Assert(!name.isEmpty());
  321. return tr::lng_passport_native_name_about(
  322. tr::now,
  323. lt_country,
  324. name);
  325. };
  326. result.additionalShown = [](const EditDocumentCountry &info) {
  327. using Result = EditDocumentScheme::AdditionalVisibility;
  328. return (info.countryCode.isEmpty())
  329. ? Result::Hidden
  330. : (info.languageCode == "en")
  331. ? Result::OnlyIfError
  332. : Result::Shown;
  333. };
  334. using Row = EditDocumentScheme::Row;
  335. auto additional = std::initializer_list<Row>{
  336. {
  337. ValueClass::Additional,
  338. Ui::PanelDetailsType::Text,
  339. "first_name_native"_q,
  340. tr::lng_passport_first_name(tr::now),
  341. NativeNameValidate,
  342. DontFormat,
  343. kMaxNameSize,
  344. QString(),
  345. "first_name"_q,
  346. },
  347. {
  348. ValueClass::Additional,
  349. Ui::PanelDetailsType::Text,
  350. "middle_name_native"_q,
  351. tr::lng_passport_middle_name(tr::now),
  352. NativeNameOrEmptyValidate,
  353. DontFormat,
  354. kMaxNameSize,
  355. "first_name_native"_q,
  356. "middle_name"_q,
  357. },
  358. {
  359. ValueClass::Additional,
  360. Ui::PanelDetailsType::Text,
  361. "last_name_native"_q,
  362. tr::lng_passport_last_name(tr::now),
  363. NativeNameValidate,
  364. DontFormat,
  365. kMaxNameSize,
  366. "first_name_native"_q,
  367. "last_name"_q,
  368. },
  369. };
  370. for (auto &row : additional) {
  371. result.rows.push_back(std::move(row));
  372. }
  373. }
  374. return result;
  375. } break;
  376. case Scope::Type::AddressDetails:
  377. case Scope::Type::Address: {
  378. auto result = Scheme();
  379. result.detailsHeader = tr::lng_passport_address(tr::now);
  380. if (scansType) {
  381. switch (*scansType) {
  382. case Value::Type::UtilityBill:
  383. result.scansHeader = tr::lng_passport_address_bill(tr::now);
  384. break;
  385. case Value::Type::BankStatement:
  386. result.scansHeader = tr::lng_passport_address_statement(tr::now);
  387. break;
  388. case Value::Type::RentalAgreement:
  389. result.scansHeader = tr::lng_passport_address_agreement(tr::now);
  390. break;
  391. case Value::Type::PassportRegistration:
  392. result.scansHeader = tr::lng_passport_address_registration(tr::now);
  393. break;
  394. case Value::Type::TemporaryRegistration:
  395. result.scansHeader = tr::lng_passport_address_temporary(tr::now);
  396. break;
  397. default:
  398. Unexpected("scansType in GetDocumentScheme:Address.");
  399. }
  400. }
  401. result.rows = {
  402. {
  403. ValueClass::Fields,
  404. Ui::PanelDetailsType::Text,
  405. "street_line1"_q,
  406. tr::lng_passport_street(tr::now),
  407. StreetValidate,
  408. DontFormat,
  409. kMaxStreetSize,
  410. },
  411. {
  412. ValueClass::Fields,
  413. Ui::PanelDetailsType::Text,
  414. "street_line2"_q,
  415. tr::lng_passport_street(tr::now),
  416. DontValidate,
  417. DontFormat,
  418. kMaxStreetSize,
  419. },
  420. {
  421. ValueClass::Fields,
  422. Ui::PanelDetailsType::Text,
  423. "city"_q,
  424. tr::lng_passport_city(tr::now),
  425. CityValidate,
  426. DontFormat,
  427. kMaxStreetSize,
  428. },
  429. {
  430. ValueClass::Fields,
  431. Ui::PanelDetailsType::Text,
  432. "state"_q,
  433. tr::lng_passport_state(tr::now),
  434. DontValidate,
  435. DontFormat,
  436. kMaxStreetSize,
  437. },
  438. {
  439. ValueClass::Fields,
  440. Ui::PanelDetailsType::Country,
  441. "country_code"_q,
  442. tr::lng_passport_residence_country(tr::now),
  443. CountryValidate,
  444. CountryFormat,
  445. },
  446. {
  447. ValueClass::Fields,
  448. Ui::PanelDetailsType::Postcode,
  449. "post_code"_q,
  450. tr::lng_passport_postcode(tr::now),
  451. PostcodeValidate,
  452. DontFormat,
  453. kMaxPostcodeSize,
  454. },
  455. };
  456. return result;
  457. } break;
  458. }
  459. Unexpected("Type in GetDocumentScheme().");
  460. }
  461. EditContactScheme GetContactScheme(Scope::Type type) {
  462. using Scheme = EditContactScheme;
  463. using ValueType = Scheme::ValueType;
  464. switch (type) {
  465. case Scope::Type::Phone: {
  466. auto result = Scheme(ValueType::Phone);
  467. result.aboutExisting = tr::lng_passport_use_existing_phone(tr::now);
  468. result.newHeader = tr::lng_passport_new_phone(tr::now);
  469. result.aboutNew = tr::lng_passport_new_phone_code(tr::now);
  470. result.validate = [](const QString &value) {
  471. static const auto RegExp = QRegularExpression("^\\d{2,12}$");
  472. return RegExp.match(value).hasMatch();
  473. };
  474. result.format = [](const QString &value) {
  475. return Ui::FormatPhone(value);
  476. };
  477. result.postprocess = [](QString value) {
  478. return value.replace(
  479. TextUtilities::RegExpDigitsExclude(),
  480. QString());
  481. };
  482. return result;
  483. } break;
  484. case Scope::Type::Email: {
  485. auto result = Scheme(ValueType::Text);
  486. result.aboutExisting = tr::lng_passport_use_existing_email(tr::now);
  487. result.newHeader = tr::lng_passport_new_email(tr::now);
  488. result.newPlaceholder = tr::lng_passport_email_title();
  489. result.aboutNew = tr::lng_passport_new_email_code(tr::now);
  490. result.validate = [](const QString &value) {
  491. const auto at = value.indexOf('@');
  492. const auto dot = value.lastIndexOf('.');
  493. return (at > 0) && (dot > at);
  494. };
  495. result.format = result.postprocess = [](const QString &value) {
  496. return value.trimmed();
  497. };
  498. return result;
  499. } break;
  500. }
  501. Unexpected("Type in GetContactScheme().");
  502. }
  503. const std::map<QString, QString> &LatinToNativeMap() {
  504. static const auto result = std::map<QString, QString> {
  505. { "first_name"_q, "first_name_native"_q },
  506. { "last_name"_q, "last_name_native"_q },
  507. { "middle_name"_q, "middle_name_native"_q },
  508. };
  509. return result;
  510. }
  511. const std::map<QString, QString> &NativeToLatinMap() {
  512. static const auto result = std::map<QString, QString> {
  513. { "first_name_native"_q, "first_name"_q },
  514. { "last_name_native"_q, "last_name"_q },
  515. { "middle_name_native"_q, "middle_name"_q },
  516. };
  517. return result;
  518. }
  519. QString AdjustKeyName(not_null<const Value*> value, const QString &key) {
  520. if (!value->nativeNames) {
  521. return key;
  522. }
  523. const auto &map = LatinToNativeMap();
  524. const auto i = map.find(key);
  525. return (i == end(map)) ? key : i->second;
  526. }
  527. bool SkipFieldCheck(not_null<const Value*> value, const QString &key) {
  528. if (value->type != Value::Type::PersonalDetails) {
  529. return false;
  530. }
  531. const auto &dontCheckNames = value->nativeNames
  532. ? LatinToNativeMap()
  533. : NativeToLatinMap();
  534. return dontCheckNames.find(key) != end(dontCheckNames);
  535. }
  536. ScanInfo::ScanInfo(FileType type) : type(type) {
  537. }
  538. ScanInfo::ScanInfo(
  539. FileType type,
  540. const FileKey &key,
  541. const QString &status,
  542. const QImage &thumb,
  543. bool deleted,
  544. const QString &error)
  545. : type(type)
  546. , key(key)
  547. , status(status)
  548. , thumb(thumb)
  549. , deleted(deleted)
  550. , error(error) {
  551. }
  552. PanelController::PanelController(not_null<FormController*> form)
  553. : _form(form)
  554. , _scopes(ComputeScopes(_form->form())) {
  555. _form->secretReadyEvents(
  556. ) | rpl::start_with_next([=] {
  557. ensurePanelCreated();
  558. _panel->showForm();
  559. }, lifetime());
  560. _form->verificationNeeded(
  561. ) | rpl::start_with_next([=](not_null<const Value*> value) {
  562. processVerificationNeeded(value);
  563. }, lifetime());
  564. _form->verificationUpdate(
  565. ) | rpl::filter([=](not_null<const Value*> field) {
  566. return (field->verification.codeLength == 0);
  567. }) | rpl::start_with_next([=](not_null<const Value*> field) {
  568. _verificationBoxes.erase(field);
  569. }, lifetime());
  570. }
  571. not_null<UserData*> PanelController::bot() const {
  572. return _form->bot();
  573. }
  574. QString PanelController::privacyPolicyUrl() const {
  575. return _form->privacyPolicyUrl();
  576. }
  577. void PanelController::fillRows(
  578. Fn<void(
  579. QString title,
  580. QString description,
  581. bool ready,
  582. bool error)> callback) {
  583. if (_scopes.empty()) {
  584. _scopes = ComputeScopes(_form->form());
  585. }
  586. for (const auto &scope : _scopes) {
  587. const auto row = ComputeScopeRow(scope);
  588. const auto main = scope.details
  589. ? not_null<const Value*>(scope.details)
  590. : scope.documents[0];
  591. if (main && !row.ready.isEmpty()) {
  592. _submitErrors.erase(
  593. ranges::remove(_submitErrors, main),
  594. _submitErrors.end());
  595. }
  596. const auto submitError = base::contains(_submitErrors, main);
  597. callback(
  598. row.title,
  599. (!row.error.isEmpty()
  600. ? row.error
  601. : !row.ready.isEmpty()
  602. ? row.ready
  603. : row.description),
  604. !row.ready.isEmpty(),
  605. !row.error.isEmpty() || submitError);
  606. }
  607. }
  608. rpl::producer<> PanelController::refillRows() const {
  609. return rpl::merge(
  610. _submitFailed.events(),
  611. _form->valueSaveFinished() | rpl::to_empty);
  612. }
  613. void PanelController::submitForm() {
  614. _submitErrors = _form->submitGetErrors();
  615. if (!_submitErrors.empty()) {
  616. _submitFailed.fire({});
  617. }
  618. }
  619. void PanelController::submitPassword(const QByteArray &password) {
  620. _form->submitPassword(password);
  621. }
  622. void PanelController::recoverPassword() {
  623. _form->recoverPassword();
  624. }
  625. rpl::producer<QString> PanelController::passwordError() const {
  626. return _form->passwordError();
  627. }
  628. QString PanelController::passwordHint() const {
  629. return _form->passwordSettings().hint;
  630. }
  631. QString PanelController::unconfirmedEmailPattern() const {
  632. return _form->passwordSettings().unconfirmedPattern;
  633. }
  634. QString PanelController::defaultEmail() const {
  635. return _form->defaultEmail();
  636. }
  637. QString PanelController::defaultPhoneNumber() const {
  638. return _form->defaultPhoneNumber();
  639. }
  640. void PanelController::setupPassword() {
  641. Expects(_panel != nullptr);
  642. const auto &settings = _form->passwordSettings();
  643. if (settings.unknownAlgo
  644. || v::is_null(settings.newAlgo)
  645. || v::is_null(settings.newSecureAlgo)) {
  646. showUpdateAppBox();
  647. return;
  648. } else if (settings.request) {
  649. showAskPassword();
  650. return;
  651. }
  652. auto fields = PasscodeBox::CloudFields{
  653. .mtp = PasscodeBox::CloudFields::Mtp{
  654. .newAlgo = settings.newAlgo,
  655. .newSecureSecretAlgo = settings.newSecureAlgo,
  656. },
  657. .hasRecovery = settings.hasRecovery,
  658. .pendingResetDate = settings.pendingResetDate,
  659. };
  660. // MSVC x64 (non-LTO) Release build fails with a linker error:
  661. // - unresolved external variant::variant(variant const &)
  662. // It looks like a MSVC bug and this works like a workaround.
  663. const auto force = fields.mtp.newSecureSecretAlgo;
  664. auto box = show(Box<PasscodeBox>(&_form->window()->session(), fields));
  665. box->newPasswordSet(
  666. ) | rpl::start_with_next([=](const QByteArray &password) {
  667. if (password.isEmpty()) {
  668. _form->reloadPassword();
  669. } else {
  670. _form->reloadAndSubmitPassword(password);
  671. }
  672. }, box->lifetime());
  673. box->passwordReloadNeeded(
  674. ) | rpl::start_with_next([=] {
  675. _form->reloadPassword();
  676. }, box->lifetime());
  677. box->clearUnconfirmedPassword(
  678. ) | rpl::start_with_next([=] {
  679. _form->cancelPassword();
  680. }, box->lifetime());
  681. }
  682. void PanelController::cancelPasswordSubmit() {
  683. show(Ui::MakeConfirmBox({
  684. .text = tr::lng_passport_stop_password_sure(),
  685. .confirmed = [=](Fn<void()> &&close) {
  686. close();
  687. _form->cancelPassword();
  688. },
  689. .confirmText = tr::lng_passport_stop(),
  690. }));
  691. }
  692. void PanelController::validateRecoveryEmail() {
  693. auto validation = ConfirmRecoveryEmail(
  694. &_form->session(),
  695. unconfirmedEmailPattern());
  696. std::move(
  697. validation.reloadRequests
  698. ) | rpl::start_with_next([=] {
  699. _form->reloadPassword();
  700. }, validation.box->lifetime());
  701. std::move(
  702. validation.cancelRequests
  703. ) | rpl::start_with_next([=] {
  704. _form->cancelPassword();
  705. }, validation.box->lifetime());
  706. show(std::move(validation.box));
  707. }
  708. bool PanelController::canAddScan(FileType type) const {
  709. Expects(_editScope != nullptr);
  710. Expects(_editDocument != nullptr);
  711. return _form->canAddScan(_editDocument, type);
  712. }
  713. void PanelController::uploadScan(FileType type, QByteArray &&content) {
  714. Expects(_editScope != nullptr);
  715. Expects(_editDocument != nullptr);
  716. Expects(_editDocument->requiresScan(type));
  717. _form->uploadScan(_editDocument, type, std::move(content));
  718. }
  719. void PanelController::deleteScan(
  720. FileType type,
  721. std::optional<int> fileIndex) {
  722. Expects(_editScope != nullptr);
  723. Expects(_editDocument != nullptr);
  724. Expects(_editDocument->requiresScan(type));
  725. _form->deleteScan(_editDocument, type, fileIndex);
  726. }
  727. void PanelController::restoreScan(
  728. FileType type,
  729. std::optional<int> fileIndex) {
  730. Expects(_editScope != nullptr);
  731. Expects(_editDocument != nullptr);
  732. Expects(_editDocument->requiresScan(type));
  733. _form->restoreScan(_editDocument, type, fileIndex);
  734. }
  735. rpl::producer<ScanInfo> PanelController::scanUpdated() const {
  736. return _form->scanUpdated(
  737. ) | rpl::filter([=](not_null<const EditFile*> file) {
  738. return (file->value == _editDocument);
  739. }) | rpl::map([](not_null<const EditFile*> file) {
  740. return CollectScanInfo(*file);
  741. });
  742. }
  743. rpl::producer<ScopeError> PanelController::saveErrors() const {
  744. return _saveErrors.events();
  745. }
  746. std::vector<ScopeError> PanelController::collectSaveErrors(
  747. not_null<const Value*> value) const {
  748. auto result = std::vector<ScopeError>();
  749. for (const auto &[key, value] : value->data.parsedInEdit.fields) {
  750. if (!value.error.isEmpty()) {
  751. result.push_back({ key, value.error });
  752. }
  753. }
  754. return result;
  755. }
  756. auto PanelController::deleteValueLabel() const
  757. -> std::optional<rpl::producer<QString>> {
  758. Expects(_editScope != nullptr);
  759. if (hasValueDocument()) {
  760. return tr::lng_passport_delete_document();
  761. } else if (!hasValueFields()) {
  762. return std::nullopt;
  763. }
  764. switch (_editScope->type) {
  765. case Scope::Type::PersonalDetails:
  766. case Scope::Type::Identity:
  767. return tr::lng_passport_delete_details();
  768. case Scope::Type::AddressDetails:
  769. case Scope::Type::Address:
  770. return tr::lng_passport_delete_address();
  771. case Scope::Type::Email:
  772. return tr::lng_passport_delete_email();
  773. case Scope::Type::Phone:
  774. return tr::lng_passport_delete_phone();
  775. }
  776. Unexpected("Type in PanelController::deleteValueLabel.");
  777. }
  778. bool PanelController::hasValueDocument() const {
  779. Expects(_editScope != nullptr);
  780. if (!_editDocument) {
  781. return false;
  782. }
  783. return !_editDocument->data.parsed.fields.empty()
  784. || !_editDocument->files(FileType::Scan).empty()
  785. || !_editDocument->files(FileType::Translation).empty()
  786. || !_editDocument->specialScans.empty();
  787. }
  788. bool PanelController::hasValueFields() const {
  789. return _editValue && !_editValue->data.parsed.fields.empty();
  790. }
  791. void PanelController::deleteValue() {
  792. Expects(_editScope != nullptr);
  793. Expects(hasValueDocument() || hasValueFields());
  794. if (savingScope()) {
  795. return;
  796. }
  797. const auto text = [&] {
  798. switch (_editScope->type) {
  799. case Scope::Type::PersonalDetails:
  800. return tr::lng_passport_delete_details_sure(tr::now);
  801. case Scope::Type::Identity:
  802. return tr::lng_passport_delete_document_sure(tr::now);
  803. case Scope::Type::AddressDetails:
  804. return tr::lng_passport_delete_address_sure(tr::now);
  805. case Scope::Type::Address:
  806. return tr::lng_passport_delete_document_sure(tr::now);
  807. case Scope::Type::Phone:
  808. return tr::lng_passport_delete_phone_sure(tr::now);
  809. case Scope::Type::Email:
  810. return tr::lng_passport_delete_email_sure(tr::now);
  811. }
  812. Unexpected("Type in deleteValue.");
  813. }();
  814. const auto checkbox = (hasValueDocument() && hasValueFields()) ? [&] {
  815. switch (_editScope->type) {
  816. case Scope::Type::Identity:
  817. return tr::lng_passport_delete_details(tr::now);
  818. case Scope::Type::Address:
  819. return tr::lng_passport_delete_address(tr::now);
  820. }
  821. Unexpected("Type in deleteValue.");
  822. }() : QString();
  823. _editScopeBoxes.emplace_back(show(ConfirmDeleteDocument(
  824. [=](bool withDetails) { deleteValueSure(withDetails); },
  825. text,
  826. checkbox)));
  827. }
  828. void PanelController::deleteValueSure(bool withDetails) {
  829. Expects(!withDetails || _editValue != nullptr);
  830. if (hasValueDocument()) {
  831. _form->deleteValueEdit(_editDocument);
  832. }
  833. if (withDetails || !hasValueDocument()) {
  834. _form->deleteValueEdit(_editValue);
  835. }
  836. }
  837. void PanelController::suggestReset(Fn<void()> callback) {
  838. _resetBox = Ui::BoxPointer(show(Ui::MakeConfirmBox({
  839. .text = Lang::Hard::PassportCorrupted(),
  840. .confirmed = [=] { resetPassport(callback); },
  841. .cancelled = [=] { cancelReset(); },
  842. .confirmText = Lang::Hard::PassportCorruptedReset(),
  843. })).data());
  844. }
  845. void PanelController::resetPassport(Fn<void()> callback) {
  846. const auto box = show(Ui::MakeConfirmBox({
  847. .text = Lang::Hard::PassportCorruptedResetSure(),
  848. .confirmed = [=] { base::take(_resetBox); callback(); },
  849. .cancelled = [=] { suggestReset(callback); },
  850. .confirmText = Lang::Hard::PassportCorruptedReset(),
  851. .confirmStyle = &st::attentionBoxButton,
  852. }));
  853. _resetBox = Ui::BoxPointer(box.data());
  854. }
  855. void PanelController::cancelReset() {
  856. const auto weak = base::take(_resetBox);
  857. _form->cancelSure();
  858. }
  859. QString PanelController::getDefaultContactValue(Scope::Type type) const {
  860. switch (type) {
  861. case Scope::Type::Phone:
  862. return _form->defaultPhoneNumber();
  863. case Scope::Type::Email:
  864. return _form->defaultEmail();
  865. }
  866. Unexpected("Type in PanelController::getDefaultContactValue().");
  867. }
  868. void PanelController::showAskPassword() {
  869. ensurePanelCreated();
  870. _panel->showAskPassword();
  871. }
  872. void PanelController::showNoPassword() {
  873. ensurePanelCreated();
  874. _panel->showNoPassword();
  875. }
  876. void PanelController::showCriticalError(const QString &error) {
  877. ensurePanelCreated();
  878. _panel->showCriticalError(error);
  879. }
  880. void PanelController::showUpdateAppBox() {
  881. ensurePanelCreated();
  882. const auto callback = [=] {
  883. _form->cancelSure();
  884. Core::UpdateApplication();
  885. };
  886. show(
  887. Ui::MakeConfirmBox({
  888. .text = tr::lng_passport_app_out_of_date(),
  889. .confirmed = callback,
  890. .cancelled = [=] { _form->cancelSure(); },
  891. .confirmText = tr::lng_menu_update(),
  892. }),
  893. Ui::LayerOption::KeepOther,
  894. anim::type::instant);
  895. }
  896. void PanelController::ensurePanelCreated() {
  897. if (!_panel) {
  898. _panel = std::make_unique<Panel>(this);
  899. }
  900. }
  901. std::optional<int> PanelController::findBestDocumentIndex(
  902. const Scope &scope) const {
  903. Expects(!scope.documents.empty());
  904. const auto &documents = scope.documents;
  905. const auto i = ranges::min_element(
  906. documents,
  907. std::less<>(),
  908. [](not_null<const Value*> document) {
  909. return document->whatNotFilled();
  910. });
  911. return ((*i)->whatNotFilled() == Value::kNothingFilled)
  912. ? std::nullopt
  913. : base::make_optional(int(i - begin(documents)));
  914. return -1;
  915. }
  916. void PanelController::editScope(int index) {
  917. Expects(_panel != nullptr);
  918. Expects(index >= 0 && index < _scopes.size());
  919. const auto &scope = _scopes[index];
  920. if (scope.documents.empty()) {
  921. editScope(index, std::nullopt);
  922. } else {
  923. const auto documentIndex = findBestDocumentIndex(scope);
  924. if (documentIndex || scope.documents.size() == 1) {
  925. editScope(index, documentIndex ? *documentIndex : 0);
  926. } else {
  927. requestScopeFilesType(index);
  928. }
  929. }
  930. }
  931. void PanelController::requestScopeFilesType(int index) {
  932. Expects(_panel != nullptr);
  933. Expects(index >= 0 && index < _scopes.size());
  934. const auto type = _scopes[index].type;
  935. _scopeDocumentTypeBox = [&] {
  936. if (type == Scope::Type::Identity) {
  937. return show(RequestIdentityType(
  938. [=](int documentIndex) {
  939. editWithUpload(index, documentIndex);
  940. },
  941. ranges::views::all(
  942. _scopes[index].documents
  943. ) | ranges::views::transform([](auto value) {
  944. return value->type;
  945. }) | ranges::views::transform([](Value::Type type) {
  946. switch (type) {
  947. case Value::Type::Passport:
  948. return tr::lng_passport_identity_passport(tr::now);
  949. case Value::Type::IdentityCard:
  950. return tr::lng_passport_identity_card(tr::now);
  951. case Value::Type::DriverLicense:
  952. return tr::lng_passport_identity_license(tr::now);
  953. case Value::Type::InternalPassport:
  954. return tr::lng_passport_identity_internal(tr::now);
  955. default:
  956. Unexpected("IdentityType in requestScopeFilesType");
  957. }
  958. }) | ranges::to_vector));
  959. } else if (type == Scope::Type::Address) {
  960. return show(RequestAddressType(
  961. [=](int documentIndex) {
  962. editWithUpload(index, documentIndex);
  963. },
  964. ranges::views::all(
  965. _scopes[index].documents
  966. ) | ranges::views::transform([](auto value) {
  967. return value->type;
  968. }) | ranges::views::transform([](Value::Type type) {
  969. switch (type) {
  970. case Value::Type::UtilityBill:
  971. return tr::lng_passport_address_bill(tr::now);
  972. case Value::Type::BankStatement:
  973. return tr::lng_passport_address_statement(tr::now);
  974. case Value::Type::RentalAgreement:
  975. return tr::lng_passport_address_agreement(tr::now);
  976. case Value::Type::PassportRegistration:
  977. return tr::lng_passport_address_registration(tr::now);
  978. case Value::Type::TemporaryRegistration:
  979. return tr::lng_passport_address_temporary(tr::now);
  980. default:
  981. Unexpected("AddressType in requestScopeFilesType");
  982. }
  983. }) | ranges::to_vector));
  984. } else {
  985. Unexpected("Type in processVerificationNeeded.");
  986. }
  987. }();
  988. }
  989. void PanelController::editWithUpload(int index, int documentIndex) {
  990. Expects(_panel != nullptr);
  991. Expects(index >= 0 && index < _scopes.size());
  992. Expects(documentIndex >= 0
  993. && documentIndex < _scopes[index].documents.size());
  994. const auto document = _scopes[index].documents[documentIndex];
  995. const auto type = document->requiresSpecialScan(FileType::FrontSide)
  996. ? FileType::FrontSide
  997. : FileType::Scan;
  998. const auto widget = _panel->widget();
  999. EditScans::ChooseScan(widget.get(), type, [=](QByteArray &&content) {
  1000. if (_scopeDocumentTypeBox) {
  1001. _scopeDocumentTypeBox = Ui::BoxPointer();
  1002. }
  1003. if (!_editScope || !_editDocument) {
  1004. startScopeEdit(index, documentIndex);
  1005. }
  1006. uploadScan(type, std::move(content));
  1007. }, [=](ReadScanError error) {
  1008. readScanError(error);
  1009. });
  1010. }
  1011. void PanelController::readScanError(ReadScanError error) {
  1012. show(Ui::MakeInformBox([&]() -> rpl::producer<QString> {
  1013. switch (error) {
  1014. case ReadScanError::FileTooLarge:
  1015. return tr::lng_passport_error_too_large();
  1016. case ReadScanError::BadImageSize:
  1017. return tr::lng_passport_error_bad_size();
  1018. case ReadScanError::CantReadImage:
  1019. return tr::lng_passport_error_cant_read();
  1020. case ReadScanError::Unknown:
  1021. return rpl::single(Lang::Hard::UnknownSecureScanError());
  1022. }
  1023. Unexpected("Error type in PanelController::readScanError.");
  1024. }()));
  1025. }
  1026. bool PanelController::editRequiresScanUpload(
  1027. int index,
  1028. std::optional<int> documentIndex) const {
  1029. Expects(index >= 0 && index < _scopes.size());
  1030. Expects(!documentIndex
  1031. || (*documentIndex >= 0
  1032. && *documentIndex < _scopes[index].documents.size()));
  1033. if (!documentIndex) {
  1034. return false;
  1035. }
  1036. const auto document = _scopes[index].documents[*documentIndex];
  1037. if (document->requiresSpecialScan(FileType::FrontSide)) {
  1038. const auto &scans = document->specialScans;
  1039. return (scans.find(FileType::FrontSide) == end(scans));
  1040. }
  1041. return document->files(FileType::Scan).empty();
  1042. }
  1043. void PanelController::editScope(
  1044. int index,
  1045. std::optional<int> documentIndex) {
  1046. if (editRequiresScanUpload(index, documentIndex)) {
  1047. editWithUpload(index, *documentIndex);
  1048. } else {
  1049. startScopeEdit(index, documentIndex);
  1050. }
  1051. }
  1052. void PanelController::startScopeEdit(
  1053. int index,
  1054. std::optional<int> documentIndex) {
  1055. Expects(_panel != nullptr);
  1056. Expects(index >= 0 && index < _scopes.size());
  1057. Expects(_scopes[index].details != 0 || documentIndex.has_value());
  1058. Expects(!documentIndex.has_value()
  1059. || (*documentIndex >= 0
  1060. && *documentIndex < _scopes[index].documents.size()));
  1061. _editScope = &_scopes[index];
  1062. _editValue = _editScope->details;
  1063. _editDocument = documentIndex
  1064. ? _scopes[index].documents[*documentIndex].get()
  1065. : nullptr;
  1066. if (_editValue) {
  1067. _form->startValueEdit(_editValue);
  1068. }
  1069. if (_editDocument) {
  1070. _form->startValueEdit(_editDocument);
  1071. }
  1072. auto preferredLanguage = [=](const QString &countryCode) {
  1073. return _form->preferredLanguage(countryCode);
  1074. };
  1075. auto content = [&]() -> object_ptr<Ui::RpWidget> {
  1076. switch (_editScope->type) {
  1077. case Scope::Type::Identity:
  1078. case Scope::Type::Address: {
  1079. Assert(_editDocument != nullptr);
  1080. auto scans = PrepareScanListData(
  1081. *_editDocument,
  1082. FileType::Scan);
  1083. auto translations = _editDocument->translationRequired
  1084. ? base::make_optional(PrepareScanListData(
  1085. *_editDocument,
  1086. FileType::Translation))
  1087. : std::nullopt;
  1088. auto result = _editValue
  1089. ? object_ptr<PanelEditDocument>(
  1090. _panel->widget(),
  1091. this,
  1092. GetDocumentScheme(
  1093. _editScope->type,
  1094. _editDocument->type,
  1095. _editValue->nativeNames,
  1096. std::move(preferredLanguage)),
  1097. _editValue->error,
  1098. _editValue->data.parsedInEdit,
  1099. _editDocument->error,
  1100. _editDocument->data.parsedInEdit,
  1101. std::move(scans),
  1102. std::move(translations),
  1103. PrepareSpecialFiles(*_editDocument))
  1104. : object_ptr<PanelEditDocument>(
  1105. _panel->widget(),
  1106. this,
  1107. GetDocumentScheme(
  1108. _editScope->type,
  1109. _editDocument->type,
  1110. false,
  1111. std::move(preferredLanguage)),
  1112. _editDocument->error,
  1113. _editDocument->data.parsedInEdit,
  1114. std::move(scans),
  1115. std::move(translations),
  1116. PrepareSpecialFiles(*_editDocument));
  1117. const auto weak = Ui::MakeWeak(result.data());
  1118. _panelHasUnsavedChanges = [=] {
  1119. return weak ? weak->hasUnsavedChanges() : false;
  1120. };
  1121. return result;
  1122. } break;
  1123. case Scope::Type::PersonalDetails:
  1124. case Scope::Type::AddressDetails: {
  1125. Assert(_editValue != nullptr);
  1126. auto result = object_ptr<PanelEditDocument>(
  1127. _panel->widget(),
  1128. this,
  1129. GetDocumentScheme(
  1130. _editScope->type,
  1131. std::nullopt,
  1132. _editValue->nativeNames,
  1133. std::move(preferredLanguage)),
  1134. _editValue->error,
  1135. _editValue->data.parsedInEdit);
  1136. const auto weak = Ui::MakeWeak(result.data());
  1137. _panelHasUnsavedChanges = [=] {
  1138. return weak ? weak->hasUnsavedChanges() : false;
  1139. };
  1140. return result;
  1141. } break;
  1142. case Scope::Type::Phone:
  1143. case Scope::Type::Email: {
  1144. Assert(_editValue != nullptr);
  1145. const auto &parsed = _editValue->data.parsedInEdit;
  1146. const auto valueIt = parsed.fields.find("value");
  1147. const auto value = (valueIt == end(parsed.fields)
  1148. ? QString()
  1149. : valueIt->second.text);
  1150. const auto existing = getDefaultContactValue(_editScope->type);
  1151. _panelHasUnsavedChanges = nullptr;
  1152. return object_ptr<PanelEditContact>(
  1153. _panel->widget(),
  1154. this,
  1155. GetContactScheme(_editScope->type),
  1156. value,
  1157. (existing.toLower().trimmed() != value.toLower().trimmed()
  1158. ? existing
  1159. : QString()));
  1160. } break;
  1161. }
  1162. Unexpected("Type in PanelController::editScope().");
  1163. }();
  1164. content->lifetime().add([=] {
  1165. cancelValueEdit();
  1166. });
  1167. _panel->setBackAllowed(true);
  1168. _panel->backRequests(
  1169. ) | rpl::start_with_next([=] {
  1170. cancelEditScope();
  1171. }, content->lifetime());
  1172. _form->valueSaveFinished(
  1173. ) | rpl::start_with_next([=](not_null<const Value*> value) {
  1174. processValueSaveFinished(value);
  1175. }, content->lifetime());
  1176. _panel->showEditValue(std::move(content));
  1177. }
  1178. void PanelController::processValueSaveFinished(
  1179. not_null<const Value*> value) {
  1180. Expects(_editScope != nullptr);
  1181. const auto boxIt = _verificationBoxes.find(value);
  1182. if (boxIt != end(_verificationBoxes)) {
  1183. const auto saved = std::move(boxIt->second);
  1184. _verificationBoxes.erase(boxIt);
  1185. }
  1186. if ((_editValue == value || _editDocument == value) && !savingScope()) {
  1187. if (auto errors = collectSaveErrors(value); !errors.empty()) {
  1188. for (auto &&error : errors) {
  1189. _saveErrors.fire(std::move(error));
  1190. }
  1191. } else {
  1192. _panel->showForm();
  1193. }
  1194. }
  1195. }
  1196. bool PanelController::uploadingScopeScan() const {
  1197. return (_editValue && _editValue->uploadingScan())
  1198. || (_editDocument && _editDocument->uploadingScan());
  1199. }
  1200. bool PanelController::savingScope() const {
  1201. return (_editValue && _editValue->saving())
  1202. || (_editDocument && _editDocument->saving());
  1203. }
  1204. void PanelController::processVerificationNeeded(
  1205. not_null<const Value*> value) {
  1206. const auto i = _verificationBoxes.find(value);
  1207. if (i != _verificationBoxes.end()) {
  1208. LOG(("API Error: Requesting for verification repeatedly."));
  1209. return;
  1210. }
  1211. const auto textIt = value->data.parsedInEdit.fields.find("value");
  1212. Assert(textIt != end(value->data.parsedInEdit.fields));
  1213. const auto text = textIt->second.text;
  1214. const auto type = value->type;
  1215. const auto update = _form->verificationUpdate(
  1216. ) | rpl::filter([=](not_null<const Value*> field) {
  1217. return (field == value);
  1218. });
  1219. const auto box = [&] {
  1220. if (type == Value::Type::Phone) {
  1221. const auto submit = [=](const QString &code) {
  1222. _form->verify(value, code);
  1223. };
  1224. const auto account = &_form->window()->session().account();
  1225. account->setHandleLoginCode(submit);
  1226. const auto box = show(VerifyPhoneBox(
  1227. text,
  1228. value->verification.codeLength,
  1229. value->verification.fragmentUrl,
  1230. submit,
  1231. value->verification.call ? rpl::single(
  1232. value->verification.call->getText()
  1233. ) | rpl::then(rpl::duplicate(
  1234. update
  1235. ) | rpl::filter([=](not_null<const Value*> field) {
  1236. return field->verification.call != nullptr;
  1237. }) | rpl::map([=](not_null<const Value*> field) {
  1238. return field->verification.call->getText();
  1239. })) : (rpl::single(QString()) | rpl::type_erased()),
  1240. rpl::duplicate(
  1241. update
  1242. ) | rpl::map([=](not_null<const Value*> field) {
  1243. return field->verification.error;
  1244. }) | rpl::distinct_until_changed()));
  1245. box->boxClosing(
  1246. ) | rpl::start_with_next([=] {
  1247. account->setHandleLoginCode(nullptr);
  1248. }, box->lifetime());
  1249. return box;
  1250. } else if (type == Value::Type::Email) {
  1251. return show(VerifyEmailBox(
  1252. text,
  1253. value->verification.codeLength,
  1254. [=](const QString &code) { _form->verify(value, code); },
  1255. nullptr, // resend
  1256. rpl::duplicate(
  1257. update
  1258. ) | rpl::map([=](not_null<const Value*> field) {
  1259. return field->verification.error;
  1260. }) | rpl::distinct_until_changed(),
  1261. nullptr));
  1262. } else {
  1263. Unexpected("Type in processVerificationNeeded.");
  1264. }
  1265. }();
  1266. box->boxClosing(
  1267. ) | rpl::start_with_next([=] {
  1268. _form->cancelValueVerification(value);
  1269. }, lifetime());
  1270. _verificationBoxes.emplace(value, box);
  1271. }
  1272. void PanelController::cancelValueEdit() {
  1273. Expects(_editScope != nullptr);
  1274. _editScopeBoxes.clear();
  1275. if (const auto value = base::take(_editValue)) {
  1276. _form->cancelValueEdit(value);
  1277. }
  1278. if (const auto document = base::take(_editDocument)) {
  1279. _form->cancelValueEdit(document);
  1280. }
  1281. _editScope = nullptr;
  1282. }
  1283. void PanelController::saveScope(ValueMap &&data, ValueMap &&filesData) {
  1284. Expects(_panel != nullptr);
  1285. if (uploadingScopeScan()) {
  1286. showToast(tr::lng_passport_wait_upload(tr::now));
  1287. return;
  1288. } else if (savingScope()) {
  1289. return;
  1290. }
  1291. if (_editValue) {
  1292. _form->saveValueEdit(_editValue, std::move(data));
  1293. } else {
  1294. Assert(data.fields.empty());
  1295. }
  1296. if (_editDocument) {
  1297. _form->saveValueEdit(_editDocument, std::move(filesData));
  1298. } else {
  1299. Assert(filesData.fields.empty());
  1300. }
  1301. }
  1302. bool PanelController::editScopeChanged(
  1303. const ValueMap &data,
  1304. const ValueMap &filesData) const {
  1305. if (_editValue && ValueChanged(_editValue, data)) {
  1306. return true;
  1307. } else if (_editDocument && ValueChanged(_editDocument, filesData)) {
  1308. return true;
  1309. }
  1310. return false;
  1311. }
  1312. void PanelController::cancelEditScope() {
  1313. Expects(_editScope != nullptr);
  1314. if (_panelHasUnsavedChanges && _panelHasUnsavedChanges()) {
  1315. if (!_confirmForgetChangesBox) {
  1316. _confirmForgetChangesBox = show(Ui::MakeConfirmBox({
  1317. .text = tr::lng_passport_sure_cancel(),
  1318. .confirmed = [=] { _panel->showForm(); },
  1319. .confirmText = tr::lng_continue(),
  1320. }));
  1321. _editScopeBoxes.emplace_back(_confirmForgetChangesBox);
  1322. }
  1323. } else {
  1324. _panel->showForm();
  1325. }
  1326. }
  1327. int PanelController::closeGetDuration() {
  1328. if (_panel) {
  1329. return _panel->hideAndDestroyGetDuration();
  1330. }
  1331. return 0;
  1332. }
  1333. void PanelController::cancelAuth() {
  1334. _form->cancel();
  1335. }
  1336. void PanelController::cancelAuthSure() {
  1337. _form->cancelSure();
  1338. }
  1339. void PanelController::showBox(
  1340. object_ptr<Ui::BoxContent> box,
  1341. Ui::LayerOptions options,
  1342. anim::type animated) {
  1343. _panel->showBox(std::move(box), options, animated);
  1344. }
  1345. void PanelController::showToast(const QString &text) {
  1346. _panel->showToast(text);
  1347. }
  1348. rpl::lifetime &PanelController::lifetime() {
  1349. return _lifetime;
  1350. }
  1351. PanelController::~PanelController() = default;
  1352. } // namespace Passport