settings_main.cpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872
  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_main.h"
  8. #include "api/api_credits.h"
  9. #include "core/application.h"
  10. #include "core/click_handler_types.h"
  11. #include "settings/settings_advanced.h"
  12. #include "settings/settings_business.h"
  13. #include "settings/settings_calls.h"
  14. #include "settings/settings_chat.h"
  15. #include "settings/settings_codes.h"
  16. #include "settings/settings_credits.h"
  17. #include "settings/settings_folders.h"
  18. #include "settings/settings_information.h"
  19. #include "settings/settings_notifications.h"
  20. #include "settings/settings_power_saving.h"
  21. #include "settings/settings_premium.h"
  22. #include "settings/settings_privacy_security.h"
  23. #include "settings/settings_scale_preview.h"
  24. #include "boxes/language_box.h"
  25. #include "boxes/username_box.h"
  26. #include "boxes/about_box.h"
  27. #include "boxes/star_gift_box.h"
  28. #include "ui/basic_click_handlers.h"
  29. #include "ui/boxes/confirm_box.h"
  30. #include "ui/controls/userpic_button.h"
  31. #include "ui/effects/premium_graphics.h"
  32. #include "ui/effects/premium_top_bar.h" // Ui::Premium::ColorizedSvg.
  33. #include "ui/wrap/slide_wrap.h"
  34. #include "ui/widgets/menu/menu_add_action_callback.h"
  35. #include "ui/widgets/continuous_sliders.h"
  36. #include "ui/widgets/popup_menu.h"
  37. #include "ui/text/text_utilities.h"
  38. #include "ui/toast/toast.h"
  39. #include "ui/new_badges.h"
  40. #include "ui/rect.h"
  41. #include "ui/vertical_list.h"
  42. #include "info/profile/info_profile_badge.h"
  43. #include "info/profile/info_profile_emoji_status_panel.h"
  44. #include "data/components/credits.h"
  45. #include "data/data_user.h"
  46. #include "data/data_session.h"
  47. #include "data/data_cloud_themes.h"
  48. #include "data/data_chat_filters.h"
  49. #include "lang/lang_cloud_manager.h"
  50. #include "lang/lang_instance.h"
  51. #include "storage/localstorage.h"
  52. #include "main/main_session.h"
  53. #include "main/main_session_settings.h"
  54. #include "main/main_account.h"
  55. #include "main/main_domain.h"
  56. #include "main/main_app_config.h"
  57. #include "apiwrap.h"
  58. #include "api/api_peer_photo.h"
  59. #include "api/api_cloud_password.h"
  60. #include "api/api_global_privacy.h"
  61. #include "api/api_sensitive_content.h"
  62. #include "api/api_premium.h"
  63. #include "info/profile/info_profile_values.h"
  64. #include "window/window_controller.h"
  65. #include "window/window_session_controller.h"
  66. #include "base/call_delayed.h"
  67. #include "base/platform/base_platform_info.h"
  68. #include "styles/style_settings.h"
  69. #include "styles/style_info.h"
  70. #include "styles/style_menu_icons.h"
  71. #include <QtGui/QGuiApplication>
  72. #include <QtGui/QClipboard>
  73. #include <QtGui/QWindow>
  74. namespace Settings {
  75. namespace {
  76. class Cover final : public Ui::FixedHeightWidget {
  77. public:
  78. Cover(
  79. QWidget *parent,
  80. not_null<Window::SessionController*> controller,
  81. not_null<UserData*> user);
  82. ~Cover();
  83. private:
  84. void setupChildGeometry();
  85. void initViewers();
  86. void refreshStatusText();
  87. void refreshNameGeometry(int newWidth);
  88. void refreshPhoneGeometry(int newWidth);
  89. void refreshUsernameGeometry(int newWidth);
  90. const not_null<Window::SessionController*> _controller;
  91. const not_null<UserData*> _user;
  92. Info::Profile::EmojiStatusPanel _emojiStatusPanel;
  93. Info::Profile::Badge _badge;
  94. object_ptr<Ui::UserpicButton> _userpic;
  95. object_ptr<Ui::FlatLabel> _name = { nullptr };
  96. object_ptr<Ui::FlatLabel> _phone = { nullptr };
  97. object_ptr<Ui::FlatLabel> _username = { nullptr };
  98. };
  99. Cover::Cover(
  100. QWidget *parent,
  101. not_null<Window::SessionController*> controller,
  102. not_null<UserData*> user)
  103. : FixedHeightWidget(
  104. parent,
  105. st::settingsPhotoTop
  106. + st::infoProfileCover.photo.size.height()
  107. + st::settingsPhotoBottom)
  108. , _controller(controller)
  109. , _user(user)
  110. , _badge(
  111. this,
  112. st::infoPeerBadge,
  113. &user->session(),
  114. Info::Profile::BadgeContentForPeer(user),
  115. &_emojiStatusPanel,
  116. [=] {
  117. return controller->isGifPausedAtLeastFor(
  118. Window::GifPauseReason::Layer);
  119. },
  120. 0, // customStatusLoopsLimit
  121. Info::Profile::BadgeType::Premium)
  122. , _userpic(
  123. this,
  124. controller,
  125. _user,
  126. Ui::UserpicButton::Role::OpenPhoto,
  127. Ui::UserpicButton::Source::PeerPhoto,
  128. st::infoProfileCover.photo)
  129. , _name(this, st::infoProfileCover.name)
  130. , _phone(this, st::defaultFlatLabel)
  131. , _username(this, st::infoProfileMegagroupCover.status) {
  132. _user->updateFull();
  133. _name->setSelectable(true);
  134. _name->setContextCopyText(tr::lng_profile_copy_fullname(tr::now));
  135. _phone->setSelectable(true);
  136. _phone->setContextCopyText(tr::lng_profile_copy_phone(tr::now));
  137. const auto hook = [=](Ui::FlatLabel::ContextMenuRequest request) {
  138. if (request.selection.empty()) {
  139. const auto c = [=] {
  140. auto phone = rpl::variable<TextWithEntities>(
  141. Info::Profile::PhoneValue(_user)).current().text;
  142. phone.replace(' ', QString()).replace('-', QString());
  143. TextUtilities::SetClipboardText({ phone });
  144. };
  145. request.menu->addAction(tr::lng_profile_copy_phone(tr::now), c);
  146. } else {
  147. _phone->fillContextMenu(request);
  148. }
  149. };
  150. _phone->setContextMenuHook(hook);
  151. initViewers();
  152. setupChildGeometry();
  153. _userpic->switchChangePhotoOverlay(_user->isSelf(), [=](
  154. Ui::UserpicButton::ChosenImage chosen) {
  155. auto &image = chosen.image;
  156. _userpic->showCustom(base::duplicate(image));
  157. _user->session().api().peerPhoto().upload(
  158. _user,
  159. {
  160. std::move(image),
  161. chosen.markup.documentId,
  162. chosen.markup.colors,
  163. });
  164. });
  165. _badge.setPremiumClickCallback([=] {
  166. _emojiStatusPanel.show(
  167. _controller,
  168. _badge.widget(),
  169. _badge.sizeTag());
  170. });
  171. _badge.updated() | rpl::start_with_next([=] {
  172. refreshNameGeometry(width());
  173. }, _name->lifetime());
  174. }
  175. Cover::~Cover() = default;
  176. void Cover::setupChildGeometry() {
  177. using namespace rpl::mappers;
  178. widthValue(
  179. ) | rpl::start_with_next([=](int newWidth) {
  180. _userpic->moveToLeft(
  181. st::settingsPhotoLeft,
  182. st::settingsPhotoTop,
  183. newWidth);
  184. refreshNameGeometry(newWidth);
  185. refreshPhoneGeometry(newWidth);
  186. refreshUsernameGeometry(newWidth);
  187. }, lifetime());
  188. }
  189. void Cover::initViewers() {
  190. Info::Profile::NameValue(
  191. _user
  192. ) | rpl::start_with_next([=](const QString &name) {
  193. _name->setText(name);
  194. refreshNameGeometry(width());
  195. }, lifetime());
  196. Info::Profile::PhoneValue(
  197. _user
  198. ) | rpl::start_with_next([=](const TextWithEntities &value) {
  199. _phone->setText(value.text);
  200. refreshPhoneGeometry(width());
  201. }, lifetime());
  202. Info::Profile::UsernameValue(
  203. _user
  204. ) | rpl::start_with_next([=](const TextWithEntities &value) {
  205. _username->setMarkedText(Ui::Text::Link(value.text.isEmpty()
  206. ? tr::lng_settings_username_add(tr::now)
  207. : value.text));
  208. refreshUsernameGeometry(width());
  209. }, lifetime());
  210. _username->overrideLinkClickHandler([=] {
  211. const auto username = _user->username();
  212. if (username.isEmpty()) {
  213. _controller->show(Box(UsernamesBox, _user));
  214. } else {
  215. QGuiApplication::clipboard()->setText(
  216. _user->session().createInternalLinkFull(username));
  217. _controller->showToast(tr::lng_username_copied(tr::now));
  218. }
  219. });
  220. }
  221. void Cover::refreshNameGeometry(int newWidth) {
  222. const auto nameLeft = st::settingsNameLeft;
  223. const auto nameTop = st::settingsNameTop;
  224. auto nameWidth = newWidth
  225. - nameLeft
  226. - st::infoProfileCover.rightSkip;
  227. if (const auto width = _badge.widget() ? _badge.widget()->width() : 0) {
  228. nameWidth -= st::infoVerifiedCheckPosition.x() + width;
  229. }
  230. _name->resizeToNaturalWidth(nameWidth);
  231. _name->moveToLeft(nameLeft, nameTop, newWidth);
  232. const auto badgeLeft = nameLeft + _name->width();
  233. const auto badgeTop = nameTop;
  234. const auto badgeBottom = nameTop + _name->height();
  235. _badge.move(badgeLeft, badgeTop, badgeBottom);
  236. }
  237. void Cover::refreshPhoneGeometry(int newWidth) {
  238. const auto phoneLeft = st::settingsPhoneLeft;
  239. const auto phoneTop = st::settingsPhoneTop;
  240. const auto phoneWidth = newWidth
  241. - phoneLeft
  242. - st::infoProfileCover.rightSkip;
  243. _phone->resizeToWidth(phoneWidth);
  244. _phone->moveToLeft(phoneLeft, phoneTop, newWidth);
  245. }
  246. void Cover::refreshUsernameGeometry(int newWidth) {
  247. const auto usernameLeft = st::settingsUsernameLeft;
  248. const auto usernameTop = st::settingsUsernameTop;
  249. const auto usernameRight = st::infoProfileCover.rightSkip;
  250. const auto usernameWidth = newWidth - usernameLeft - usernameRight;
  251. _username->resizeToWidth(usernameWidth);
  252. _username->moveToLeft(usernameLeft, usernameTop, newWidth);
  253. }
  254. [[nodiscard]] not_null<Ui::SettingsButton*> AddPremiumStar(
  255. not_null<Ui::SettingsButton*> button,
  256. bool credits,
  257. Fn<bool()> isPaused) {
  258. const auto stops = credits
  259. ? Ui::Premium::CreditsIconGradientStops()
  260. : Ui::Premium::ButtonGradientStops();
  261. const auto ministarsContainer = Ui::CreateChild<Ui::RpWidget>(button);
  262. const auto &buttonSt = button->st();
  263. const auto fullHeight = buttonSt.height
  264. + rect::m::sum::v(buttonSt.padding);
  265. using MiniStars = Ui::Premium::ColoredMiniStars;
  266. const auto ministars = button->lifetime().make_state<MiniStars>(
  267. ministarsContainer,
  268. false);
  269. ministars->setColorOverride(stops);
  270. const auto isPausedValue
  271. = button->lifetime().make_state<rpl::variable<bool>>(isPaused());
  272. isPausedValue->value() | rpl::start_with_next([=](bool value) {
  273. ministars->setPaused(value);
  274. }, ministarsContainer->lifetime());
  275. ministarsContainer->paintRequest(
  276. ) | rpl::start_with_next([=] {
  277. (*isPausedValue) = isPaused();
  278. auto p = QPainter(ministarsContainer);
  279. {
  280. constexpr auto kScale = 0.35;
  281. const auto r = ministarsContainer->rect();
  282. p.translate(r.center());
  283. p.scale(kScale, kScale);
  284. p.translate(-r.center());
  285. }
  286. ministars->paint(p);
  287. }, ministarsContainer->lifetime());
  288. const auto badge = Ui::CreateChild<Ui::RpWidget>(button.get());
  289. auto star = [&] {
  290. const auto factor = style::DevicePixelRatio();
  291. const auto size = Size(st::settingsButtonNoIcon.style.font->ascent);
  292. auto image = QImage(
  293. size * factor,
  294. QImage::Format_ARGB32_Premultiplied);
  295. image.setDevicePixelRatio(factor);
  296. image.fill(Qt::transparent);
  297. {
  298. auto p = QPainter(&image);
  299. auto star = QSvgRenderer(Ui::Premium::ColorizedSvg(stops));
  300. star.render(&p, Rect(size));
  301. }
  302. return image;
  303. }();
  304. badge->resize(star.size() / style::DevicePixelRatio());
  305. badge->paintRequest(
  306. ) | rpl::start_with_next([=] {
  307. auto p = QPainter(badge);
  308. p.drawImage(0, 0, star);
  309. }, badge->lifetime());
  310. button->sizeValue(
  311. ) | rpl::start_with_next([=](const QSize &s) {
  312. badge->moveToLeft(
  313. button->st().iconLeft
  314. + (st::menuIconShop.width() - badge->width()) / 2,
  315. (s.height() - badge->height()) / 2);
  316. ministarsContainer->moveToLeft(
  317. badge->x() - (fullHeight - badge->height()) / 2,
  318. 0);
  319. }, badge->lifetime());
  320. ministarsContainer->resize(fullHeight, fullHeight);
  321. ministars->setCenter(ministarsContainer->rect());
  322. return button;
  323. }
  324. } // namespace
  325. void SetupPowerSavingButton(
  326. not_null<Window::Controller*> window,
  327. not_null<Ui::VerticalLayout*> container) {
  328. const auto button = AddButtonWithIcon(
  329. container,
  330. tr::lng_settings_power_menu(),
  331. st::settingsButton,
  332. { &st::menuIconPowerUsage });
  333. button->setClickedCallback([=] {
  334. window->show(Box(PowerSavingBox));
  335. });
  336. }
  337. void SetupLanguageButton(
  338. not_null<Window::Controller*> window,
  339. not_null<Ui::VerticalLayout*> container) {
  340. const auto button = AddButtonWithLabel(
  341. container,
  342. tr::lng_settings_language(),
  343. rpl::single(
  344. Lang::GetInstance().id()
  345. ) | rpl::then(
  346. Lang::GetInstance().idChanges()
  347. ) | rpl::map([] { return Lang::GetInstance().nativeName(); }),
  348. st::settingsButton,
  349. { &st::menuIconTranslate });
  350. const auto guard = Ui::CreateChild<base::binary_guard>(button.get());
  351. button->addClickHandler([=] {
  352. const auto m = button->clickModifiers();
  353. if ((m & Qt::ShiftModifier) && (m & Qt::AltModifier)) {
  354. Lang::CurrentCloudManager().switchToLanguage({ u"#custom"_q });
  355. } else {
  356. *guard = LanguageBox::Show(window->sessionController());
  357. }
  358. });
  359. }
  360. void SetupSections(
  361. not_null<Window::SessionController*> controller,
  362. not_null<Ui::VerticalLayout*> container,
  363. Fn<void(Type)> showOther) {
  364. Ui::AddDivider(container);
  365. Ui::AddSkip(container);
  366. const auto addSection = [&](
  367. rpl::producer<QString> label,
  368. Type type,
  369. IconDescriptor &&descriptor) {
  370. AddButtonWithIcon(
  371. container,
  372. std::move(label),
  373. st::settingsButton,
  374. std::move(descriptor)
  375. )->addClickHandler([=] {
  376. showOther(type);
  377. });
  378. };
  379. if (controller->session().supportMode()) {
  380. SetupSupport(controller, container);
  381. Ui::AddDivider(container);
  382. Ui::AddSkip(container);
  383. } else {
  384. addSection(
  385. tr::lng_settings_my_account(),
  386. Information::Id(),
  387. { &st::menuIconProfile });
  388. }
  389. addSection(
  390. tr::lng_settings_section_notify(),
  391. Notifications::Id(),
  392. { &st::menuIconNotifications });
  393. addSection(
  394. tr::lng_settings_section_privacy(),
  395. PrivacySecurity::Id(),
  396. { &st::menuIconLock });
  397. addSection(
  398. tr::lng_settings_section_chat_settings(),
  399. Chat::Id(),
  400. { &st::menuIconChatBubble });
  401. const auto preload = [=] {
  402. controller->session().data().chatsFilters().requestSuggested();
  403. };
  404. const auto account = &controller->session().account();
  405. const auto slided = container->add(
  406. object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
  407. container,
  408. CreateButtonWithIcon(
  409. container,
  410. tr::lng_settings_section_filters(),
  411. st::settingsButton,
  412. { &st::menuIconShowInFolder }))
  413. )->setDuration(0);
  414. if (controller->session().data().chatsFilters().has()
  415. || controller->session().settings().dialogsFiltersEnabled()) {
  416. slided->show(anim::type::instant);
  417. preload();
  418. } else {
  419. const auto enabled = [=] {
  420. const auto result = account->appConfig().get<bool>(
  421. u"dialog_filters_enabled"_q,
  422. false);
  423. if (result) {
  424. preload();
  425. }
  426. return result;
  427. };
  428. const auto preloadIfEnabled = [=](bool enabled) {
  429. if (enabled) {
  430. preload();
  431. }
  432. };
  433. slided->toggleOn(
  434. rpl::single(rpl::empty) | rpl::then(
  435. account->appConfig().refreshed()
  436. ) | rpl::map(
  437. enabled
  438. ) | rpl::before_next(preloadIfEnabled));
  439. }
  440. slided->entity()->setClickedCallback([=] {
  441. showOther(Folders::Id());
  442. });
  443. addSection(
  444. tr::lng_settings_advanced(),
  445. Advanced::Id(),
  446. { &st::menuIconManage });
  447. addSection(
  448. tr::lng_settings_section_devices(),
  449. Calls::Id(),
  450. { &st::menuIconUnmute });
  451. SetupPowerSavingButton(&controller->window(), container);
  452. SetupLanguageButton(&controller->window(), container);
  453. Ui::AddSkip(container);
  454. }
  455. void SetupPremium(
  456. not_null<Window::SessionController*> controller,
  457. not_null<Ui::VerticalLayout*> container,
  458. Fn<void(Type)> showOther) {
  459. if (!controller->session().premiumPossible()) {
  460. return;
  461. }
  462. Ui::AddDivider(container);
  463. Ui::AddSkip(container);
  464. const auto isPaused = Window::PausedIn(
  465. controller,
  466. Window::GifPauseReason::Any);
  467. AddPremiumStar(
  468. AddButtonWithIcon(
  469. container,
  470. tr::lng_premium_summary_title(),
  471. st::settingsButton),
  472. false,
  473. isPaused
  474. )->addClickHandler([=] {
  475. controller->setPremiumRef("settings");
  476. showOther(PremiumId());
  477. });
  478. {
  479. controller->session().credits().load();
  480. AddPremiumStar(
  481. AddButtonWithLabel(
  482. container,
  483. tr::lng_settings_credits(),
  484. controller->session().credits().balanceValue(
  485. ) | rpl::map([=](StarsAmount c) {
  486. return c
  487. ? Lang::FormatStarsAmountToShort(c).string
  488. : QString();
  489. }),
  490. st::settingsButton),
  491. true,
  492. isPaused
  493. )->addClickHandler([=] {
  494. controller->setPremiumRef("settings");
  495. showOther(CreditsId());
  496. });
  497. }
  498. const auto button = AddButtonWithIcon(
  499. container,
  500. tr::lng_business_title(),
  501. st::settingsButton,
  502. { .icon = &st::menuIconShop });
  503. button->addClickHandler([=] {
  504. showOther(BusinessId());
  505. });
  506. Ui::NewBadge::AddToRight(button);
  507. if (controller->session().premiumCanBuy()) {
  508. const auto button = AddButtonWithIcon(
  509. container,
  510. tr::lng_settings_gift_premium(),
  511. st::settingsButton,
  512. { .icon = &st::menuIconGiftPremium }
  513. );
  514. button->addClickHandler([=] {
  515. Ui::ChooseStarGiftRecipient(controller);
  516. });
  517. }
  518. Ui::AddSkip(container);
  519. }
  520. bool HasInterfaceScale() {
  521. return true;
  522. }
  523. void SetupInterfaceScale(
  524. not_null<Window::Controller*> window,
  525. not_null<Ui::VerticalLayout*> container,
  526. bool icon) {
  527. if (!HasInterfaceScale()) {
  528. return;
  529. }
  530. const auto toggled = Ui::CreateChild<rpl::event_stream<bool>>(
  531. container.get());
  532. const auto switched = (cConfigScale() == style::kScaleAuto);
  533. const auto button = AddButtonWithIcon(
  534. container,
  535. tr::lng_settings_default_scale(),
  536. icon ? st::settingsButton : st::settingsButtonNoIcon,
  537. { icon ? &st::menuIconShowInChat : nullptr }
  538. )->toggleOn(toggled->events_starting_with_copy(switched));
  539. const auto ratio = style::DevicePixelRatio();
  540. const auto scaleMin = style::kScaleMin;
  541. const auto scaleMax = style::MaxScaleForRatio(ratio);
  542. const auto scaleConfig = cConfigScale();
  543. const auto step = 5;
  544. Assert(!((scaleMax - scaleMin) % step));
  545. auto values = std::vector<int>();
  546. for (auto i = scaleMin; i != scaleMax; i += step) {
  547. values.push_back(i);
  548. if (scaleConfig > i && scaleConfig < i + step) {
  549. values.push_back(scaleConfig);
  550. }
  551. }
  552. values.push_back(scaleMax);
  553. const auto valuesCount = int(values.size());
  554. const auto valueFromScale = [=](int scale) {
  555. scale = cEvalScale(scale);
  556. auto result = 0;
  557. for (const auto value : values) {
  558. if (scale == value) {
  559. break;
  560. }
  561. ++result;
  562. }
  563. return ((result == valuesCount) ? (result - 1) : result)
  564. / float64(valuesCount - 1);
  565. };
  566. auto sliderWithLabel = MakeSliderWithLabel(
  567. container,
  568. st::settingsScale,
  569. st::settingsScaleLabel,
  570. st::normalFont->spacew * 2,
  571. st::settingsScaleLabel.style.font->width("300%"),
  572. true);
  573. container->add(
  574. std::move(sliderWithLabel.widget),
  575. icon ? st::settingsScalePadding : st::settingsBigScalePadding);
  576. const auto slider = sliderWithLabel.slider;
  577. const auto label = sliderWithLabel.label;
  578. const auto updateLabel = [=](int scale) {
  579. const auto labelText = [&](int scale) {
  580. if constexpr (Platform::IsMac()) {
  581. return QString::number(scale) + '%';
  582. } else {
  583. const auto handle = window->widget()->windowHandle();
  584. const auto ratio = handle->devicePixelRatio();
  585. return QString::number(base::SafeRound(scale * ratio)) + '%';
  586. }
  587. };
  588. label->setText(labelText(cEvalScale(scale)));
  589. };
  590. updateLabel(cConfigScale());
  591. const auto inSetScale = container->lifetime().make_state<bool>();
  592. const auto setScale = [=](int scale, const auto &repeatSetScale) -> void {
  593. if (*inSetScale) {
  594. return;
  595. }
  596. *inSetScale = true;
  597. const auto guard = gsl::finally([=] { *inSetScale = false; });
  598. updateLabel(scale);
  599. toggled->fire(scale == style::kScaleAuto);
  600. slider->setValue(valueFromScale(scale));
  601. if (cEvalScale(scale) != cEvalScale(cConfigScale())) {
  602. const auto confirmed = crl::guard(button, [=] {
  603. cSetConfigScale(scale);
  604. Local::writeSettings();
  605. Core::Restart();
  606. });
  607. const auto cancelled = crl::guard(button, [=](Fn<void()> close) {
  608. base::call_delayed(
  609. st::defaultSettingsSlider.duration,
  610. button,
  611. [=] { repeatSetScale(cConfigScale(), repeatSetScale); });
  612. close();
  613. });
  614. window->show(Ui::MakeConfirmBox({
  615. .text = tr::lng_settings_need_restart(),
  616. .confirmed = confirmed,
  617. .cancelled = cancelled,
  618. .confirmText = tr::lng_settings_restart_now(),
  619. }));
  620. } else if (scale != cConfigScale()) {
  621. cSetConfigScale(scale);
  622. Local::writeSettings();
  623. }
  624. };
  625. const auto shown = container->lifetime().make_state<bool>();
  626. const auto togglePreview = SetupScalePreview(window, slider);
  627. const auto toggleForScale = [=](int scale) {
  628. scale = cEvalScale(scale);
  629. const auto show = *shown
  630. ? ScalePreviewShow::Update
  631. : ScalePreviewShow::Show;
  632. *shown = true;
  633. for (auto i = 0; i != valuesCount; ++i) {
  634. if (values[i] <= scale
  635. && (i + 1 == valuesCount || values[i + 1] > scale)) {
  636. const auto x = (slider->width() * i) / (valuesCount - 1);
  637. togglePreview(show, scale, x);
  638. return;
  639. }
  640. }
  641. togglePreview(show, scale, slider->width() / 2);
  642. };
  643. const auto toggleHidePreview = [=] {
  644. togglePreview(ScalePreviewShow::Hide, 0, 0);
  645. *shown = false;
  646. };
  647. slider->setPseudoDiscrete(
  648. valuesCount,
  649. [=](int index) { return values[index]; },
  650. cConfigScale(),
  651. [=](int scale) { updateLabel(scale); toggleForScale(scale); },
  652. [=](int scale) { toggleHidePreview(); setScale(scale, setScale); });
  653. button->toggledValue(
  654. ) | rpl::map([](bool checked) {
  655. return checked ? style::kScaleAuto : cEvalScale(cConfigScale());
  656. }) | rpl::start_with_next([=](int scale) {
  657. setScale(scale, setScale);
  658. }, button->lifetime());
  659. if (!icon) {
  660. Ui::AddSkip(container, st::settingsThumbSkip);
  661. }
  662. }
  663. void SetupHelp(
  664. not_null<Window::SessionController*> controller,
  665. not_null<Ui::VerticalLayout*> container) {
  666. Ui::AddDivider(container);
  667. Ui::AddSkip(container);
  668. AddButtonWithIcon(
  669. container,
  670. tr::lng_settings_faq(),
  671. st::settingsButton,
  672. { &st::menuIconFaq }
  673. )->addClickHandler([=] {
  674. OpenFaq(controller);
  675. });
  676. AddButtonWithIcon(
  677. container,
  678. tr::lng_settings_features(),
  679. st::settingsButton,
  680. { &st::menuIconEmojiObjects }
  681. )->setClickedCallback([=] {
  682. UrlClickHandler::Open(tr::lng_telegram_features_url(tr::now));
  683. });
  684. const auto button = AddButtonWithIcon(
  685. container,
  686. tr::lng_settings_ask_question(),
  687. st::settingsButton,
  688. { &st::menuIconDiscussion });
  689. const auto requestId = button->lifetime().make_state<mtpRequestId>();
  690. button->lifetime().add([=] {
  691. if (*requestId) {
  692. controller->session().api().request(*requestId).cancel();
  693. }
  694. });
  695. button->addClickHandler([=] {
  696. const auto sure = crl::guard(button, [=] {
  697. if (*requestId) {
  698. return;
  699. }
  700. *requestId = controller->session().api().request(
  701. MTPhelp_GetSupport()
  702. ).done([=](const MTPhelp_Support &result) {
  703. *requestId = 0;
  704. result.match([&](const MTPDhelp_support &data) {
  705. auto &owner = controller->session().data();
  706. if (const auto user = owner.processUser(data.vuser())) {
  707. controller->showPeerHistory(user);
  708. }
  709. });
  710. }).fail([=] {
  711. *requestId = 0;
  712. }).send();
  713. });
  714. auto box = Ui::MakeConfirmBox({
  715. .text = tr::lng_settings_ask_sure(),
  716. .confirmed = sure,
  717. .cancelled = [=](Fn<void()> close) {
  718. OpenFaq(controller);
  719. close();
  720. },
  721. .confirmText = tr::lng_settings_ask_ok(),
  722. .cancelText = tr::lng_settings_faq_button(),
  723. .strictCancel = true,
  724. });
  725. controller->show(std::move(box));
  726. });
  727. }
  728. Main::Main(
  729. QWidget *parent,
  730. not_null<Window::SessionController*> controller)
  731. : Section(parent)
  732. , _controller(controller) {
  733. setupContent(controller);
  734. _controller->session().api().premium().reload();
  735. }
  736. rpl::producer<QString> Main::title() {
  737. return tr::lng_menu_settings();
  738. }
  739. void Main::fillTopBarMenu(const Ui::Menu::MenuCallback &addAction) {
  740. const auto &list = Core::App().domain().accounts();
  741. if (list.size() < Core::App().domain().maxAccounts()) {
  742. addAction(tr::lng_menu_add_account(tr::now), [=] {
  743. Core::App().domain().addActivated(MTP::Environment{});
  744. }, &st::menuIconAddAccount);
  745. }
  746. if (!_controller->session().supportMode()) {
  747. addAction(
  748. tr::lng_settings_information(tr::now),
  749. [=] { showOther(Information::Id()); },
  750. &st::menuIconEdit);
  751. }
  752. const auto window = &_controller->window();
  753. addAction({
  754. .text = tr::lng_settings_logout(tr::now),
  755. .handler = [=] { window->showLogoutConfirmation(); },
  756. .icon = &st::menuIconLeaveAttention,
  757. .isAttention = true,
  758. });
  759. }
  760. void Main::keyPressEvent(QKeyEvent *e) {
  761. crl::on_main(this, [=, text = e->text()]{
  762. CodesFeedString(_controller, text);
  763. });
  764. return Section::keyPressEvent(e);
  765. }
  766. void Main::setupContent(not_null<Window::SessionController*> controller) {
  767. const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
  768. content->add(object_ptr<Cover>(
  769. content,
  770. controller,
  771. controller->session().user()));
  772. SetupSections(controller, content, showOtherMethod());
  773. if (HasInterfaceScale()) {
  774. Ui::AddDivider(content);
  775. Ui::AddSkip(content);
  776. SetupInterfaceScale(&controller->window(), content);
  777. Ui::AddSkip(content);
  778. }
  779. SetupPremium(controller, content, showOtherMethod());
  780. SetupHelp(controller, content);
  781. Ui::ResizeFitChild(this, content);
  782. // If we load this in advance it won't jump when we open its' section.
  783. controller->session().api().cloudPassword().reload();
  784. controller->session().api().reloadContactSignupSilent();
  785. controller->session().api().sensitiveContent().reload();
  786. controller->session().api().globalPrivacy().reload();
  787. controller->session().data().cloudThemes().refresh();
  788. }
  789. void OpenFaq(base::weak_ptr<Window::SessionController> weak) {
  790. UrlClickHandler::Open(
  791. tr::lng_settings_faq_link(tr::now),
  792. QVariant::fromValue(ClickHandlerContext{
  793. .sessionWindow = weak,
  794. }));
  795. }
  796. } // namespace Settings