settings_local_passcode.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660
  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 "settings/settings_local_passcode.h"
  8. #include "base/platform/base_platform_last_input.h"
  9. #include "base/platform/base_platform_info.h"
  10. #include "base/system_unlock.h"
  11. #include "boxes/auto_lock_box.h"
  12. #include "core/application.h"
  13. #include "core/core_settings.h"
  14. #include "lang/lang_keys.h"
  15. #include "lottie/lottie_icon.h"
  16. #include "main/main_domain.h"
  17. #include "main/main_session.h"
  18. #include "settings/cloud_password/settings_cloud_password_common.h"
  19. #include "settings/cloud_password/settings_cloud_password_step.h"
  20. #include "storage/storage_domain.h"
  21. #include "ui/vertical_list.h"
  22. #include "ui/boxes/confirm_box.h"
  23. #include "ui/widgets/buttons.h"
  24. #include "ui/widgets/fields/password_input.h"
  25. #include "ui/widgets/labels.h"
  26. #include "ui/wrap/vertical_layout.h"
  27. #include "ui/wrap/slide_wrap.h"
  28. #include "window/window_session_controller.h"
  29. #include "styles/style_boxes.h"
  30. #include "styles/style_layers.h"
  31. #include "styles/style_menu_icons.h"
  32. #include "styles/style_settings.h"
  33. namespace Settings {
  34. namespace {
  35. void SetPasscode(
  36. not_null<Window::SessionController*> controller,
  37. const QString &pass) {
  38. cSetPasscodeBadTries(0);
  39. controller->session().domain().local().setPasscode(pass.toUtf8());
  40. Core::App().localPasscodeChanged();
  41. }
  42. } // namespace
  43. namespace details {
  44. class LocalPasscodeEnter : public AbstractSection {
  45. public:
  46. enum class EnterType {
  47. Create,
  48. Check,
  49. Change,
  50. };
  51. LocalPasscodeEnter(
  52. QWidget *parent,
  53. not_null<Window::SessionController*> controller);
  54. ~LocalPasscodeEnter();
  55. void showFinished() override;
  56. void setInnerFocus() override;
  57. [[nodiscard]] rpl::producer<Type> sectionShowOther() override;
  58. [[nodiscard]] rpl::producer<> sectionShowBack() override;
  59. [[nodiscard]] rpl::producer<QString> title() override;
  60. protected:
  61. void setupContent();
  62. [[nodiscard]] virtual EnterType enterType() const = 0;
  63. private:
  64. const not_null<Window::SessionController*> _controller;
  65. rpl::event_stream<> _showFinished;
  66. rpl::event_stream<> _setInnerFocus;
  67. rpl::event_stream<Type> _showOther;
  68. rpl::event_stream<> _showBack;
  69. bool _systemUnlockWithBiometric = false;
  70. };
  71. LocalPasscodeEnter::LocalPasscodeEnter(
  72. QWidget *parent,
  73. not_null<Window::SessionController*> controller)
  74. : AbstractSection(parent)
  75. , _controller(controller) {
  76. }
  77. rpl::producer<QString> LocalPasscodeEnter::title() {
  78. return tr::lng_settings_passcode_title();
  79. }
  80. void LocalPasscodeEnter::setupContent() {
  81. const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
  82. base::SystemUnlockStatus(
  83. true
  84. ) | rpl::start_with_next([=](base::SystemUnlockAvailability status) {
  85. _systemUnlockWithBiometric = status.available
  86. && status.withBiometrics;
  87. }, lifetime());
  88. const auto isCreate = (enterType() == EnterType::Create);
  89. const auto isCheck = (enterType() == EnterType::Check);
  90. [[maybe_unused]] const auto isChange = (enterType() == EnterType::Change);
  91. auto icon = CreateLottieIcon(
  92. content,
  93. {
  94. .name = u"local_passcode_enter"_q,
  95. .sizeOverride = {
  96. st::changePhoneIconSize,
  97. st::changePhoneIconSize,
  98. },
  99. },
  100. st::settingLocalPasscodeIconPadding);
  101. content->add(std::move(icon.widget));
  102. _showFinished.events(
  103. ) | rpl::start_with_next([animate = std::move(icon.animate)] {
  104. animate(anim::repeat::once);
  105. }, content->lifetime());
  106. if (isChange) {
  107. CloudPassword::SetupAutoCloseTimer(
  108. content->lifetime(),
  109. [=] { _showBack.fire({}); },
  110. [] { return Core::App().lastNonIdleTime(); });
  111. }
  112. Ui::AddSkip(content);
  113. content->add(
  114. object_ptr<Ui::CenterWrap<>>(
  115. content,
  116. object_ptr<Ui::FlatLabel>(
  117. content,
  118. isCreate
  119. ? tr::lng_passcode_create_title()
  120. : isCheck
  121. ? tr::lng_passcode_check_title()
  122. : tr::lng_passcode_change_title(),
  123. st::changePhoneTitle)),
  124. st::changePhoneTitlePadding);
  125. const auto addDescription = [&](rpl::producer<QString> &&text) {
  126. const auto &st = st::settingLocalPasscodeDescription;
  127. content->add(
  128. object_ptr<Ui::CenterWrap<>>(
  129. content,
  130. object_ptr<Ui::FlatLabel>(content, std::move(text), st)),
  131. st::changePhoneDescriptionPadding);
  132. };
  133. addDescription(tr::lng_passcode_about1());
  134. Ui::AddSkip(content);
  135. addDescription(tr::lng_passcode_about2());
  136. Ui::AddSkip(content, st::settingLocalPasscodeDescriptionBottomSkip);
  137. const auto addField = [&](rpl::producer<QString> &&text) {
  138. const auto &st = st::settingLocalPasscodeInputField;
  139. auto container = object_ptr<Ui::RpWidget>(content);
  140. container->resize(container->width(), st.heightMin);
  141. const auto field = Ui::CreateChild<Ui::PasswordInput>(
  142. container.data(),
  143. st,
  144. std::move(text));
  145. container->geometryValue(
  146. ) | rpl::start_with_next([=](const QRect &r) {
  147. field->moveToLeft((r.width() - field->width()) / 2, 0);
  148. }, container->lifetime());
  149. content->add(std::move(container));
  150. return field;
  151. };
  152. const auto addError = [&](not_null<Ui::PasswordInput*> input) {
  153. const auto error = content->add(
  154. object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
  155. content,
  156. object_ptr<Ui::FlatLabel>(
  157. content,
  158. // Set any text to resize.
  159. tr::lng_language_name(tr::now),
  160. st::settingLocalPasscodeError)),
  161. st::changePhoneDescriptionPadding)->entity();
  162. error->hide();
  163. QObject::connect(input.get(), &Ui::MaskedInputField::changed, [=] {
  164. error->hide();
  165. });
  166. return error;
  167. };
  168. const auto newPasscode = addField(isCreate
  169. ? tr::lng_passcode_enter_first()
  170. : tr::lng_passcode_enter());
  171. const auto reenterPasscode = isCheck
  172. ? (Ui::PasswordInput*)(nullptr)
  173. : addField(tr::lng_passcode_confirm_new());
  174. const auto error = addError(isCheck ? newPasscode : reenterPasscode);
  175. const auto button = content->add(
  176. object_ptr<Ui::CenterWrap<Ui::RoundButton>>(
  177. content,
  178. object_ptr<Ui::RoundButton>(
  179. content,
  180. (isCreate
  181. ? tr::lng_passcode_create_button()
  182. : isCheck
  183. ? tr::lng_passcode_check_button()
  184. : tr::lng_passcode_change_button()),
  185. st::changePhoneButton)),
  186. st::settingLocalPasscodeButtonPadding)->entity();
  187. button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
  188. button->setClickedCallback([=] {
  189. const auto newText = newPasscode->text();
  190. const auto reenterText = reenterPasscode
  191. ? reenterPasscode->text()
  192. : QString();
  193. if (isCreate || isChange) {
  194. if (newText.isEmpty()) {
  195. newPasscode->setFocus();
  196. newPasscode->showError();
  197. } else if (reenterText.isEmpty()) {
  198. reenterPasscode->setFocus();
  199. reenterPasscode->showError();
  200. } else if (newText != reenterText) {
  201. reenterPasscode->setFocus();
  202. reenterPasscode->showError();
  203. reenterPasscode->selectAll();
  204. error->show();
  205. error->setText(tr::lng_passcode_differ(tr::now));
  206. } else {
  207. if (isChange) {
  208. const auto &domain = _controller->session().domain();
  209. if (domain.local().checkPasscode(newText.toUtf8())) {
  210. newPasscode->setFocus();
  211. newPasscode->showError();
  212. newPasscode->selectAll();
  213. error->show();
  214. error->setText(tr::lng_passcode_is_same(tr::now));
  215. return;
  216. }
  217. }
  218. SetPasscode(_controller, newText);
  219. if (isCreate) {
  220. if (Platform::IsWindows() || _systemUnlockWithBiometric) {
  221. Core::App().settings().setSystemUnlockEnabled(true);
  222. Core::App().saveSettingsDelayed();
  223. }
  224. _showOther.fire(LocalPasscodeManageId());
  225. } else if (isChange) {
  226. _showBack.fire({});
  227. }
  228. }
  229. } else if (isCheck) {
  230. if (!passcodeCanTry()) {
  231. newPasscode->setFocus();
  232. newPasscode->showError();
  233. error->show();
  234. error->setText(tr::lng_flood_error(tr::now));
  235. return;
  236. }
  237. const auto &domain = _controller->session().domain();
  238. if (domain.local().checkPasscode(newText.toUtf8())) {
  239. cSetPasscodeBadTries(0);
  240. _showOther.fire(LocalPasscodeManageId());
  241. } else {
  242. cSetPasscodeBadTries(cPasscodeBadTries() + 1);
  243. cSetPasscodeLastTry(crl::now());
  244. newPasscode->selectAll();
  245. newPasscode->setFocus();
  246. newPasscode->showError();
  247. error->show();
  248. error->setText(tr::lng_passcode_wrong(tr::now));
  249. }
  250. }
  251. });
  252. const auto submit = [=] {
  253. if (!reenterPasscode || reenterPasscode->hasFocus()) {
  254. button->clicked({}, Qt::LeftButton);
  255. } else {
  256. reenterPasscode->setFocus();
  257. }
  258. };
  259. connect(newPasscode, &Ui::MaskedInputField::submitted, submit);
  260. if (reenterPasscode) {
  261. connect(reenterPasscode, &Ui::MaskedInputField::submitted, submit);
  262. }
  263. _setInnerFocus.events(
  264. ) | rpl::start_with_next([=] {
  265. if (newPasscode->text().isEmpty()) {
  266. newPasscode->setFocus();
  267. } else if (reenterPasscode && reenterPasscode->text().isEmpty()) {
  268. reenterPasscode->setFocus();
  269. } else {
  270. newPasscode->setFocus();
  271. }
  272. }, content->lifetime());
  273. Ui::ResizeFitChild(this, content);
  274. }
  275. void LocalPasscodeEnter::showFinished() {
  276. _showFinished.fire({});
  277. }
  278. void LocalPasscodeEnter::setInnerFocus() {
  279. _setInnerFocus.fire({});
  280. }
  281. rpl::producer<Type> LocalPasscodeEnter::sectionShowOther() {
  282. return _showOther.events();
  283. }
  284. rpl::producer<> LocalPasscodeEnter::sectionShowBack() {
  285. return _showBack.events();
  286. }
  287. LocalPasscodeEnter::~LocalPasscodeEnter() = default;
  288. } // namespace details
  289. class LocalPasscodeCreate;
  290. class LocalPasscodeCheck;
  291. class LocalPasscodeChange;
  292. template <typename SectionType>
  293. class TypedLocalPasscodeEnter : public details::LocalPasscodeEnter {
  294. public:
  295. TypedLocalPasscodeEnter(
  296. QWidget *parent,
  297. not_null<Window::SessionController*> controller)
  298. : details::LocalPasscodeEnter(parent, controller) {
  299. setupContent();
  300. }
  301. [[nodiscard]] static Type Id() {
  302. return SectionFactory<SectionType>::Instance();
  303. }
  304. [[nodiscard]] Type id() const final override {
  305. return Id();
  306. }
  307. protected:
  308. [[nodiscard]] EnterType enterType() const final override {
  309. if constexpr (std::is_same_v<SectionType, LocalPasscodeCreate>) {
  310. return EnterType::Create;
  311. }
  312. if constexpr (std::is_same_v<SectionType, LocalPasscodeCheck>) {
  313. return EnterType::Check;
  314. }
  315. if constexpr (std::is_same_v<SectionType, LocalPasscodeChange>) {
  316. return EnterType::Change;
  317. }
  318. return EnterType::Create;
  319. }
  320. };
  321. class LocalPasscodeCreate final
  322. : public TypedLocalPasscodeEnter<LocalPasscodeCreate> {
  323. public:
  324. using TypedLocalPasscodeEnter::TypedLocalPasscodeEnter;
  325. };
  326. class LocalPasscodeCheck final
  327. : public TypedLocalPasscodeEnter<LocalPasscodeCheck> {
  328. public:
  329. using TypedLocalPasscodeEnter::TypedLocalPasscodeEnter;
  330. };
  331. class LocalPasscodeChange final
  332. : public TypedLocalPasscodeEnter<LocalPasscodeChange> {
  333. public:
  334. using TypedLocalPasscodeEnter::TypedLocalPasscodeEnter;
  335. };
  336. class LocalPasscodeManage : public Section<LocalPasscodeManage> {
  337. public:
  338. LocalPasscodeManage(
  339. QWidget *parent,
  340. not_null<Window::SessionController*> controller);
  341. ~LocalPasscodeManage();
  342. [[nodiscard]] rpl::producer<QString> title() override;
  343. void showFinished() override;
  344. [[nodiscard]] rpl::producer<> sectionShowBack() override;
  345. [[nodiscard]] rpl::producer<std::vector<Type>> removeFromStack() override;
  346. [[nodiscard]] QPointer<Ui::RpWidget> createPinnedToBottom(
  347. not_null<Ui::RpWidget*> parent) override;
  348. private:
  349. void setupContent();
  350. const not_null<Window::SessionController*> _controller;
  351. rpl::variable<bool> _isBottomFillerShown;
  352. rpl::event_stream<> _showFinished;
  353. rpl::event_stream<> _showBack;
  354. };
  355. LocalPasscodeManage::LocalPasscodeManage(
  356. QWidget *parent,
  357. not_null<Window::SessionController*> controller)
  358. : Section(parent)
  359. , _controller(controller) {
  360. setupContent();
  361. }
  362. rpl::producer<QString> LocalPasscodeManage::title() {
  363. return tr::lng_settings_passcode_title();
  364. }
  365. rpl::producer<std::vector<Type>> LocalPasscodeManage::removeFromStack() {
  366. return rpl::single(std::vector<Type>{
  367. LocalPasscodeManage::Id(),
  368. LocalPasscodeCreate::Id(),
  369. LocalPasscodeCheck::Id(),
  370. LocalPasscodeChange::Id(),
  371. });
  372. }
  373. void LocalPasscodeManage::setupContent() {
  374. const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
  375. struct State {
  376. rpl::event_stream<> autoLockBoxClosing;
  377. };
  378. const auto state = content->lifetime().make_state<State>();
  379. CloudPassword::SetupAutoCloseTimer(
  380. content->lifetime(),
  381. [=] { _showBack.fire({}); },
  382. [] { return Core::App().lastNonIdleTime(); });
  383. Ui::AddSkip(content);
  384. AddButtonWithIcon(
  385. content,
  386. tr::lng_passcode_change(),
  387. st::settingsButton,
  388. { &st::menuIconLock }
  389. )->addClickHandler([=] {
  390. showOther(LocalPasscodeChange::Id());
  391. });
  392. auto autolockLabel = state->autoLockBoxClosing.events_starting_with(
  393. {}
  394. ) | rpl::map([] {
  395. const auto autolock = Core::App().settings().autoLock();
  396. const auto hours = autolock / 3600;
  397. const auto minutes = (autolock - (hours * 3600)) / 60;
  398. return (hours && minutes)
  399. ? tr::lng_passcode_autolock_hours_minutes(
  400. tr::now,
  401. lt_hours_count,
  402. QString::number(hours),
  403. lt_minutes_count,
  404. QString::number(minutes))
  405. : minutes
  406. ? tr::lng_minutes(tr::now, lt_count, minutes)
  407. : tr::lng_hours(tr::now, lt_count, hours);
  408. });
  409. AddButtonWithLabel(
  410. content,
  411. (base::Platform::LastUserInputTimeSupported()
  412. ? tr::lng_passcode_autolock_away
  413. : tr::lng_passcode_autolock_inactive)(),
  414. std::move(autolockLabel),
  415. st::settingsButton,
  416. { &st::menuIconTimer }
  417. )->addClickHandler([=] {
  418. const auto box = _controller->show(Box<AutoLockBox>());
  419. box->boxClosing(
  420. ) | rpl::start_to_stream(state->autoLockBoxClosing, box->lifetime());
  421. });
  422. Ui::AddSkip(content);
  423. using Divider = CloudPassword::OneEdgeBoxContentDivider;
  424. const auto divider = Ui::CreateChild<Divider>(this);
  425. divider->lower();
  426. const auto about = content->add(
  427. object_ptr<Ui::PaddingWrap<>>(
  428. content,
  429. object_ptr<Ui::FlatLabel>(
  430. content,
  431. rpl::combine(
  432. tr::lng_passcode_about1(),
  433. tr::lng_passcode_about3()
  434. ) | rpl::map([](const QString &s1, const QString &s2) {
  435. return s1 + "\n\n" + s2;
  436. }),
  437. st::boxDividerLabel),
  438. st::defaultBoxDividerLabelPadding));
  439. about->geometryValue(
  440. ) | rpl::start_with_next([=](const QRect &r) {
  441. divider->setGeometry(r);
  442. }, divider->lifetime());
  443. _isBottomFillerShown.value(
  444. ) | rpl::start_with_next([=](bool shown) {
  445. divider->skipEdge(Qt::BottomEdge, shown);
  446. }, divider->lifetime());
  447. const auto systemUnlockWrap = content->add(
  448. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  449. content,
  450. object_ptr<Ui::VerticalLayout>(content))
  451. )->setDuration(0);
  452. const auto systemUnlockContent = systemUnlockWrap->entity();
  453. enum class UnlockType {
  454. None,
  455. Default,
  456. Biometrics,
  457. Companion,
  458. };
  459. const auto unlockType = systemUnlockContent->lifetime().make_state<
  460. rpl::variable<UnlockType>
  461. >(base::SystemUnlockStatus(
  462. true
  463. ) | rpl::map([](base::SystemUnlockAvailability status) {
  464. return status.withBiometrics
  465. ? UnlockType::Biometrics
  466. : status.withCompanion
  467. ? UnlockType::Companion
  468. : status.available
  469. ? UnlockType::Default
  470. : UnlockType::None;
  471. }));
  472. unlockType->value(
  473. ) | rpl::start_with_next([=](UnlockType type) {
  474. while (systemUnlockContent->count()) {
  475. delete systemUnlockContent->widgetAt(0);
  476. }
  477. Ui::AddSkip(systemUnlockContent);
  478. AddButtonWithIcon(
  479. systemUnlockContent,
  480. (Platform::IsWindows()
  481. ? tr::lng_settings_use_winhello()
  482. : (type == UnlockType::Biometrics)
  483. ? tr::lng_settings_use_touchid()
  484. : (type == UnlockType::Companion)
  485. ? tr::lng_settings_use_applewatch()
  486. : tr::lng_settings_use_systempwd()),
  487. st::settingsButton,
  488. { Platform::IsWindows()
  489. ? &st::menuIconWinHello
  490. : (type == UnlockType::Biometrics)
  491. ? &st::menuIconTouchID
  492. : (type == UnlockType::Companion)
  493. ? &st::menuIconAppleWatch
  494. : &st::menuIconSystemPwd }
  495. )->toggleOn(
  496. rpl::single(Core::App().settings().systemUnlockEnabled())
  497. )->toggledChanges(
  498. ) | rpl::filter([=](bool value) {
  499. return value != Core::App().settings().systemUnlockEnabled();
  500. }) | rpl::start_with_next([=](bool value) {
  501. Core::App().settings().setSystemUnlockEnabled(value);
  502. Core::App().saveSettingsDelayed();
  503. }, systemUnlockContent->lifetime());
  504. Ui::AddSkip(systemUnlockContent);
  505. Ui::AddDividerText(
  506. systemUnlockContent,
  507. (Platform::IsWindows()
  508. ? tr::lng_settings_use_winhello_about()
  509. : (type == UnlockType::Biometrics)
  510. ? tr::lng_settings_use_touchid_about()
  511. : (type == UnlockType::Companion)
  512. ? tr::lng_settings_use_applewatch_about()
  513. : tr::lng_settings_use_systempwd_about()));
  514. }, systemUnlockContent->lifetime());
  515. systemUnlockWrap->toggleOn(unlockType->value(
  516. ) | rpl::map(rpl::mappers::_1 != UnlockType::None));
  517. Ui::ResizeFitChild(this, content);
  518. }
  519. QPointer<Ui::RpWidget> LocalPasscodeManage::createPinnedToBottom(
  520. not_null<Ui::RpWidget*> parent) {
  521. auto callback = [=] {
  522. _controller->show(
  523. Ui::MakeConfirmBox({
  524. .text = tr::lng_settings_passcode_disable_sure(),
  525. .confirmed = [=](Fn<void()> &&close) {
  526. SetPasscode(_controller, QString());
  527. Core::App().settings().setSystemUnlockEnabled(false);
  528. Core::App().saveSettingsDelayed();
  529. close();
  530. _showBack.fire({});
  531. },
  532. .confirmText = tr::lng_settings_auto_night_disable(),
  533. .confirmStyle = &st::attentionBoxButton,
  534. }));
  535. };
  536. auto bottomButton = CloudPassword::CreateBottomDisableButton(
  537. parent,
  538. geometryValue(),
  539. tr::lng_settings_passcode_disable(),
  540. std::move(callback));
  541. _isBottomFillerShown = base::take(bottomButton.isBottomFillerShown);
  542. return bottomButton.content;
  543. }
  544. void LocalPasscodeManage::showFinished() {
  545. _showFinished.fire({});
  546. }
  547. rpl::producer<> LocalPasscodeManage::sectionShowBack() {
  548. return _showBack.events();
  549. }
  550. LocalPasscodeManage::~LocalPasscodeManage() = default;
  551. Type LocalPasscodeCreateId() {
  552. return LocalPasscodeCreate::Id();
  553. }
  554. Type LocalPasscodeCheckId() {
  555. return LocalPasscodeCheck::Id();
  556. }
  557. Type LocalPasscodeManageId() {
  558. return LocalPasscodeManage::Id();
  559. }
  560. } // namespace Settings