settings_privacy_security.cpp 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128
  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_privacy_security.h"
  8. #include "api/api_authorizations.h"
  9. #include "api/api_cloud_password.h"
  10. #include "api/api_self_destruct.h"
  11. #include "api/api_sensitive_content.h"
  12. #include "api/api_global_privacy.h"
  13. #include "api/api_websites.h"
  14. #include "settings/cloud_password/settings_cloud_password_email_confirm.h"
  15. #include "settings/cloud_password/settings_cloud_password_input.h"
  16. #include "settings/cloud_password/settings_cloud_password_start.h"
  17. #include "settings/settings_active_sessions.h"
  18. #include "settings/settings_blocked_peers.h"
  19. #include "settings/settings_global_ttl.h"
  20. #include "settings/settings_local_passcode.h"
  21. #include "settings/settings_premium.h" // Settings::ShowPremium.
  22. #include "settings/settings_privacy_controllers.h"
  23. #include "settings/settings_websites.h"
  24. #include "base/system_unlock.h"
  25. #include "base/timer_rpl.h"
  26. #include "boxes/passcode_box.h"
  27. #include "ui/boxes/confirm_box.h"
  28. #include "boxes/self_destruction_box.h"
  29. #include "core/application.h"
  30. #include "core/core_settings.h"
  31. #include "ui/chat/chat_style.h"
  32. #include "ui/effects/premium_graphics.h"
  33. #include "ui/effects/premium_top_bar.h"
  34. #include "ui/text/format_values.h"
  35. #include "ui/text/text_utilities.h"
  36. #include "ui/toast/toast.h"
  37. #include "ui/wrap/slide_wrap.h"
  38. #include "ui/wrap/fade_wrap.h"
  39. #include "ui/widgets/fields/input_field.h"
  40. #include "ui/widgets/shadow.h"
  41. #include "ui/widgets/checkbox.h"
  42. #include "ui/vertical_list.h"
  43. #include "ui/rect.h"
  44. #include "calls/calls_instance.h"
  45. #include "core/update_checker.h"
  46. #include "lang/lang_keys.h"
  47. #include "data/components/top_peers.h"
  48. #include "data/data_session.h"
  49. #include "data/data_chat.h"
  50. #include "data/data_channel.h"
  51. #include "data/data_peer_values.h"
  52. #include "main/main_app_config.h"
  53. #include "main/main_domain.h"
  54. #include "main/main_session.h"
  55. #include "storage/storage_domain.h"
  56. #include "window/window_session_controller.h"
  57. #include "apiwrap.h"
  58. #include "styles/style_settings.h"
  59. #include "styles/style_menu_icons.h"
  60. #include "styles/style_layers.h"
  61. #include <QtGui/QGuiApplication>
  62. #include <QtSvg/QSvgRenderer>
  63. namespace Settings {
  64. namespace {
  65. constexpr auto kUpdateTimeout = 60 * crl::time(1000);
  66. using Privacy = Api::UserPrivacy;
  67. [[nodiscard]] QImage PremiumStar() {
  68. const auto factor = style::DevicePixelRatio();
  69. const auto size = Size(st::settingsButtonNoIcon.style.font->ascent);
  70. auto image = QImage(
  71. size * factor,
  72. QImage::Format_ARGB32_Premultiplied);
  73. image.setDevicePixelRatio(factor);
  74. image.fill(Qt::transparent);
  75. {
  76. auto p = QPainter(&image);
  77. auto star = QSvgRenderer(
  78. Ui::Premium::ColorizedSvg(Ui::Premium::ButtonGradientStops()));
  79. star.render(&p, Rect(size));
  80. }
  81. return image;
  82. }
  83. void AddPremiumStar(
  84. not_null<Ui::SettingsButton*> button,
  85. not_null<Main::Session*> session,
  86. rpl::producer<QString> label,
  87. const QMargins &padding) {
  88. const auto badge = Ui::CreateChild<Ui::RpWidget>(button.get());
  89. badge->showOn(Data::AmPremiumValue(session));
  90. const auto sampleLeft = st::settingsColorSamplePadding.left();
  91. const auto badgeLeft = padding.left() + sampleLeft;
  92. auto star = PremiumStar();
  93. badge->resize(star.size() / style::DevicePixelRatio());
  94. badge->paintRequest(
  95. ) | rpl::start_with_next([=] {
  96. auto p = QPainter(badge);
  97. p.drawImage(0, 0, star);
  98. }, badge->lifetime());
  99. rpl::combine(
  100. button->sizeValue(),
  101. std::move(label)
  102. ) | rpl::start_with_next([=](const QSize &s, const QString &) {
  103. if (s.isNull()) {
  104. return;
  105. }
  106. badge->moveToLeft(
  107. button->fullTextWidth() + badgeLeft,
  108. (s.height() - badge->height()) / 2);
  109. }, badge->lifetime());
  110. }
  111. void OpenFileConfirmationsBox(not_null<Ui::GenericBox*> box) {
  112. box->setTitle(tr::lng_settings_file_confirmations());
  113. const auto settings = &Core::App().settings();
  114. const auto &list = settings->noWarningExtensions();
  115. const auto text = QStringList(begin(list), end(list)).join(' ');
  116. const auto layout = box->verticalLayout();
  117. const auto extensions = box->addRow(
  118. object_ptr<Ui::InputField>(
  119. box,
  120. st::defaultInputField,
  121. Ui::InputField::Mode::MultiLine,
  122. tr::lng_settings_edit_extensions(),
  123. TextWithTags{ text }),
  124. st::boxRowPadding + QMargins(0, 0, 0, st::settingsPrivacySkip));
  125. Ui::AddDividerText(layout, tr::lng_settings_edit_extensions_about());
  126. Ui::AddSkip(layout);
  127. const auto ip = layout->add(object_ptr<Ui::SettingsButton>(
  128. box,
  129. tr::lng_settings_edit_ip_confirm(),
  130. st::settingsButtonNoIcon
  131. ))->toggleOn(rpl::single(settings->ipRevealWarning()));
  132. Ui::AddSkip(layout);
  133. Ui::AddDividerText(layout, tr::lng_settings_edit_ip_confirm_about());
  134. box->setFocusCallback([=] {
  135. extensions->setFocusFast();
  136. });
  137. box->addButton(tr::lng_settings_save(), [=] {
  138. const auto extensionsList = extensions->getLastText()
  139. .mid(0, 10240)
  140. .split(' ', Qt::SkipEmptyParts)
  141. .mid(0, 1024);
  142. auto extensions = base::flat_set<QString>(
  143. extensionsList.begin(),
  144. extensionsList.end());
  145. const auto ipRevealWarning = ip->toggled();
  146. if (extensions != settings->noWarningExtensions()
  147. || ipRevealWarning != settings->ipRevealWarning()) {
  148. settings->setNoWarningExtensions(std::move(extensions));
  149. settings->setIpRevealWarning(ipRevealWarning);
  150. Core::App().saveSettingsDelayed();
  151. }
  152. box->closeBox();
  153. });
  154. box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
  155. }
  156. QString PrivacyBase(Privacy::Key key, const Privacy::Rule &rule) {
  157. using Key = Privacy::Key;
  158. using Option = Privacy::Option;
  159. switch (key) {
  160. case Key::CallsPeer2Peer:
  161. switch (rule.option) {
  162. case Option::Everyone:
  163. return tr::lng_edit_privacy_calls_p2p_everyone(tr::now);
  164. case Option::Contacts:
  165. return tr::lng_edit_privacy_calls_p2p_contacts(tr::now);
  166. case Option::Nobody:
  167. return tr::lng_edit_privacy_calls_p2p_nobody(tr::now);
  168. }
  169. [[fallthrough]];
  170. default:
  171. switch (rule.option) {
  172. case Option::Everyone:
  173. return rule.never.miniapps
  174. ? tr::lng_edit_privacy_no_miniapps(tr::now)
  175. : tr::lng_edit_privacy_everyone(tr::now);
  176. case Option::Contacts:
  177. return rule.always.premiums
  178. ? tr::lng_edit_privacy_contacts_and_premium(tr::now)
  179. : rule.always.miniapps
  180. ? tr::lng_edit_privacy_contacts_and_miniapps(tr::now)
  181. : tr::lng_edit_privacy_contacts(tr::now);
  182. case Option::CloseFriends:
  183. return tr::lng_edit_privacy_close_friends(tr::now);
  184. case Option::Nobody:
  185. return rule.always.premiums
  186. ? tr::lng_edit_privacy_premium(tr::now)
  187. : rule.always.miniapps
  188. ? tr::lng_edit_privacy_miniapps(tr::now)
  189. : tr::lng_edit_privacy_nobody(tr::now);
  190. }
  191. Unexpected("Value in Privacy::Option.");
  192. }
  193. }
  194. rpl::producer<QString> PrivacyString(
  195. not_null<::Main::Session*> session,
  196. Privacy::Key key) {
  197. session->api().userPrivacy().reload(key);
  198. return session->api().userPrivacy().value(
  199. key
  200. ) | rpl::map([=](const Privacy::Rule &value) {
  201. auto add = QStringList();
  202. if (const auto never = ExceptionUsersCount(value.never.peers)) {
  203. add.push_back("-" + QString::number(never));
  204. }
  205. if (const auto always = ExceptionUsersCount(value.always.peers)) {
  206. add.push_back("+" + QString::number(always));
  207. }
  208. if (!add.isEmpty()) {
  209. return PrivacyBase(key, value)
  210. + " (" + add.join(", ") + ")";
  211. } else {
  212. return PrivacyBase(key, value);
  213. }
  214. });
  215. }
  216. #if 0 // Dead code.
  217. void AddPremiumPrivacyButton(
  218. not_null<Window::SessionController*> controller,
  219. not_null<Ui::VerticalLayout*> container,
  220. rpl::producer<QString> label,
  221. Privacy::Key key,
  222. Fn<std::unique_ptr<EditPrivacyController>()> controllerFactory) {
  223. const auto shower = Ui::CreateChild<rpl::lifetime>(container.get());
  224. const auto session = &controller->session();
  225. const auto &st = st::settingsButtonNoIcon;
  226. const auto button = container->add(object_ptr<Button>(
  227. container,
  228. rpl::duplicate(label),
  229. st));
  230. AddPremiumStar(button, session, rpl::duplicate(label), st.padding);
  231. struct State {
  232. State(QWidget *parent) : widget(parent) {
  233. widget.setAttribute(Qt::WA_TransparentForMouseEvents);
  234. }
  235. Ui::RpWidget widget;
  236. };
  237. const auto state = button->lifetime().make_state<State>(button);
  238. using WeakToast = base::weak_ptr<Ui::Toast::Instance>;
  239. const auto toast = std::make_shared<WeakToast>();
  240. {
  241. const auto rightLabel = Ui::CreateChild<Ui::FlatLabel>(
  242. button,
  243. st.rightLabel);
  244. state->widget.resize(st::settingsPremiumLock.size());
  245. state->widget.paintRequest(
  246. ) | rpl::filter([=]() -> bool {
  247. return state->widget.x();
  248. }) | rpl::start_with_next([=] {
  249. auto p = QPainter(&state->widget);
  250. st::settingsPremiumLock.paint(p, 0, 0, state->widget.width());
  251. }, state->widget.lifetime());
  252. rpl::combine(
  253. button->sizeValue(),
  254. std::move(label),
  255. PrivacyString(session, key),
  256. Data::AmPremiumValue(session)
  257. ) | rpl::start_with_next([=, &st](
  258. const QSize &buttonSize,
  259. const QString &button,
  260. const QString &text,
  261. bool premium) {
  262. const auto locked = !premium;
  263. const auto rightSkip = st::settingsButtonRightSkip;
  264. const auto lockSkip = st::settingsPremiumLockSkip;
  265. const auto available = buttonSize.width()
  266. - st.padding.left()
  267. - st.padding.right()
  268. - st.style.font->width(button)
  269. - rightSkip
  270. - (locked ? state->widget.width() + lockSkip : 0);
  271. rightLabel->setText(text);
  272. rightLabel->resizeToNaturalWidth(available);
  273. rightLabel->moveToRight(
  274. rightSkip,
  275. st.padding.top());
  276. state->widget.moveToRight(
  277. rightSkip + rightLabel->width() + lockSkip,
  278. (buttonSize.height() - state->widget.height()) / 2);
  279. state->widget.setVisible(locked);
  280. }, rightLabel->lifetime());
  281. rightLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
  282. }
  283. const auto showToast = [=] {
  284. auto link = Ui::Text::Link(
  285. Ui::Text::Semibold(
  286. tr::lng_settings_privacy_premium_link(tr::now)));
  287. (*toast) = controller->showToast({
  288. .text = tr::lng_settings_privacy_premium(
  289. tr::now,
  290. lt_link,
  291. link,
  292. Ui::Text::WithEntities),
  293. .duration = Ui::Toast::kDefaultDuration * 2,
  294. .filter = crl::guard(&controller->session(), [=](
  295. const ClickHandlerPtr &,
  296. Qt::MouseButton button) {
  297. if (button == Qt::LeftButton) {
  298. if (const auto strong = toast->get()) {
  299. strong->hideAnimated();
  300. (*toast) = nullptr;
  301. Settings::ShowPremium(controller, QString());
  302. return true;
  303. }
  304. }
  305. return false;
  306. }),
  307. });
  308. };
  309. button->addClickHandler([=] {
  310. if (!session->premium()) {
  311. if (toast->empty()) {
  312. showToast();
  313. }
  314. return;
  315. }
  316. *shower = session->api().userPrivacy().value(
  317. key
  318. ) | rpl::take(
  319. 1
  320. ) | rpl::start_with_next([=](const Privacy::Rule &value) {
  321. controller->show(Box<EditPrivacyBox>(
  322. controller,
  323. controllerFactory(),
  324. value));
  325. });
  326. });
  327. }
  328. #endif
  329. void AddMessagesPrivacyButton(
  330. not_null<Window::SessionController*> controller,
  331. not_null<Ui::VerticalLayout*> container) {
  332. const auto session = &controller->session();
  333. const auto privacy = &session->api().globalPrivacy();
  334. auto label = rpl::combine(
  335. privacy->newRequirePremium(),
  336. privacy->newChargeStars()
  337. ) | rpl::map([=](bool requirePremium, int chargeStars) {
  338. return chargeStars
  339. ? tr::lng_edit_privacy_paid()
  340. : requirePremium
  341. ? tr::lng_edit_privacy_contacts_and_premium()
  342. : tr::lng_edit_privacy_everyone();
  343. }) | rpl::flatten_latest();
  344. const auto &st = st::settingsButtonNoIcon;
  345. const auto button = AddButtonWithLabel(
  346. container,
  347. tr::lng_settings_messages_privacy(),
  348. rpl::duplicate(label),
  349. st,
  350. {});
  351. button->addClickHandler([=] {
  352. controller->show(Box(EditMessagesPrivacyBox, controller));
  353. });
  354. if (!session->appConfig().newRequirePremiumFree()) {
  355. AddPremiumStar(button, session, rpl::duplicate(label), st.padding);
  356. }
  357. }
  358. rpl::producer<int> BlockedPeersCount(not_null<::Main::Session*> session) {
  359. return session->api().blockedPeers().slice(
  360. ) | rpl::map([](const Api::BlockedPeers::Slice &data) {
  361. return data.total;
  362. });
  363. }
  364. void SetupPrivacy(
  365. not_null<Window::SessionController*> controller,
  366. not_null<Ui::VerticalLayout*> container,
  367. rpl::producer<> updateTrigger) {
  368. Ui::AddSkip(container, st::settingsPrivacySkip);
  369. Ui::AddSubsectionTitle(container, tr::lng_settings_privacy_title());
  370. const auto session = &controller->session();
  371. using Key = Privacy::Key;
  372. const auto add = [&](
  373. rpl::producer<QString> label,
  374. Key key,
  375. auto controllerFactory) {
  376. return AddPrivacyButton(
  377. controller,
  378. container,
  379. std::move(label),
  380. {},
  381. key,
  382. controllerFactory);
  383. };
  384. add(
  385. tr::lng_settings_phone_number_privacy(),
  386. Key::PhoneNumber,
  387. [=] { return std::make_unique<PhoneNumberPrivacyController>(
  388. controller); });
  389. add(
  390. tr::lng_settings_last_seen(),
  391. Key::LastSeen,
  392. [=] { return std::make_unique<LastSeenPrivacyController>(
  393. session); });
  394. add(
  395. tr::lng_settings_profile_photo_privacy(),
  396. Key::ProfilePhoto,
  397. [] { return std::make_unique<ProfilePhotoPrivacyController>(); });
  398. add(
  399. tr::lng_settings_bio_privacy(),
  400. Key::About,
  401. [] { return std::make_unique<AboutPrivacyController>(); });
  402. add(
  403. tr::lng_settings_gifts_privacy(),
  404. Key::GiftsAutoSave,
  405. [=] { return std::make_unique<GiftsAutoSavePrivacyController>(); });
  406. add(
  407. tr::lng_settings_birthday_privacy(),
  408. Key::Birthday,
  409. [] { return std::make_unique<BirthdayPrivacyController>(); });
  410. add(
  411. tr::lng_settings_forwards_privacy(),
  412. Key::Forwards,
  413. [=] { return std::make_unique<ForwardsPrivacyController>(
  414. controller); });
  415. add(
  416. tr::lng_settings_calls(),
  417. Key::Calls,
  418. [] { return std::make_unique<CallsPrivacyController>(); });
  419. add(
  420. tr::lng_settings_groups_invite(),
  421. Key::Invites,
  422. [] { return std::make_unique<GroupsInvitePrivacyController>(); });
  423. {
  424. const auto &phrase = tr::lng_settings_voices_privacy;
  425. const auto &st = st::settingsButtonNoIcon;
  426. auto callback = [=] {
  427. return std::make_unique<VoicesPrivacyController>(session);
  428. };
  429. const auto voices = add(phrase(), Key::Voices, std::move(callback));
  430. AddPremiumStar(voices, session, phrase(), st.padding);
  431. }
  432. AddMessagesPrivacyButton(controller, container);
  433. session->api().userPrivacy().reload(
  434. Api::UserPrivacy::Key::AddedByPhone);
  435. Ui::AddSkip(container, st::settingsPrivacySecurityPadding);
  436. Ui::AddDivider(container);
  437. }
  438. void SetupLocalPasscode(
  439. not_null<Window::SessionController*> controller,
  440. not_null<Ui::VerticalLayout*> container,
  441. Fn<void(Type)> showOther) {
  442. auto has = rpl::single(rpl::empty) | rpl::then(
  443. controller->session().domain().local().localPasscodeChanged()
  444. ) | rpl::map([=] {
  445. return controller->session().domain().local().hasLocalPasscode();
  446. });
  447. auto label = rpl::combine(
  448. tr::lng_settings_cloud_password_on(),
  449. tr::lng_settings_cloud_password_off(),
  450. std::move(has),
  451. [](const QString &on, const QString &off, bool has) {
  452. return has ? on : off;
  453. });
  454. AddButtonWithLabel(
  455. container,
  456. tr::lng_settings_passcode_title(),
  457. std::move(label),
  458. st::settingsButton,
  459. { &st::menuIconLock }
  460. )->addClickHandler([=] {
  461. if (controller->session().domain().local().hasLocalPasscode()) {
  462. showOther(LocalPasscodeCheckId());
  463. } else {
  464. showOther(LocalPasscodeCreateId());
  465. }
  466. });
  467. }
  468. void SetupCloudPassword(
  469. not_null<Window::SessionController*> controller,
  470. not_null<Ui::VerticalLayout*> container,
  471. Fn<void(Type)> showOther) {
  472. using namespace rpl::mappers;
  473. using State = Core::CloudPasswordState;
  474. enum class PasswordState {
  475. Loading,
  476. On,
  477. Off,
  478. Unconfirmed,
  479. };
  480. const auto session = &controller->session();
  481. auto passwordState = rpl::single(
  482. PasswordState::Loading
  483. ) | rpl::then(session->api().cloudPassword().state(
  484. ) | rpl::map([](const State &state) {
  485. return (!state.unconfirmedPattern.isEmpty())
  486. ? PasswordState::Unconfirmed
  487. : state.hasPassword
  488. ? PasswordState::On
  489. : PasswordState::Off;
  490. })) | rpl::distinct_until_changed();
  491. auto label = rpl::duplicate(
  492. passwordState
  493. ) | rpl::map([=](PasswordState state) {
  494. return (state == PasswordState::Loading)
  495. ? tr::lng_profile_loading(tr::now)
  496. : (state == PasswordState::On)
  497. ? tr::lng_settings_cloud_password_on(tr::now)
  498. : tr::lng_settings_cloud_password_off(tr::now);
  499. });
  500. AddButtonWithLabel(
  501. container,
  502. tr::lng_settings_cloud_password_start_title(),
  503. std::move(label),
  504. st::settingsButton,
  505. { &st::menuIconPermissions }
  506. )->addClickHandler([=, passwordState = base::duplicate(passwordState)] {
  507. const auto state = rpl::variable<PasswordState>(
  508. base::duplicate(passwordState)).current();
  509. if (state == PasswordState::Loading) {
  510. return;
  511. } else if (state == PasswordState::On) {
  512. showOther(CloudPasswordInputId());
  513. } else if (state == PasswordState::Off) {
  514. showOther(CloudPasswordStartId());
  515. } else if (state == PasswordState::Unconfirmed) {
  516. showOther(CloudPasswordEmailConfirmId());
  517. }
  518. });
  519. const auto reloadOnActivation = [=](Qt::ApplicationState state) {
  520. if (/*label->toggled() && */state == Qt::ApplicationActive) {
  521. controller->session().api().cloudPassword().reload();
  522. }
  523. };
  524. QObject::connect(
  525. static_cast<QGuiApplication*>(QCoreApplication::instance()),
  526. &QGuiApplication::applicationStateChanged,
  527. container,
  528. reloadOnActivation);
  529. session->api().cloudPassword().reload();
  530. }
  531. void SetupTopPeers(
  532. not_null<Window::SessionController*> controller,
  533. not_null<Ui::VerticalLayout*> container) {
  534. Ui::AddSkip(container);
  535. Ui::AddSubsectionTitle(container, tr::lng_settings_top_peers_title());
  536. const auto session = &controller->session();
  537. container->add(object_ptr<Button>(
  538. container,
  539. tr::lng_settings_top_peers_suggest(),
  540. st::settingsButtonNoIcon
  541. ))->toggleOn(rpl::single(
  542. rpl::empty
  543. ) | rpl::then(
  544. session->topPeers().updates()
  545. ) | rpl::map([=] {
  546. return !session->topPeers().disabled();
  547. }))->toggledChanges(
  548. ) | rpl::filter([=](bool enabled) {
  549. return enabled == session->topPeers().disabled();
  550. }) | rpl::start_with_next([=](bool enabled) {
  551. session->topPeers().toggleDisabled(!enabled);
  552. }, container->lifetime());
  553. Ui::AddSkip(container);
  554. Ui::AddDividerText(container, tr::lng_settings_top_peers_about());
  555. }
  556. void SetupSelfDestruction(
  557. not_null<Window::SessionController*> controller,
  558. not_null<Ui::VerticalLayout*> container,
  559. rpl::producer<> updateTrigger) {
  560. Ui::AddSkip(container);
  561. Ui::AddSubsectionTitle(container, tr::lng_settings_destroy_title());
  562. const auto session = &controller->session();
  563. std::move(
  564. updateTrigger
  565. ) | rpl::start_with_next([=] {
  566. session->api().selfDestruct().reload();
  567. }, container->lifetime());
  568. const auto label = [&] {
  569. return session->api().selfDestruct().daysAccountTTL(
  570. ) | rpl::map(SelfDestructionBox::DaysLabel);
  571. };
  572. AddButtonWithLabel(
  573. container,
  574. tr::lng_settings_destroy_if(),
  575. label(),
  576. st::settingsButtonNoIcon
  577. )->addClickHandler([=] {
  578. controller->show(Box<SelfDestructionBox>(
  579. session,
  580. SelfDestructionBox::Type::Account,
  581. session->api().selfDestruct().daysAccountTTL()));
  582. });
  583. Ui::AddSkip(container);
  584. }
  585. void ClearPaymentInfoBoxBuilder(
  586. not_null<Ui::GenericBox*> box,
  587. not_null<Main::Session*> session) {
  588. box->setTitle(tr::lng_clear_payment_info_title());
  589. const auto checkboxPadding = style::margins(
  590. st::boxRowPadding.left(),
  591. st::boxRowPadding.left(),
  592. st::boxRowPadding.right(),
  593. st::boxRowPadding.bottom());
  594. const auto label = box->addRow(object_ptr<Ui::FlatLabel>(
  595. box,
  596. tr::lng_clear_payment_info_sure(),
  597. st::boxLabel));
  598. const auto shipping = box->addRow(
  599. object_ptr<Ui::Checkbox>(
  600. box,
  601. tr::lng_clear_payment_info_shipping(tr::now),
  602. true,
  603. st::defaultBoxCheckbox),
  604. checkboxPadding);
  605. const auto payment = box->addRow(
  606. object_ptr<Ui::Checkbox>(
  607. box,
  608. tr::lng_clear_payment_info_payment(tr::now),
  609. true,
  610. st::defaultBoxCheckbox),
  611. checkboxPadding);
  612. using Flags = MTPpayments_ClearSavedInfo::Flags;
  613. const auto flags = box->lifetime().make_state<Flags>();
  614. box->addButton(tr::lng_clear_payment_info_clear(), [=] {
  615. using Flag = Flags::Enum;
  616. *flags = (shipping->checked() ? Flag::f_info : Flag(0))
  617. | (payment->checked() ? Flag::f_credentials : Flag(0));
  618. delete label;
  619. delete shipping;
  620. delete payment;
  621. box->addRow(object_ptr<Ui::FlatLabel>(
  622. box,
  623. tr::lng_clear_payment_info_confirm(),
  624. st::boxLabel));
  625. box->clearButtons();
  626. box->addButton(tr::lng_clear_payment_info_clear(), [=] {
  627. session->api().request(MTPpayments_ClearSavedInfo(
  628. MTP_flags(*flags)
  629. )).send();
  630. box->closeBox();
  631. }, st::attentionBoxButton);
  632. box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
  633. }, st::attentionBoxButton);
  634. box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
  635. }
  636. auto ClearPaymentInfoBox(not_null<Main::Session*> session) {
  637. return Box(ClearPaymentInfoBoxBuilder, session);
  638. }
  639. void SetupBotsAndWebsites(
  640. not_null<Window::SessionController*> controller,
  641. not_null<Ui::VerticalLayout*> container) {
  642. Ui::AddSkip(container);
  643. Ui::AddSubsectionTitle(container, tr::lng_settings_security_bots());
  644. const auto session = &controller->session();
  645. container->add(object_ptr<Button>(
  646. container,
  647. tr::lng_settings_clear_payment_info(),
  648. st::settingsButtonNoIcon
  649. ))->addClickHandler([=] {
  650. controller->show(ClearPaymentInfoBox(session));
  651. });
  652. Ui::AddSkip(container);
  653. Ui::AddDivider(container);
  654. }
  655. void SetupConfirmationExtensions(
  656. not_null<Window::SessionController*> controller,
  657. not_null<Ui::VerticalLayout*> container) {
  658. if (Core::App().settings().noWarningExtensions().empty()
  659. && Core::App().settings().ipRevealWarning()) {
  660. return;
  661. }
  662. Ui::AddSkip(container);
  663. Ui::AddSubsectionTitle(container, tr::lng_settings_file_confirmations());
  664. container->add(object_ptr<Button>(
  665. container,
  666. tr::lng_settings_edit_extensions(),
  667. st::settingsButtonNoIcon
  668. ))->addClickHandler([=] {
  669. controller->show(Box(OpenFileConfirmationsBox));
  670. });
  671. Ui::AddSkip(container);
  672. Ui::AddDividerText(container, tr::lng_settings_edit_extensions_about());
  673. }
  674. void SetupBlockedList(
  675. not_null<Window::SessionController*> controller,
  676. not_null<Ui::VerticalLayout*> container,
  677. rpl::producer<> updateTrigger,
  678. Fn<void(Type)> showOther) {
  679. const auto session = &controller->session();
  680. auto blockedCount = rpl::combine(
  681. BlockedPeersCount(session),
  682. tr::lng_settings_no_blocked_users()
  683. ) | rpl::map([](int count, const QString &none) {
  684. return count ? QString::number(count) : none;
  685. });
  686. const auto blockedPeers = AddButtonWithLabel(
  687. container,
  688. tr::lng_settings_blocked_users(),
  689. std::move(blockedCount),
  690. st::settingsButton,
  691. { &st::menuIconBlock });
  692. blockedPeers->addClickHandler([=] {
  693. showOther(Blocked::Id());
  694. });
  695. std::move(
  696. updateTrigger
  697. ) | rpl::start_with_next([=] {
  698. session->api().blockedPeers().reload();
  699. }, blockedPeers->lifetime());
  700. }
  701. void SetupWebsitesList(
  702. not_null<Window::SessionController*> controller,
  703. not_null<Ui::VerticalLayout*> container,
  704. rpl::producer<> updateTrigger,
  705. Fn<void(Type)> showOther) {
  706. std::move(
  707. updateTrigger
  708. ) | rpl::start_with_next([=] {
  709. controller->session().api().websites().reload();
  710. }, container->lifetime());
  711. auto count = controller->session().api().websites().totalValue();
  712. auto countText = rpl::duplicate(
  713. count
  714. ) | rpl::filter(rpl::mappers::_1 > 0) | rpl::map([](int count) {
  715. return QString::number(count);
  716. });
  717. const auto wrap = container->add(
  718. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  719. container,
  720. object_ptr<Ui::VerticalLayout>(container)));
  721. const auto inner = wrap->entity();
  722. AddButtonWithLabel(
  723. inner,
  724. tr::lng_settings_logged_in(),
  725. std::move(countText),
  726. st::settingsButton,
  727. { &st::menuIconIpAddress }
  728. )->addClickHandler([=] {
  729. showOther(Websites::Id());
  730. });
  731. wrap->toggleOn(std::move(count) | rpl::map(rpl::mappers::_1 > 0));
  732. wrap->finishAnimating();
  733. }
  734. void SetupSessionsList(
  735. not_null<Window::SessionController*> controller,
  736. not_null<Ui::VerticalLayout*> container,
  737. rpl::producer<> updateTrigger,
  738. Fn<void(Type)> showOther) {
  739. std::move(
  740. updateTrigger
  741. ) | rpl::start_with_next([=] {
  742. controller->session().api().authorizations().reload();
  743. }, container->lifetime());
  744. auto count = controller->session().api().authorizations().totalValue(
  745. ) | rpl::map([](int count) {
  746. return count ? QString::number(count) : QString();
  747. });
  748. AddButtonWithLabel(
  749. container,
  750. tr::lng_settings_show_sessions(),
  751. std::move(count),
  752. st::settingsButton,
  753. { &st::menuIconDevices }
  754. )->addClickHandler([=] {
  755. showOther(Sessions::Id());
  756. });
  757. Ui::AddSkip(container);
  758. Ui::AddDividerText(container, tr::lng_settings_sessions_about());
  759. }
  760. void SetupGlobalTTLList(
  761. not_null<Window::SessionController*> controller,
  762. not_null<Ui::VerticalLayout*> container,
  763. rpl::producer<> updateTrigger,
  764. Fn<void(Type)> showOther) {
  765. const auto session = &controller->session();
  766. auto ttlLabel = rpl::combine(
  767. session->api().selfDestruct().periodDefaultHistoryTTL(),
  768. tr::lng_settings_ttl_after_off()
  769. ) | rpl::map([](int ttl, const QString &none) {
  770. return ttl ? Ui::FormatTTL(ttl) : none;
  771. });
  772. const auto globalTTLButton = AddButtonWithLabel(
  773. container,
  774. tr::lng_settings_ttl_title(),
  775. std::move(ttlLabel),
  776. st::settingsButton,
  777. { &st::menuIconTTL });
  778. globalTTLButton->addClickHandler([=] {
  779. showOther(GlobalTTLId());
  780. });
  781. std::move(
  782. updateTrigger
  783. ) | rpl::start_with_next([=] {
  784. session->api().selfDestruct().reload();
  785. }, container->lifetime());
  786. }
  787. void SetupSecurity(
  788. not_null<Window::SessionController*> controller,
  789. not_null<Ui::VerticalLayout*> container,
  790. rpl::producer<> updateTrigger,
  791. Fn<void(Type)> showOther) {
  792. Ui::AddSkip(container, st::settingsPrivacySkip);
  793. Ui::AddSubsectionTitle(container, tr::lng_settings_security());
  794. SetupCloudPassword(controller, container, showOther);
  795. SetupGlobalTTLList(
  796. controller,
  797. container,
  798. rpl::duplicate(updateTrigger),
  799. showOther);
  800. SetupLocalPasscode(controller, container, showOther);
  801. SetupBlockedList(
  802. controller,
  803. container,
  804. rpl::duplicate(updateTrigger),
  805. showOther);
  806. SetupWebsitesList(
  807. controller,
  808. container,
  809. rpl::duplicate(updateTrigger),
  810. showOther);
  811. SetupSessionsList(
  812. controller,
  813. container,
  814. rpl::duplicate(updateTrigger),
  815. showOther);
  816. }
  817. } // namespace
  818. void SetupSensitiveContent(
  819. not_null<Window::SessionController*> controller,
  820. not_null<Ui::VerticalLayout*> container,
  821. rpl::producer<> updateTrigger) {
  822. using namespace rpl::mappers;
  823. const auto wrap = container->add(
  824. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  825. container,
  826. object_ptr<Ui::VerticalLayout>(container)));
  827. const auto inner = wrap->entity();
  828. Ui::AddSkip(inner);
  829. Ui::AddSubsectionTitle(inner, tr::lng_settings_sensitive_title());
  830. const auto session = &controller->session();
  831. std::move(
  832. updateTrigger
  833. ) | rpl::start_with_next([=] {
  834. session->api().sensitiveContent().reload();
  835. }, container->lifetime());
  836. inner->add(object_ptr<Button>(
  837. inner,
  838. tr::lng_settings_sensitive_disable_filtering(),
  839. st::settingsButtonNoIcon
  840. ))->toggleOn(
  841. session->api().sensitiveContent().enabled()
  842. )->toggledChanges(
  843. ) | rpl::filter([=](bool toggled) {
  844. return toggled != session->api().sensitiveContent().enabledCurrent();
  845. }) | rpl::start_with_next([=](bool toggled) {
  846. session->api().sensitiveContent().update(toggled);
  847. }, container->lifetime());
  848. Ui::AddSkip(inner);
  849. Ui::AddDividerText(inner, tr::lng_settings_sensitive_about());
  850. wrap->toggleOn(session->api().sensitiveContent().canChange());
  851. }
  852. int ExceptionUsersCount(const std::vector<not_null<PeerData*>> &exceptions) {
  853. const auto add = [](int already, not_null<PeerData*> peer) {
  854. if (const auto chat = peer->asChat()) {
  855. return already + chat->count;
  856. } else if (const auto channel = peer->asChannel()) {
  857. return already + channel->membersCount();
  858. }
  859. return already + 1;
  860. };
  861. return ranges::accumulate(exceptions, 0, add);
  862. }
  863. bool CheckEditCloudPassword(not_null<::Main::Session*> session) {
  864. const auto current = session->api().cloudPassword().stateCurrent();
  865. Assert(current.has_value());
  866. return !current->outdatedClient;
  867. }
  868. object_ptr<Ui::BoxContent> EditCloudPasswordBox(not_null<Main::Session*> session) {
  869. const auto current = session->api().cloudPassword().stateCurrent();
  870. Assert(current.has_value());
  871. auto result = Box<PasscodeBox>(
  872. session,
  873. PasscodeBox::CloudFields::From(*current));
  874. const auto box = result.data();
  875. rpl::merge(
  876. box->newPasswordSet() | rpl::to_empty,
  877. box->passwordReloadNeeded()
  878. ) | rpl::start_with_next([=] {
  879. session->api().cloudPassword().reload();
  880. }, box->lifetime());
  881. box->clearUnconfirmedPassword(
  882. ) | rpl::start_with_next([=] {
  883. session->api().cloudPassword().clearUnconfirmedPassword();
  884. }, box->lifetime());
  885. return result;
  886. }
  887. void RemoveCloudPassword(not_null<Window::SessionController*> controller) {
  888. const auto session = &controller->session();
  889. const auto current = session->api().cloudPassword().stateCurrent();
  890. Assert(current.has_value());
  891. if (!current->hasPassword) {
  892. session->api().cloudPassword().clearUnconfirmedPassword();
  893. return;
  894. }
  895. auto fields = PasscodeBox::CloudFields::From(*current);
  896. fields.turningOff = true;
  897. auto box = Box<PasscodeBox>(session, fields);
  898. rpl::merge(
  899. box->newPasswordSet() | rpl::to_empty,
  900. box->passwordReloadNeeded()
  901. ) | rpl::start_with_next([=] {
  902. session->api().cloudPassword().reload();
  903. }, box->lifetime());
  904. box->clearUnconfirmedPassword(
  905. ) | rpl::start_with_next([=] {
  906. session->api().cloudPassword().clearUnconfirmedPassword();
  907. }, box->lifetime());
  908. controller->show(std::move(box));
  909. }
  910. object_ptr<Ui::BoxContent> CloudPasswordAppOutdatedBox() {
  911. const auto callback = [=](Fn<void()> &&close) {
  912. Core::UpdateApplication();
  913. close();
  914. };
  915. return Ui::MakeConfirmBox({
  916. .text = tr::lng_passport_app_out_of_date(),
  917. .confirmed = callback,
  918. .confirmText = tr::lng_menu_update(),
  919. });
  920. }
  921. not_null<Ui::SettingsButton*> AddPrivacyButton(
  922. not_null<Window::SessionController*> controller,
  923. not_null<Ui::VerticalLayout*> container,
  924. rpl::producer<QString> label,
  925. IconDescriptor &&descriptor,
  926. Privacy::Key key,
  927. Fn<std::unique_ptr<EditPrivacyController>()> controllerFactory,
  928. const style::SettingsButton *stOverride) {
  929. const auto shower = Ui::CreateChild<rpl::lifetime>(container.get());
  930. const auto session = &controller->session();
  931. const auto button = AddButtonWithLabel(
  932. container,
  933. std::move(label),
  934. PrivacyString(session, key),
  935. stOverride ? *stOverride : st::settingsButtonNoIcon,
  936. std::move(descriptor));
  937. button->addClickHandler([=] {
  938. *shower = session->api().userPrivacy().value(
  939. key
  940. ) | rpl::take(
  941. 1
  942. ) | rpl::start_with_next([=](const Privacy::Rule &value) {
  943. controller->show(Box<EditPrivacyBox>(
  944. controller,
  945. controllerFactory(),
  946. value));
  947. });
  948. });
  949. return button;
  950. }
  951. void SetupArchiveAndMute(
  952. not_null<Window::SessionController*> controller,
  953. not_null<Ui::VerticalLayout*> container) {
  954. using namespace rpl::mappers;
  955. const auto wrap = container->add(
  956. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  957. container,
  958. object_ptr<Ui::VerticalLayout>(container)));
  959. const auto inner = wrap->entity();
  960. Ui::AddSkip(inner);
  961. Ui::AddSubsectionTitle(inner, tr::lng_settings_new_unknown());
  962. const auto session = &controller->session();
  963. const auto privacy = &session->api().globalPrivacy();
  964. privacy->reload();
  965. inner->add(object_ptr<Button>(
  966. inner,
  967. tr::lng_settings_auto_archive(),
  968. st::settingsButtonNoIcon
  969. ))->toggleOn(
  970. privacy->archiveAndMute()
  971. )->toggledChanges(
  972. ) | rpl::filter([=](bool toggled) {
  973. return toggled != privacy->archiveAndMuteCurrent();
  974. }) | rpl::start_with_next([=](bool toggled) {
  975. privacy->updateArchiveAndMute(toggled);
  976. }, container->lifetime());
  977. Ui::AddSkip(inner);
  978. Ui::AddDividerText(inner, tr::lng_settings_auto_archive_about());
  979. auto shown = rpl::single(
  980. false
  981. ) | rpl::then(session->api().globalPrivacy().showArchiveAndMute(
  982. ) | rpl::filter(_1) | rpl::take(1));
  983. auto premium = Data::AmPremiumValue(&controller->session());
  984. using namespace rpl::mappers;
  985. wrap->toggleOn(rpl::combine(
  986. std::move(shown),
  987. std::move(premium),
  988. _1 || _2));
  989. }
  990. PrivacySecurity::PrivacySecurity(
  991. QWidget *parent,
  992. not_null<Window::SessionController*> controller)
  993. : Section(parent) {
  994. setupContent(controller);
  995. [[maybe_unused]] auto preload = base::SystemUnlockStatus();
  996. }
  997. rpl::producer<QString> PrivacySecurity::title() {
  998. return tr::lng_settings_section_privacy();
  999. }
  1000. void PrivacySecurity::setupContent(
  1001. not_null<Window::SessionController*> controller) {
  1002. const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
  1003. auto updateOnTick = rpl::single(
  1004. ) | rpl::then(base::timer_each(kUpdateTimeout));
  1005. const auto trigger = [=] {
  1006. return rpl::duplicate(updateOnTick);
  1007. };
  1008. SetupSecurity(controller, content, trigger(), showOtherMethod());
  1009. SetupPrivacy(controller, content, trigger());
  1010. SetupTopPeers(controller, content);
  1011. SetupArchiveAndMute(controller, content);
  1012. SetupConfirmationExtensions(controller, content);
  1013. SetupBotsAndWebsites(controller, content);
  1014. SetupSelfDestruction(controller, content, trigger());
  1015. Ui::ResizeFitChild(this, content);
  1016. }
  1017. } // namespace Settings