settings_chat.cpp 52 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881
  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_chat.h"
  8. #include "base/timer_rpl.h"
  9. #include "settings/settings_advanced.h"
  10. #include "settings/settings_privacy_security.h"
  11. #include "settings/settings_experimental.h"
  12. #include "settings/settings_shortcuts.h"
  13. #include "boxes/abstract_box.h"
  14. #include "boxes/peers/edit_peer_color_box.h"
  15. #include "boxes/connection_box.h"
  16. #include "boxes/auto_download_box.h"
  17. #include "boxes/reactions_settings_box.h"
  18. #include "boxes/stickers_box.h"
  19. #include "ui/boxes/confirm_box.h"
  20. #include "boxes/background_box.h"
  21. #include "boxes/background_preview_box.h"
  22. #include "boxes/download_path_box.h"
  23. #include "boxes/local_storage_box.h"
  24. #include "ui/boxes/choose_font_box.h"
  25. #include "ui/wrap/vertical_layout.h"
  26. #include "ui/wrap/slide_wrap.h"
  27. #include "ui/widgets/fields/input_field.h"
  28. #include "ui/widgets/checkbox.h"
  29. #include "ui/widgets/color_editor.h"
  30. #include "ui/widgets/buttons.h"
  31. #include "ui/chat/attach/attach_extensions.h"
  32. #include "ui/chat/chat_style.h"
  33. #include "ui/chat/chat_theme.h"
  34. #include "ui/layers/generic_box.h"
  35. #include "ui/effects/radial_animation.h"
  36. #include "ui/style/style_palette_colorizer.h"
  37. #include "ui/toast/toast.h"
  38. #include "ui/image/image.h"
  39. #include "ui/painter.h"
  40. #include "ui/vertical_list.h"
  41. #include "ui/ui_utility.h"
  42. #include "ui/widgets/menu/menu_add_action_callback.h"
  43. #include "history/view/history_view_quick_action.h"
  44. #include "lang/lang_keys.h"
  45. #include "export/export_manager.h"
  46. #include "window/themes/window_theme.h"
  47. #include "window/themes/window_themes_embedded.h"
  48. #include "window/themes/window_theme_editor_box.h"
  49. #include "window/themes/window_themes_cloud_list.h"
  50. #include "window/window_adaptive.h"
  51. #include "window/window_session_controller.h"
  52. #include "window/window_controller.h"
  53. #include "info/downloads/info_downloads_widget.h"
  54. #include "info/info_memento.h"
  55. #include "storage/localstorage.h"
  56. #include "core/file_utilities.h"
  57. #include "core/application.h"
  58. #include "data/data_session.h"
  59. #include "data/data_cloud_themes.h"
  60. #include "data/data_file_origin.h"
  61. #include "data/data_message_reactions.h"
  62. #include "data/data_peer_values.h"
  63. #include "data/data_user.h"
  64. #include "chat_helpers/emoji_sets_manager.h"
  65. #include "base/platform/base_platform_info.h"
  66. #include "base/call_delayed.h"
  67. #include "support/support_common.h"
  68. #include "support/support_templates.h"
  69. #include "main/main_session.h"
  70. #include "main/main_session_settings.h"
  71. #include "mainwidget.h"
  72. #include "styles/style_chat_helpers.h" // stickersRemove
  73. #include "styles/style_settings.h"
  74. #include "styles/style_layers.h"
  75. #include "styles/style_menu_icons.h"
  76. #include "styles/style_window.h"
  77. namespace Settings {
  78. namespace {
  79. const auto kSchemesList = Window::Theme::EmbeddedThemes();
  80. constexpr auto kCustomColorButtonParts = 7;
  81. class ColorsPalette final {
  82. public:
  83. using Type = Window::Theme::EmbeddedType;
  84. using Scheme = Window::Theme::EmbeddedScheme;
  85. explicit ColorsPalette(not_null<Ui::VerticalLayout*> container);
  86. void show(Type type);
  87. rpl::producer<QColor> selected() const;
  88. private:
  89. class Button {
  90. public:
  91. Button(
  92. not_null<QWidget*> parent,
  93. std::vector<QColor> &&colors,
  94. bool selected);
  95. void moveToLeft(int x, int y);
  96. void update(std::vector<QColor> &&colors, bool selected);
  97. rpl::producer<> clicks() const;
  98. bool selected() const;
  99. QColor color() const;
  100. private:
  101. void paint();
  102. Ui::AbstractButton _widget;
  103. std::vector<QColor> _colors;
  104. Ui::Animations::Simple _selectedAnimation;
  105. bool _selected = false;
  106. };
  107. void show(
  108. not_null<const Scheme*> scheme,
  109. std::vector<QColor> &&colors,
  110. int selected);
  111. void selectCustom(not_null<const Scheme*> scheme);
  112. void updateInnerGeometry();
  113. not_null<Ui::SlideWrap<>*> _outer;
  114. std::vector<std::unique_ptr<Button>> _buttons;
  115. rpl::event_stream<QColor> _selected;
  116. };
  117. void PaintCustomButton(QPainter &p, const std::vector<QColor> &colors) {
  118. Expects(colors.size() >= kCustomColorButtonParts);
  119. p.setPen(Qt::NoPen);
  120. const auto size = st::settingsAccentColorSize;
  121. const auto smallSize = size / 8.;
  122. const auto drawAround = [&](QPointF center, int index) {
  123. const auto where = QPointF{
  124. size * (1. + center.x()) / 2,
  125. size * (1. + center.y()) / 2
  126. };
  127. p.setBrush(colors[index]);
  128. p.drawEllipse(
  129. where.x() - smallSize,
  130. where.y() - smallSize,
  131. 2 * smallSize,
  132. 2 * smallSize);
  133. };
  134. drawAround(QPointF(), 0);
  135. for (auto i = 0; i != 6; ++i) {
  136. const auto angle = i * M_PI / 3.;
  137. const auto point = QPointF{ cos(angle), sin(angle) };
  138. const auto adjusted = point * (1. - (2 * smallSize / size));
  139. drawAround(adjusted, i + 1);
  140. }
  141. }
  142. ColorsPalette::Button::Button(
  143. not_null<QWidget*> parent,
  144. std::vector<QColor> &&colors,
  145. bool selected)
  146. : _widget(parent.get())
  147. , _colors(std::move(colors))
  148. , _selected(selected) {
  149. _widget.show();
  150. _widget.resize(st::settingsAccentColorSize, st::settingsAccentColorSize);
  151. _widget.paintRequest(
  152. ) | rpl::start_with_next([=] {
  153. paint();
  154. }, _widget.lifetime());
  155. }
  156. void ColorsPalette::Button::moveToLeft(int x, int y) {
  157. _widget.moveToLeft(x, y);
  158. }
  159. void ColorsPalette::Button::update(
  160. std::vector<QColor> &&colors,
  161. bool selected) {
  162. if (_colors != colors) {
  163. _colors = std::move(colors);
  164. _widget.update();
  165. }
  166. if (_selected != selected) {
  167. _selected = selected;
  168. _selectedAnimation.start(
  169. [=] { _widget.update(); },
  170. _selected ? 0. : 1.,
  171. _selected ? 1. : 0.,
  172. st::defaultRadio.duration * 2);
  173. }
  174. }
  175. rpl::producer<> ColorsPalette::Button::clicks() const {
  176. return _widget.clicks() | rpl::to_empty;
  177. }
  178. bool ColorsPalette::Button::selected() const {
  179. return _selected;
  180. }
  181. QColor ColorsPalette::Button::color() const {
  182. Expects(_colors.size() == 1);
  183. return _colors.front();
  184. }
  185. void ColorsPalette::Button::paint() {
  186. auto p = QPainter(&_widget);
  187. PainterHighQualityEnabler hq(p);
  188. if (_colors.size() == 1) {
  189. PaintRoundColorButton(
  190. p,
  191. st::settingsAccentColorSize,
  192. _colors.front(),
  193. _selectedAnimation.value(_selected ? 1. : 0.));
  194. } else if (_colors.size() >= kCustomColorButtonParts) {
  195. PaintCustomButton(p, _colors);
  196. }
  197. }
  198. ColorsPalette::ColorsPalette(not_null<Ui::VerticalLayout*> container)
  199. : _outer(container->add(
  200. object_ptr<Ui::SlideWrap<>>(
  201. container,
  202. object_ptr<Ui::RpWidget>(container)))) {
  203. _outer->hide(anim::type::instant);
  204. const auto inner = _outer->entity();
  205. inner->widthValue(
  206. ) | rpl::start_with_next([=] {
  207. updateInnerGeometry();
  208. }, inner->lifetime());
  209. }
  210. void ColorsPalette::show(Type type) {
  211. const auto scheme = ranges::find(kSchemesList, type, &Scheme::type);
  212. if (scheme == end(kSchemesList)) {
  213. _outer->hide(anim::type::instant);
  214. return;
  215. }
  216. auto list = Window::Theme::DefaultAccentColors(type);
  217. if (list.empty()) {
  218. _outer->hide(anim::type::instant);
  219. return;
  220. }
  221. list.insert(list.begin(), scheme->accentColor);
  222. const auto color = Core::App().settings().themesAccentColors().get(type);
  223. const auto current = color.value_or(scheme->accentColor);
  224. const auto i = ranges::find(list, current);
  225. if (i == end(list)) {
  226. list.back() = current;
  227. }
  228. const auto selected = std::clamp(
  229. int(i - begin(list)),
  230. 0,
  231. int(list.size()) - 1);
  232. _outer->show(anim::type::instant);
  233. show(&*scheme, std::move(list), selected);
  234. const auto inner = _outer->entity();
  235. inner->resize(_outer->width(), inner->height());
  236. updateInnerGeometry();
  237. }
  238. void ColorsPalette::show(
  239. not_null<const Scheme*> scheme,
  240. std::vector<QColor> &&colors,
  241. int selected) {
  242. Expects(selected >= 0 && selected < colors.size());
  243. while (_buttons.size() > colors.size()) {
  244. _buttons.pop_back();
  245. }
  246. auto index = 0;
  247. const auto inner = _outer->entity();
  248. const auto pushButton = [&](std::vector<QColor> &&colors) {
  249. auto result = rpl::producer<>();
  250. const auto chosen = (index == selected);
  251. if (_buttons.size() > index) {
  252. _buttons[index]->update(std::move(colors), chosen);
  253. } else {
  254. _buttons.push_back(std::make_unique<Button>(
  255. inner,
  256. std::move(colors),
  257. chosen));
  258. result = _buttons.back()->clicks();
  259. }
  260. ++index;
  261. return result;
  262. };
  263. for (const auto &color : colors) {
  264. auto clicks = pushButton({ color });
  265. if (clicks) {
  266. std::move(
  267. clicks
  268. ) | rpl::map([=] {
  269. return _buttons[index - 1]->color();
  270. }) | rpl::start_with_next([=](QColor color) {
  271. _selected.fire_copy(color);
  272. }, inner->lifetime());
  273. }
  274. }
  275. auto clicks = pushButton(std::move(colors));
  276. if (clicks) {
  277. std::move(
  278. clicks
  279. ) | rpl::start_with_next([=] {
  280. selectCustom(scheme);
  281. }, inner->lifetime());
  282. }
  283. }
  284. void ColorsPalette::selectCustom(not_null<const Scheme*> scheme) {
  285. const auto selected = ranges::find(_buttons, true, &Button::selected);
  286. Assert(selected != end(_buttons));
  287. const auto colorizer = Window::Theme::ColorizerFrom(
  288. *scheme,
  289. scheme->accentColor);
  290. Ui::show(Box([=](not_null<Ui::GenericBox*> box) {
  291. const auto editor = box->addRow(object_ptr<ColorEditor>(
  292. box,
  293. ColorEditor::Mode::HSL,
  294. (*selected)->color()));
  295. const auto save = crl::guard(_outer, [=] {
  296. _selected.fire_copy(editor->color());
  297. box->closeBox();
  298. });
  299. editor->submitRequests(
  300. ) | rpl::start_with_next(save, editor->lifetime());
  301. editor->setLightnessLimits(
  302. colorizer.lightnessMin,
  303. colorizer.lightnessMax);
  304. box->setFocusCallback([=] {
  305. editor->setInnerFocus();
  306. });
  307. box->addButton(tr::lng_settings_save(), save);
  308. box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
  309. box->setTitle(tr::lng_settings_theme_accent_title());
  310. box->setWidth(editor->width());
  311. }));
  312. }
  313. rpl::producer<QColor> ColorsPalette::selected() const {
  314. return _selected.events();
  315. }
  316. void ColorsPalette::updateInnerGeometry() {
  317. if (_buttons.size() < 2) {
  318. return;
  319. }
  320. const auto inner = _outer->entity();
  321. const auto size = st::settingsAccentColorSize;
  322. const auto padding = st::settingsButtonNoIcon.padding;
  323. const auto width = inner->width() - padding.left() - padding.right();
  324. const auto skip = (width - size * _buttons.size())
  325. / float64(_buttons.size() - 1);
  326. const auto y = st::defaultVerticalListSkip * 2;
  327. auto x = float64(padding.left());
  328. for (const auto &button : _buttons) {
  329. button->moveToLeft(int(base::SafeRound(x)), y);
  330. x += size + skip;
  331. }
  332. inner->resize(inner->width(), y + size);
  333. }
  334. } // namespace
  335. void PaintRoundColorButton(
  336. QPainter &p,
  337. int size,
  338. QBrush brush,
  339. float64 selected) {
  340. const auto rect = QRect(0, 0, size, size);
  341. p.setBrush(brush);
  342. p.setPen(Qt::NoPen);
  343. p.drawEllipse(rect);
  344. if (selected > 0.) {
  345. const auto startSkip = -st::settingsAccentColorLine / 2.;
  346. const auto endSkip = float64(st::settingsAccentColorSkip);
  347. const auto skip = startSkip + (endSkip - startSkip) * selected;
  348. auto pen = st::boxBg->p;
  349. pen.setWidth(st::settingsAccentColorLine);
  350. p.setBrush(Qt::NoBrush);
  351. p.setPen(pen);
  352. p.setOpacity(selected);
  353. p.drawEllipse(QRectF(rect).marginsRemoved({ skip, skip, skip, skip }));
  354. }
  355. }
  356. class BackgroundRow : public Ui::RpWidget {
  357. public:
  358. BackgroundRow(
  359. QWidget *parent,
  360. not_null<Window::SessionController*> controller);
  361. protected:
  362. void paintEvent(QPaintEvent *e) override;
  363. int resizeGetHeight(int newWidth) override;
  364. private:
  365. void updateImage();
  366. float64 radialProgress() const;
  367. bool radialLoading() const;
  368. QRect radialRect() const;
  369. void radialStart();
  370. crl::time radialTimeShift() const;
  371. void radialAnimationCallback(crl::time now);
  372. const not_null<Window::SessionController*> _controller;
  373. QPixmap _background;
  374. object_ptr<Ui::LinkButton> _chooseFromGallery;
  375. object_ptr<Ui::LinkButton> _chooseFromFile;
  376. Ui::RadialAnimation _radial;
  377. };
  378. void ChooseFromFile(
  379. not_null<Window::SessionController*> controller,
  380. not_null<QWidget*> parent);
  381. BackgroundRow::BackgroundRow(
  382. QWidget *parent,
  383. not_null<Window::SessionController*> controller)
  384. : RpWidget(parent)
  385. , _controller(controller)
  386. , _chooseFromGallery(
  387. this,
  388. tr::lng_settings_bg_from_gallery(tr::now),
  389. st::settingsLink)
  390. , _chooseFromFile(this, tr::lng_settings_bg_from_file(tr::now), st::settingsLink)
  391. , _radial([=](crl::time now) { radialAnimationCallback(now); }) {
  392. updateImage();
  393. _chooseFromGallery->addClickHandler([=] {
  394. controller->show(Box<BackgroundBox>(controller));
  395. });
  396. _chooseFromFile->addClickHandler([=] {
  397. ChooseFromFile(controller, this);
  398. });
  399. using Update = const Window::Theme::BackgroundUpdate;
  400. Window::Theme::Background()->updates(
  401. ) | rpl::filter([](const Update &update) {
  402. return (update.type == Update::Type::New
  403. || update.type == Update::Type::Start
  404. || update.type == Update::Type::Changed);
  405. }) | rpl::start_with_next([=] {
  406. updateImage();
  407. }, lifetime());
  408. }
  409. void BackgroundRow::paintEvent(QPaintEvent *e) {
  410. auto p = QPainter(this);
  411. const auto radial = _radial.animating();
  412. const auto radialOpacity = radial ? _radial.opacity() : 0.;
  413. if (radial) {
  414. const auto backThumb = _controller->content()->newBackgroundThumb();
  415. if (!backThumb) {
  416. p.drawPixmap(0, 0, _background);
  417. } else {
  418. const auto &pix = backThumb->pix(
  419. st::settingsBackgroundThumb,
  420. { .options = Images::Option::Blur });
  421. const auto factor = style::DevicePixelRatio();
  422. p.drawPixmap(
  423. 0,
  424. 0,
  425. st::settingsBackgroundThumb,
  426. st::settingsBackgroundThumb,
  427. pix,
  428. 0,
  429. (pix.height() - st::settingsBackgroundThumb * factor) / 2,
  430. st::settingsBackgroundThumb * factor,
  431. st::settingsBackgroundThumb * factor);
  432. }
  433. const auto outer = radialRect();
  434. const auto inner = QRect(
  435. QPoint(
  436. outer.x() + (outer.width() - st::radialSize.width()) / 2,
  437. outer.y() + (outer.height() - st::radialSize.height()) / 2),
  438. st::radialSize);
  439. p.setPen(Qt::NoPen);
  440. p.setOpacity(radialOpacity);
  441. p.setBrush(st::radialBg);
  442. {
  443. PainterHighQualityEnabler hq(p);
  444. p.drawEllipse(inner);
  445. }
  446. p.setOpacity(1);
  447. const auto arc = inner.marginsRemoved(QMargins(
  448. st::radialLine,
  449. st::radialLine,
  450. st::radialLine,
  451. st::radialLine));
  452. _radial.draw(p, arc, st::radialLine, st::radialFg);
  453. } else {
  454. p.drawPixmap(0, 0, _background);
  455. }
  456. }
  457. int BackgroundRow::resizeGetHeight(int newWidth) {
  458. auto linkTop = st::settingsFromGalleryTop;
  459. auto linkLeft = st::settingsBackgroundThumb + st::settingsThumbSkip;
  460. auto linkWidth = newWidth - linkLeft;
  461. _chooseFromGallery->resizeToWidth(
  462. qMin(linkWidth, _chooseFromGallery->naturalWidth()));
  463. _chooseFromFile->resizeToWidth(
  464. qMin(linkWidth, _chooseFromFile->naturalWidth()));
  465. _chooseFromGallery->moveToLeft(linkLeft, linkTop, newWidth);
  466. linkTop += _chooseFromGallery->height() + st::settingsFromFileTop;
  467. _chooseFromFile->moveToLeft(linkLeft, linkTop, newWidth);
  468. return st::settingsBackgroundThumb;
  469. }
  470. float64 BackgroundRow::radialProgress() const {
  471. return _controller->content()->chatBackgroundProgress();
  472. }
  473. bool BackgroundRow::radialLoading() const {
  474. const auto widget = _controller->content();
  475. if (widget->chatBackgroundLoading()) {
  476. widget->checkChatBackground();
  477. if (widget->chatBackgroundLoading()) {
  478. return true;
  479. } else {
  480. const_cast<BackgroundRow*>(this)->updateImage();
  481. }
  482. }
  483. return false;
  484. }
  485. QRect BackgroundRow::radialRect() const {
  486. return QRect(
  487. 0,
  488. 0,
  489. st::settingsBackgroundThumb,
  490. st::settingsBackgroundThumb);
  491. }
  492. void BackgroundRow::radialStart() {
  493. if (radialLoading() && !_radial.animating()) {
  494. _radial.start(radialProgress());
  495. if (const auto shift = radialTimeShift()) {
  496. _radial.update(
  497. radialProgress(),
  498. !radialLoading(),
  499. crl::now() + shift);
  500. }
  501. }
  502. }
  503. crl::time BackgroundRow::radialTimeShift() const {
  504. return st::radialDuration;
  505. }
  506. void BackgroundRow::radialAnimationCallback(crl::time now) {
  507. const auto updated = _radial.update(
  508. radialProgress(),
  509. !radialLoading(),
  510. now + radialTimeShift());
  511. if (!anim::Disabled() || updated) {
  512. rtlupdate(radialRect());
  513. }
  514. }
  515. void BackgroundRow::updateImage() {
  516. const auto size = st::settingsBackgroundThumb;
  517. const auto fullsize = size * style::DevicePixelRatio();
  518. const auto &background = *Window::Theme::Background();
  519. const auto &paper = background.paper();
  520. const auto &prepared = background.prepared();
  521. const auto preparePattern = [&] {
  522. const auto paintPattern = [&](QPainter &p, bool inverted) {
  523. if (prepared.isNull()) {
  524. return;
  525. }
  526. const auto w = prepared.width();
  527. const auto h = prepared.height();
  528. const auto s = [&] {
  529. const auto scaledw = w * st::windowMinHeight / h;
  530. const auto result = (w * size) / scaledw;
  531. return std::min({ result, w, h });
  532. }();
  533. auto small = prepared.copy((w - s) / 2, (h - s) / 2, s, s);
  534. if (inverted) {
  535. small = Ui::InvertPatternImage(std::move(small));
  536. }
  537. p.drawImage(QRect(0, 0, fullsize, fullsize), small);
  538. };
  539. return Ui::GenerateBackgroundImage(
  540. { fullsize, fullsize },
  541. paper.backgroundColors(),
  542. paper.gradientRotation(),
  543. paper.patternOpacity(),
  544. paintPattern);
  545. };
  546. const auto prepareNormal = [&] {
  547. auto result = QImage(
  548. QSize{ fullsize, fullsize },
  549. QImage::Format_ARGB32_Premultiplied);
  550. result.setDevicePixelRatio(style::DevicePixelRatio());
  551. if (const auto color = background.colorForFill()) {
  552. result.fill(*color);
  553. return result;
  554. } else if (prepared.isNull()) {
  555. result.fill(Qt::transparent);
  556. return result;
  557. }
  558. auto p = QPainter(&result);
  559. PainterHighQualityEnabler hq(p);
  560. const auto w = prepared.width();
  561. const auto h = prepared.height();
  562. const auto s = std::min(w, h);
  563. p.drawImage(
  564. QRect(0, 0, size, size),
  565. prepared,
  566. QRect((w - s) / 2, (h - s) / 2, s, s));
  567. p.end();
  568. return result;
  569. };
  570. auto back = (paper.isPattern() || !background.gradientForFill().isNull())
  571. ? preparePattern()
  572. : prepareNormal();
  573. _background = Ui::PixmapFromImage(
  574. Images::Round(std::move(back), ImageRoundRadius::Small));
  575. _background.setDevicePixelRatio(style::DevicePixelRatio());
  576. rtlupdate(radialRect());
  577. if (radialLoading()) {
  578. radialStart();
  579. }
  580. }
  581. void ChooseFromFile(
  582. not_null<Window::SessionController*> controller,
  583. not_null<QWidget*> parent) {
  584. auto filters = QStringList(
  585. u"Theme files (*.tdesktop-theme *.tdesktop-palette *"_q
  586. + Ui::ImageExtensions().join(u" *"_q)
  587. + u")"_q);
  588. filters.push_back(FileDialog::AllFilesFilter());
  589. const auto callback = crl::guard(controller, [=](
  590. const FileDialog::OpenResult &result) {
  591. if (result.paths.isEmpty() && result.remoteContent.isEmpty()) {
  592. return;
  593. }
  594. if (!result.paths.isEmpty()) {
  595. const auto filePath = result.paths.front();
  596. const auto hasExtension = [&](QLatin1String extension) {
  597. return filePath.endsWith(extension, Qt::CaseInsensitive);
  598. };
  599. if (hasExtension(qstr(".tdesktop-theme"))
  600. || hasExtension(qstr(".tdesktop-palette"))) {
  601. Window::Theme::Apply(filePath);
  602. return;
  603. }
  604. }
  605. auto image = Images::Read({
  606. .path = result.paths.isEmpty() ? QString() : result.paths.front(),
  607. .content = result.remoteContent,
  608. .forceOpaque = true,
  609. }).image;
  610. if (image.isNull() || image.width() <= 0 || image.height() <= 0) {
  611. return;
  612. }
  613. auto local = Data::CustomWallPaper();
  614. local.setLocalImageAsThumbnail(std::make_shared<Image>(
  615. std::move(image)));
  616. controller->show(Box<BackgroundPreviewBox>(controller, local));
  617. });
  618. FileDialog::GetOpenPath(
  619. parent.get(),
  620. tr::lng_choose_image(tr::now),
  621. filters.join(u";;"_q),
  622. crl::guard(parent, callback));
  623. }
  624. void SetupStickersEmoji(
  625. not_null<Window::SessionController*> controller,
  626. not_null<Ui::VerticalLayout*> container) {
  627. Ui::AddSkip(container);
  628. Ui::AddSubsectionTitle(container, tr::lng_settings_stickers_emoji());
  629. const auto session = &controller->session();
  630. auto wrap = object_ptr<Ui::VerticalLayout>(container);
  631. const auto inner = wrap.data();
  632. container->add(object_ptr<Ui::OverrideMargins>(
  633. container,
  634. std::move(wrap),
  635. QMargins(0, 0, 0, st::settingsCheckbox.margin.bottom())));
  636. const auto checkbox = [&](const QString &label, bool checked) {
  637. return object_ptr<Ui::Checkbox>(
  638. container,
  639. label,
  640. checked,
  641. st::settingsCheckbox);
  642. };
  643. const auto add = [&](const QString &label, bool checked, auto &&handle) {
  644. inner->add(
  645. checkbox(label, checked),
  646. st::settingsCheckboxPadding
  647. )->checkedChanges(
  648. ) | rpl::start_with_next(
  649. std::move(handle),
  650. inner->lifetime());
  651. };
  652. const auto addSliding = [&](
  653. const QString &label,
  654. bool checked,
  655. auto &&handle,
  656. rpl::producer<bool> shown) {
  657. inner->add(
  658. object_ptr<Ui::SlideWrap<Ui::Checkbox>>(
  659. inner,
  660. checkbox(label, checked),
  661. st::settingsCheckboxPadding)
  662. )->setDuration(0)->toggleOn(std::move(shown))->entity()->checkedChanges(
  663. ) | rpl::start_with_next(
  664. std::move(handle),
  665. inner->lifetime());
  666. };
  667. add(
  668. tr::lng_settings_large_emoji(tr::now),
  669. Core::App().settings().largeEmoji(),
  670. [=](bool checked) {
  671. Core::App().settings().setLargeEmoji(checked);
  672. Core::App().saveSettingsDelayed();
  673. });
  674. add(
  675. tr::lng_settings_replace_emojis(tr::now),
  676. Core::App().settings().replaceEmoji(),
  677. [=](bool checked) {
  678. Core::App().settings().setReplaceEmoji(checked);
  679. Core::App().saveSettingsDelayed();
  680. });
  681. const auto suggestEmoji = inner->lifetime().make_state<
  682. rpl::variable<bool>
  683. >(Core::App().settings().suggestEmoji());
  684. add(
  685. tr::lng_settings_suggest_emoji(tr::now),
  686. Core::App().settings().suggestEmoji(),
  687. [=](bool checked) {
  688. *suggestEmoji = checked;
  689. Core::App().settings().setSuggestEmoji(checked);
  690. Core::App().saveSettingsDelayed();
  691. });
  692. using namespace rpl::mappers;
  693. addSliding(
  694. tr::lng_settings_suggest_animated_emoji(tr::now),
  695. Core::App().settings().suggestAnimatedEmoji(),
  696. [=](bool checked) {
  697. Core::App().settings().setSuggestAnimatedEmoji(checked);
  698. Core::App().saveSettingsDelayed();
  699. },
  700. rpl::combine(
  701. Data::AmPremiumValue(session),
  702. suggestEmoji->value(),
  703. _1 && _2));
  704. add(
  705. tr::lng_settings_suggest_by_emoji(tr::now),
  706. Core::App().settings().suggestStickersByEmoji(),
  707. [=](bool checked) {
  708. Core::App().settings().setSuggestStickersByEmoji(checked);
  709. Core::App().saveSettingsDelayed();
  710. });
  711. add(
  712. tr::lng_settings_loop_stickers(tr::now),
  713. Core::App().settings().loopAnimatedStickers(),
  714. [=](bool checked) {
  715. Core::App().settings().setLoopAnimatedStickers(checked);
  716. Core::App().saveSettingsDelayed();
  717. });
  718. AddButtonWithIcon(
  719. container,
  720. tr::lng_stickers_you_have(),
  721. st::settingsButton,
  722. { &st::menuIconStickers }
  723. )->addClickHandler([=] {
  724. controller->show(Box<StickersBox>(
  725. controller->uiShow(),
  726. StickersBox::Section::Installed));
  727. });
  728. AddButtonWithIcon(
  729. container,
  730. tr::lng_emoji_manage_sets(),
  731. st::settingsButton,
  732. { &st::menuIconEmoji }
  733. )->addClickHandler([=] {
  734. controller->show(Box<Ui::Emoji::ManageSetsBox>(session));
  735. });
  736. Ui::AddSkip(container, st::settingsCheckboxesSkip);
  737. }
  738. void SetupMessages(
  739. not_null<Window::SessionController*> controller,
  740. not_null<Ui::VerticalLayout*> container) {
  741. Ui::AddDivider(container);
  742. Ui::AddSkip(container);
  743. Ui::AddSubsectionTitle(container, tr::lng_settings_messages());
  744. Ui::AddSkip(container, st::settingsSendTypeSkip);
  745. using SendByType = Ui::InputSubmitSettings;
  746. using Quick = HistoryView::DoubleClickQuickAction;
  747. const auto skip = st::settingsSendTypeSkip;
  748. auto wrap = object_ptr<Ui::VerticalLayout>(container);
  749. const auto inner = wrap.data();
  750. container->add(
  751. object_ptr<Ui::OverrideMargins>(
  752. container,
  753. std::move(wrap),
  754. QMargins(0, skip, 0, skip)));
  755. const auto groupSend = std::make_shared<Ui::RadioenumGroup<SendByType>>(
  756. Core::App().settings().sendSubmitWay());
  757. const auto addSend = [&](SendByType value, const QString &text) {
  758. inner->add(
  759. object_ptr<Ui::Radioenum<SendByType>>(
  760. inner,
  761. groupSend,
  762. value,
  763. text,
  764. st::settingsSendType),
  765. st::settingsSendTypePadding);
  766. };
  767. addSend(SendByType::Enter, tr::lng_settings_send_enter(tr::now));
  768. addSend(
  769. SendByType::CtrlEnter,
  770. (Platform::IsMac()
  771. ? tr::lng_settings_send_cmdenter(tr::now)
  772. : tr::lng_settings_send_ctrlenter(tr::now)));
  773. groupSend->setChangedCallback([=](SendByType value) {
  774. Core::App().settings().setSendSubmitWay(value);
  775. Core::App().saveSettingsDelayed();
  776. });
  777. Ui::AddSkip(inner, st::settingsCheckboxesSkip);
  778. const auto groupQuick = std::make_shared<Ui::RadioenumGroup<Quick>>(
  779. Core::App().settings().chatQuickAction());
  780. const auto addQuick = [&](Quick value, const QString &text) {
  781. return inner->add(
  782. object_ptr<Ui::Radioenum<Quick>>(
  783. inner,
  784. groupQuick,
  785. value,
  786. text,
  787. st::settingsSendType),
  788. st::settingsSendTypePadding);
  789. };
  790. addQuick(Quick::Reply, tr::lng_settings_chat_quick_action_reply(tr::now));
  791. const auto react = addQuick(
  792. Quick::React,
  793. tr::lng_settings_chat_quick_action_react(tr::now));
  794. const auto buttonRight = Ui::CreateSimpleCircleButton(
  795. inner,
  796. st::stickersRemove.ripple);
  797. buttonRight->resize(st::stickersRemove.width, st::stickersRemove.height);
  798. const auto toggleButtonRight = [=](bool value) {
  799. buttonRight->setAttribute(Qt::WA_TransparentForMouseEvents, !value);
  800. };
  801. toggleButtonRight(false);
  802. struct State {
  803. struct {
  804. std::vector<rpl::lifetime> lifetimes;
  805. bool flag = false;
  806. } icons;
  807. };
  808. const auto state = buttonRight->lifetime().make_state<State>();
  809. state->icons.lifetimes = std::vector<rpl::lifetime>(2);
  810. const auto &reactions = controller->session().data().reactions();
  811. auto idValue = rpl::single(
  812. reactions.favoriteId()
  813. ) | rpl::then(
  814. reactions.favoriteUpdates() | rpl::map([=] {
  815. return controller->session().data().reactions().favoriteId();
  816. })
  817. ) | rpl::filter([](const Data::ReactionId &id) {
  818. return !id.empty();
  819. });
  820. auto selected = rpl::duplicate(idValue);
  821. std::move(
  822. selected
  823. ) | rpl::start_with_next([=, idValue = std::move(idValue)](
  824. const Data::ReactionId &id) {
  825. const auto index = state->icons.flag ? 1 : 0;
  826. const auto iconSize = st::settingsReactionRightIcon;
  827. const auto &reactions = controller->session().data().reactions();
  828. const auto &list = reactions.list(Data::Reactions::Type::All);
  829. const auto i = ranges::find(list, id, &Data::Reaction::id);
  830. state->icons.lifetimes[index] = rpl::lifetime();
  831. if (i != end(list)) {
  832. AddReactionAnimatedIcon(
  833. inner,
  834. buttonRight->geometryValue(
  835. ) | rpl::map([=](const QRect &r) {
  836. return QPoint(
  837. r.left() + (r.width() - iconSize) / 2,
  838. r.top() + (r.height() - iconSize) / 2);
  839. }),
  840. iconSize,
  841. *i,
  842. buttonRight->events(
  843. ) | rpl::filter([=](not_null<QEvent*> event) {
  844. return event->type() == QEvent::Enter;
  845. }) | rpl::to_empty,
  846. rpl::duplicate(idValue) | rpl::skip(1) | rpl::to_empty,
  847. &state->icons.lifetimes[index]);
  848. } else if (const auto customId = id.custom()) {
  849. AddReactionCustomIcon(
  850. inner,
  851. buttonRight->geometryValue(
  852. ) | rpl::map([=](const QRect &r) {
  853. return QPoint(
  854. r.left() + (r.width() - iconSize) / 2,
  855. r.top() + (r.height() - iconSize) / 2);
  856. }),
  857. iconSize,
  858. controller,
  859. customId,
  860. rpl::duplicate(idValue) | rpl::skip(1) | rpl::to_empty,
  861. &state->icons.lifetimes[index]);
  862. }
  863. state->icons.flag = !state->icons.flag;
  864. toggleButtonRight(true);
  865. }, buttonRight->lifetime());
  866. react->geometryValue(
  867. ) | rpl::start_with_next([=](const QRect &r) {
  868. const auto rightSize = buttonRight->size();
  869. buttonRight->moveToRight(
  870. st::settingsButtonRightSkip,
  871. r.y() + (r.height() - rightSize.height()) / 2);
  872. }, buttonRight->lifetime());
  873. groupQuick->setChangedCallback([=](Quick value) {
  874. Core::App().settings().setChatQuickAction(value);
  875. Core::App().saveSettingsDelayed();
  876. });
  877. buttonRight->setClickedCallback([=, show = controller->uiShow()] {
  878. show->showBox(Box(ReactionsSettingsBox, controller));
  879. });
  880. Ui::AddSkip(inner, st::settingsSendTypeSkip);
  881. inner->add(
  882. object_ptr<Ui::Checkbox>(
  883. inner,
  884. tr::lng_settings_chat_corner_reaction(tr::now),
  885. Core::App().settings().cornerReaction(),
  886. st::settingsCheckbox),
  887. st::settingsCheckboxPadding
  888. )->checkedChanges(
  889. ) | rpl::start_with_next([=](bool checked) {
  890. Core::App().settings().setCornerReaction(checked);
  891. Core::App().saveSettingsDelayed();
  892. }, inner->lifetime());
  893. Ui::AddSkip(inner);
  894. }
  895. void SetupArchive(
  896. not_null<Window::SessionController*> controller,
  897. not_null<Ui::VerticalLayout*> container,
  898. Fn<void(Type)> showOther) {
  899. Ui::AddSkip(container);
  900. AddButtonWithIcon(
  901. container,
  902. tr::lng_settings_shortcuts(),
  903. st::settingsButton,
  904. { &st::menuIconShortcut }
  905. )->addClickHandler([=] {
  906. showOther(Shortcuts::Id());
  907. });
  908. PreloadArchiveSettings(&controller->session());
  909. AddButtonWithIcon(
  910. container,
  911. tr::lng_context_archive_settings(),
  912. st::settingsButton,
  913. { &st::menuIconArchive }
  914. )->addClickHandler([=] {
  915. controller->show(Box(Settings::ArchiveSettingsBox, controller));
  916. });
  917. }
  918. void SetupExport(
  919. not_null<Window::SessionController*> controller,
  920. not_null<Ui::VerticalLayout*> container,
  921. Fn<void(Type)> showOther) {
  922. AddButtonWithIcon(
  923. container,
  924. tr::lng_settings_export_data(),
  925. st::settingsButton,
  926. { &st::menuIconExport }
  927. )->addClickHandler([=] {
  928. const auto session = &controller->session();
  929. controller->window().hideSettingsAndLayer();
  930. base::call_delayed(
  931. st::boxDuration,
  932. session,
  933. [=] { Core::App().exportManager().start(session); });
  934. });
  935. AddButtonWithIcon(
  936. container,
  937. tr::lng_settings_experimental(),
  938. st::settingsButton,
  939. { &st::menuIconExperimental }
  940. )->addClickHandler([=] {
  941. showOther(Experimental::Id());
  942. });
  943. }
  944. void SetupLocalStorage(
  945. not_null<Window::SessionController*> controller,
  946. not_null<Ui::VerticalLayout*> container) {
  947. AddButtonWithIcon(
  948. container,
  949. tr::lng_settings_manage_local_storage(),
  950. st::settingsButton,
  951. { &st::menuIconStorage }
  952. )->addClickHandler([=] { LocalStorageBox::Show(controller); });
  953. }
  954. void SetupDataStorage(
  955. not_null<Window::SessionController*> controller,
  956. not_null<Ui::VerticalLayout*> container) {
  957. using namespace rpl::mappers;
  958. Ui::AddSkip(container);
  959. Ui::AddSubsectionTitle(container, tr::lng_settings_data_storage());
  960. SetupConnectionType(
  961. &controller->window(),
  962. &controller->session().account(),
  963. container);
  964. #ifndef OS_WIN_STORE
  965. const auto showpath = container->lifetime(
  966. ).make_state<rpl::event_stream<bool>>();
  967. const auto path = container->add(
  968. object_ptr<Ui::SlideWrap<Button>>(
  969. container,
  970. CreateButtonWithIcon(
  971. container,
  972. tr::lng_download_path(),
  973. st::settingsButton,
  974. { &st::menuIconShowInFolder })));
  975. auto pathtext = Core::App().settings().downloadPathValue(
  976. ) | rpl::map([](const QString &text) {
  977. if (text.isEmpty()) {
  978. return Core::App().canReadDefaultDownloadPath()
  979. ? tr::lng_download_path_default(tr::now)
  980. : tr::lng_download_path_temp(tr::now);
  981. } else if (text == FileDialog::Tmp()) {
  982. return tr::lng_download_path_temp(tr::now);
  983. }
  984. return QDir::toNativeSeparators(text);
  985. });
  986. CreateRightLabel(
  987. path->entity(),
  988. std::move(pathtext),
  989. st::settingsButton,
  990. tr::lng_download_path());
  991. path->entity()->addClickHandler([=] {
  992. controller->show(Box<DownloadPathBox>(controller));
  993. });
  994. #endif // OS_WIN_STORE
  995. SetupLocalStorage(controller, container);
  996. AddButtonWithIcon(
  997. container,
  998. tr::lng_downloads_section(),
  999. st::settingsButton,
  1000. { &st::menuIconDownload }
  1001. )->setClickedCallback([=] {
  1002. controller->showSection(
  1003. Info::Downloads::Make(controller->session().user()));
  1004. });
  1005. const auto ask = container->add(object_ptr<Ui::SettingsButton>(
  1006. container,
  1007. tr::lng_download_path_ask(),
  1008. st::settingsButtonNoIcon
  1009. ))->toggleOn(rpl::single(Core::App().settings().askDownloadPath()));
  1010. ask->toggledValue(
  1011. ) | rpl::filter([](bool checked) {
  1012. return (checked != Core::App().settings().askDownloadPath());
  1013. }) | rpl::start_with_next([=](bool checked) {
  1014. Core::App().settings().setAskDownloadPath(checked);
  1015. Core::App().saveSettingsDelayed();
  1016. #ifndef OS_WIN_STORE
  1017. showpath->fire_copy(!checked);
  1018. #endif // OS_WIN_STORE
  1019. }, ask->lifetime());
  1020. #ifndef OS_WIN_STORE
  1021. path->toggleOn(ask->toggledValue() | rpl::map(!_1));
  1022. #endif // OS_WIN_STORE
  1023. Ui::AddSkip(container, st::settingsCheckboxesSkip);
  1024. }
  1025. void SetupAutoDownload(
  1026. not_null<Window::SessionController*> controller,
  1027. not_null<Ui::VerticalLayout*> container) {
  1028. Ui::AddDivider(container);
  1029. Ui::AddSkip(container);
  1030. Ui::AddSubsectionTitle(container, tr::lng_media_auto_settings());
  1031. using Source = Data::AutoDownload::Source;
  1032. const auto add = [&](
  1033. rpl::producer<QString> label,
  1034. Source source,
  1035. IconDescriptor &&descriptor) {
  1036. AddButtonWithIcon(
  1037. container,
  1038. std::move(label),
  1039. st::settingsButton,
  1040. std::move(descriptor)
  1041. )->addClickHandler([=] {
  1042. controller->show(
  1043. Box<AutoDownloadBox>(&controller->session(), source));
  1044. });
  1045. };
  1046. add(
  1047. tr::lng_media_auto_in_private(),
  1048. Source::User,
  1049. { &st::menuIconProfile });
  1050. add(
  1051. tr::lng_media_auto_in_groups(),
  1052. Source::Group,
  1053. { &st::menuIconGroups });
  1054. add(
  1055. tr::lng_media_auto_in_channels(),
  1056. Source::Channel,
  1057. { &st::menuIconChannel });
  1058. Ui::AddSkip(container, st::settingsCheckboxesSkip);
  1059. }
  1060. void SetupChatBackground(
  1061. not_null<Window::SessionController*> controller,
  1062. not_null<Ui::VerticalLayout*> container) {
  1063. Ui::AddDivider(container);
  1064. Ui::AddSkip(container);
  1065. Ui::AddSubsectionTitle(container, tr::lng_settings_section_background());
  1066. container->add(
  1067. object_ptr<BackgroundRow>(container, controller),
  1068. st::settingsBackgroundPadding);
  1069. const auto skipTop = st::settingsCheckbox.margin.top();
  1070. const auto skipBottom = st::settingsCheckbox.margin.bottom();
  1071. auto wrap = object_ptr<Ui::VerticalLayout>(container);
  1072. const auto inner = wrap.data();
  1073. container->add(
  1074. object_ptr<Ui::OverrideMargins>(
  1075. container,
  1076. std::move(wrap),
  1077. QMargins(0, skipTop, 0, skipBottom)));
  1078. Ui::AddSkip(container, st::settingsTileSkip);
  1079. const auto background = Window::Theme::Background();
  1080. const auto tile = inner->add(
  1081. object_ptr<Ui::SlideWrap<Ui::Checkbox>>(
  1082. inner,
  1083. object_ptr<Ui::Checkbox>(
  1084. inner,
  1085. tr::lng_settings_bg_tile(tr::now),
  1086. background->tile(),
  1087. st::settingsCheckbox),
  1088. st::settingsSendTypePadding));
  1089. const auto adaptive = inner->add(
  1090. object_ptr<Ui::SlideWrap<Ui::Checkbox>>(
  1091. inner,
  1092. object_ptr<Ui::Checkbox>(
  1093. inner,
  1094. tr::lng_settings_adaptive_wide(tr::now),
  1095. Core::App().settings().adaptiveForWide(),
  1096. st::settingsCheckbox),
  1097. st::settingsSendTypePadding));
  1098. tile->entity()->checkedChanges(
  1099. ) | rpl::start_with_next([=](bool checked) {
  1100. background->setTile(checked);
  1101. }, tile->lifetime());
  1102. const auto shown = [=] {
  1103. return !background->paper().isPattern()
  1104. && !background->colorForFill();
  1105. };
  1106. tile->toggle(shown(), anim::type::instant);
  1107. using Update = const Window::Theme::BackgroundUpdate;
  1108. background->updates(
  1109. ) | rpl::filter([](const Update &update) {
  1110. return (update.type == Update::Type::Changed)
  1111. || (update.type == Update::Type::New);
  1112. }) | rpl::start_with_next([=] {
  1113. tile->entity()->setChecked(background->tile());
  1114. tile->toggle(shown(), anim::type::instant);
  1115. }, tile->lifetime());
  1116. adaptive->toggleOn(controller->adaptive().chatLayoutValue(
  1117. ) | rpl::map([](Window::Adaptive::ChatLayout layout) {
  1118. return (layout == Window::Adaptive::ChatLayout::Wide);
  1119. }));
  1120. adaptive->entity()->checkedChanges(
  1121. ) | rpl::start_with_next([=](bool checked) {
  1122. Core::App().settings().setAdaptiveForWide(checked);
  1123. Core::App().saveSettingsDelayed();
  1124. }, adaptive->lifetime());
  1125. }
  1126. void SetupChatListSwipe(
  1127. not_null<Window::SessionController*> controller,
  1128. not_null<Ui::VerticalLayout*> container) {
  1129. Ui::AddDivider(container);
  1130. Ui::AddSkip(container);
  1131. Ui::AddSubsectionTitle(
  1132. container,
  1133. tr::lng_settings_quick_dialog_action_title());
  1134. using Type = Dialogs::Ui::QuickDialogAction;
  1135. const auto group = std::make_shared<Ui::RadioenumGroup<Type>>(
  1136. Core::App().settings().quickDialogAction());
  1137. group->setChangedCallback([=](Type value) {
  1138. Core::App().settings().setQuickDialogAction(value);
  1139. Core::App().saveSettings();
  1140. });
  1141. container->add(
  1142. object_ptr<Ui::SettingsButton>(
  1143. container,
  1144. group->value() | rpl::map([](Type value) {
  1145. return ((value == Dialogs::Ui::QuickDialogAction::Mute)
  1146. ? tr::lng_settings_quick_dialog_action_mute
  1147. : (value == Dialogs::Ui::QuickDialogAction::Pin)
  1148. ? tr::lng_settings_quick_dialog_action_pin
  1149. : (value == Dialogs::Ui::QuickDialogAction::Read)
  1150. ? tr::lng_settings_quick_dialog_action_read
  1151. : (value == Dialogs::Ui::QuickDialogAction::Archive)
  1152. ? tr::lng_settings_quick_dialog_action_archive
  1153. : tr::lng_settings_quick_dialog_action_disabled)();
  1154. }) | rpl::flatten_latest(),
  1155. st::settingsButtonNoIcon)
  1156. )->setClickedCallback([=] {
  1157. controller->uiShow()->showBox(Box([=](not_null<Ui::GenericBox*> box) {
  1158. box->setTitle(tr::lng_settings_quick_dialog_action_title());
  1159. const auto addRadio = [&](Type value, tr::phrase<> phrase) {
  1160. box->verticalLayout()->add(
  1161. object_ptr<Ui::Radioenum<Type>>(
  1162. box->verticalLayout(),
  1163. group,
  1164. value,
  1165. phrase(tr::now),
  1166. st::settingsSendType),
  1167. st::settingsSendTypePadding);
  1168. };
  1169. addRadio(Type::Mute, tr::lng_settings_quick_dialog_action_mute);
  1170. addRadio(Type::Pin, tr::lng_settings_quick_dialog_action_pin);
  1171. addRadio(Type::Read, tr::lng_settings_quick_dialog_action_read);
  1172. addRadio(
  1173. Type::Archive,
  1174. tr::lng_settings_quick_dialog_action_archive);
  1175. addRadio(
  1176. Type::Delete,
  1177. tr::lng_settings_quick_dialog_action_delete);
  1178. addRadio(
  1179. Type::Disabled,
  1180. tr::lng_settings_quick_dialog_action_disabled);
  1181. box->addButton(tr::lng_box_ok(), [=] { box->closeBox(); });
  1182. }));
  1183. });
  1184. Ui::AddSkip(container);
  1185. Ui::AddDividerText(
  1186. container,
  1187. tr::lng_settings_quick_dialog_action_about());
  1188. Ui::AddSkip(container);
  1189. }
  1190. void SetupDefaultThemes(
  1191. not_null<Window::Controller*> window,
  1192. not_null<Ui::VerticalLayout*> container) {
  1193. using Type = Window::Theme::EmbeddedType;
  1194. using Scheme = Window::Theme::EmbeddedScheme;
  1195. using Check = Window::Theme::CloudListCheck;
  1196. using namespace Window::Theme;
  1197. const auto block = container->add(object_ptr<Ui::FixedHeightWidget>(
  1198. container));
  1199. const auto palette = Ui::CreateChild<ColorsPalette>(
  1200. container.get(),
  1201. container.get());
  1202. const auto chosen = [] {
  1203. const auto &object = Background()->themeObject();
  1204. if (object.cloud.id) {
  1205. return Type(-1);
  1206. }
  1207. for (const auto &scheme : kSchemesList) {
  1208. if (object.pathAbsolute == scheme.path) {
  1209. return scheme.type;
  1210. }
  1211. }
  1212. return Type(-1);
  1213. };
  1214. const auto group = std::make_shared<Ui::RadioenumGroup<Type>>(chosen());
  1215. const auto apply = [=](const Scheme &scheme) {
  1216. const auto isNight = [](const Scheme &scheme) {
  1217. const auto type = scheme.type;
  1218. return (type != Type::DayBlue) && (type != Type::Default);
  1219. };
  1220. const auto currentlyIsCustom = (chosen() == Type(-1))
  1221. && !Background()->themeObject().cloud.id;
  1222. const auto keep = [=] {
  1223. if (!currentlyIsCustom) {
  1224. KeepApplied();
  1225. }
  1226. };
  1227. if (IsNightMode() == isNight(scheme)) {
  1228. ApplyDefaultWithPath(scheme.path);
  1229. keep();
  1230. } else {
  1231. Window::Theme::ToggleNightModeWithConfirmation(
  1232. window,
  1233. [=, path = scheme.path] { ToggleNightMode(path); keep();});
  1234. }
  1235. };
  1236. const auto schemeClicked = [=](
  1237. const Scheme &scheme,
  1238. Qt::KeyboardModifiers modifiers) {
  1239. apply(scheme);
  1240. };
  1241. auto checks = base::flat_map<Type,not_null<Check*>>();
  1242. auto buttons = ranges::views::all(
  1243. kSchemesList
  1244. ) | ranges::views::transform([&](const Scheme &scheme) {
  1245. auto check = std::make_unique<Check>(
  1246. ColorsFromScheme(scheme),
  1247. false);
  1248. const auto weak = check.get();
  1249. const auto result = Ui::CreateChild<Ui::Radioenum<Type>>(
  1250. block,
  1251. group,
  1252. scheme.type,
  1253. QString(),
  1254. st::settingsTheme,
  1255. std::move(check));
  1256. rpl::duplicate(
  1257. scheme.name
  1258. ) | rpl::start_with_next([=](const QString &themeName) {
  1259. result->setText(themeName);
  1260. }, result->lifetime());
  1261. result->addClickHandler([=] {
  1262. schemeClicked(scheme, result->clickModifiers());
  1263. });
  1264. weak->setUpdateCallback([=] { result->update(); });
  1265. checks.emplace(scheme.type, weak);
  1266. return result;
  1267. }) | ranges::to_vector;
  1268. const auto refreshColorizer = [=](Type type) {
  1269. if (type == chosen()) {
  1270. palette->show(type);
  1271. }
  1272. const auto &colors = Core::App().settings().themesAccentColors();
  1273. const auto i = checks.find(type);
  1274. const auto scheme = ranges::find(kSchemesList, type, &Scheme::type);
  1275. if (scheme == end(kSchemesList)) {
  1276. return;
  1277. }
  1278. if (i != end(checks)) {
  1279. if (const auto color = colors.get(type)) {
  1280. const auto colorizer = ColorizerFrom(*scheme, *color);
  1281. i->second->setColors(ColorsFromScheme(*scheme, colorizer));
  1282. } else {
  1283. i->second->setColors(ColorsFromScheme(*scheme));
  1284. }
  1285. }
  1286. };
  1287. group->setChangedCallback([=](Type type) {
  1288. group->setValue(chosen());
  1289. });
  1290. for (const auto &scheme : kSchemesList) {
  1291. refreshColorizer(scheme.type);
  1292. }
  1293. Background()->updates(
  1294. ) | rpl::filter([](const BackgroundUpdate &update) {
  1295. return (update.type == BackgroundUpdate::Type::ApplyingTheme);
  1296. }) | rpl::map([=] {
  1297. return chosen();
  1298. }) | rpl::start_with_next([=](Type type) {
  1299. refreshColorizer(type);
  1300. group->setValue(type);
  1301. }, container->lifetime());
  1302. for (const auto button : buttons) {
  1303. button->setCheckAlignment(style::al_top);
  1304. button->resizeToWidth(button->width());
  1305. }
  1306. block->resize(block->width(), buttons[0]->height());
  1307. block->widthValue(
  1308. ) | rpl::start_with_next([buttons = std::move(buttons)](int width) {
  1309. Expects(!buttons.empty());
  1310. const auto padding = st::settingsButtonNoIcon.padding;
  1311. width -= padding.left() + padding.right();
  1312. const auto desired = st::settingsThemePreviewSize.width();
  1313. const auto count = int(buttons.size());
  1314. const auto skips = count - 1;
  1315. const auto minSkip = st::settingsThemeMinSkip;
  1316. const auto single = [&] {
  1317. if (width >= skips * minSkip + count * desired) {
  1318. return desired;
  1319. }
  1320. return (width - skips * minSkip) / count;
  1321. }();
  1322. if (single <= 0) {
  1323. return;
  1324. }
  1325. const auto fullSkips = width - count * single;
  1326. const auto skip = fullSkips / float64(skips);
  1327. auto left = padding.left() + 0.;
  1328. for (const auto button : buttons) {
  1329. button->resizeToWidth(single);
  1330. button->moveToLeft(int(base::SafeRound(left)), 0);
  1331. left += button->width() + skip;
  1332. }
  1333. }, block->lifetime());
  1334. palette->selected(
  1335. ) | rpl::start_with_next([=](QColor color) {
  1336. if (Background()->editingTheme()) {
  1337. // We don't remember old accent color to revert it properly
  1338. // in Window::Theme::Revert which is called by Editor.
  1339. //
  1340. // So we check here, before we change the saved accent color.
  1341. window->show(Ui::MakeInformBox(
  1342. tr::lng_theme_editor_cant_change_theme()));
  1343. return;
  1344. }
  1345. const auto type = chosen();
  1346. const auto scheme = ranges::find(kSchemesList, type, &Scheme::type);
  1347. if (scheme == end(kSchemesList)) {
  1348. return;
  1349. }
  1350. auto &colors = Core::App().settings().themesAccentColors();
  1351. if (colors.get(type) != color) {
  1352. colors.set(type, color);
  1353. Local::writeSettings();
  1354. }
  1355. apply(*scheme);
  1356. }, container->lifetime());
  1357. Ui::AddSkip(container);
  1358. }
  1359. void SetupThemeOptions(
  1360. not_null<Window::SessionController*> controller,
  1361. not_null<Ui::VerticalLayout*> container) {
  1362. using namespace Window::Theme;
  1363. Ui::AddSkip(container, st::settingsPrivacySkip);
  1364. Ui::AddSubsectionTitle(container, tr::lng_settings_themes());
  1365. Ui::AddSkip(container, st::settingsThemesTopSkip);
  1366. SetupDefaultThemes(&controller->window(), container);
  1367. Ui::AddSkip(container);
  1368. }
  1369. void SetupCloudThemes(
  1370. not_null<Window::SessionController*> controller,
  1371. not_null<Ui::VerticalLayout*> container) {
  1372. using namespace Window::Theme;
  1373. using namespace rpl::mappers;
  1374. const auto wrap = container->add(
  1375. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  1376. container,
  1377. object_ptr<Ui::VerticalLayout>(container))
  1378. )->setDuration(0);
  1379. const auto inner = wrap->entity();
  1380. Ui::AddDivider(inner);
  1381. Ui::AddSkip(inner, st::settingsPrivacySkip);
  1382. const auto title = AddSubsectionTitle(
  1383. inner,
  1384. tr::lng_settings_bg_cloud_themes());
  1385. const auto showAll = Ui::CreateChild<Ui::LinkButton>(
  1386. inner,
  1387. tr::lng_settings_bg_show_all(tr::now));
  1388. rpl::combine(
  1389. title->topValue(),
  1390. inner->widthValue(),
  1391. showAll->widthValue()
  1392. ) | rpl::start_with_next([=](int top, int outerWidth, int width) {
  1393. showAll->moveToRight(
  1394. st::defaultSubsectionTitlePadding.left(),
  1395. top,
  1396. outerWidth);
  1397. }, showAll->lifetime());
  1398. Ui::AddSkip(inner, st::settingsThemesTopSkip);
  1399. const auto list = inner->lifetime().make_state<CloudList>(
  1400. inner,
  1401. controller);
  1402. inner->add(
  1403. list->takeWidget(),
  1404. style::margins(
  1405. st::settingsButtonNoIcon.padding.left(),
  1406. 0,
  1407. st::settingsButtonNoIcon.padding.right(),
  1408. 0));
  1409. list->allShown(
  1410. ) | rpl::start_with_next([=](bool shown) {
  1411. showAll->setVisible(!shown);
  1412. }, showAll->lifetime());
  1413. showAll->addClickHandler([=] {
  1414. list->showAll();
  1415. });
  1416. const auto editWrap = inner->add(
  1417. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  1418. inner,
  1419. object_ptr<Ui::VerticalLayout>(inner))
  1420. )->setDuration(0);
  1421. const auto edit = editWrap->entity();
  1422. Ui::AddSkip(edit, st::settingsThemesBottomSkip);
  1423. AddButtonWithIcon(
  1424. edit,
  1425. tr::lng_settings_bg_theme_edit(),
  1426. st::settingsButton,
  1427. { &st::menuIconPalette }
  1428. )->addClickHandler([=] {
  1429. StartEditor(
  1430. &controller->window(),
  1431. Background()->themeObject().cloud);
  1432. });
  1433. editWrap->toggleOn(rpl::single(BackgroundUpdate(
  1434. BackgroundUpdate::Type::ApplyingTheme,
  1435. Background()->tile()
  1436. )) | rpl::then(
  1437. Background()->updates()
  1438. ) | rpl::filter([](const BackgroundUpdate &update) {
  1439. return (update.type == BackgroundUpdate::Type::ApplyingTheme);
  1440. }) | rpl::map([=] {
  1441. const auto userId = controller->session().userId();
  1442. return (Background()->themeObject().cloud.createdBy == userId);
  1443. }));
  1444. Ui::AddSkip(inner, 2 * st::defaultVerticalListSkip);
  1445. wrap->setDuration(0)->toggleOn(list->empty() | rpl::map(!_1));
  1446. }
  1447. void SetupThemeSettings(
  1448. not_null<Window::SessionController*> controller,
  1449. not_null<Ui::VerticalLayout*> container) {
  1450. Ui::AddDivider(container);
  1451. Ui::AddSkip(container, st::settingsPrivacySkip);
  1452. Ui::AddSubsectionTitle(container, tr::lng_settings_theme_settings());
  1453. AddPeerColorButton(
  1454. container,
  1455. controller->uiShow(),
  1456. controller->session().user(),
  1457. st::settingsColorButton);
  1458. const auto settings = &Core::App().settings();
  1459. if (settings->systemDarkMode().has_value()) {
  1460. auto label = settings->systemDarkModeEnabledValue(
  1461. ) | rpl::map([=](bool enabled) {
  1462. return enabled
  1463. ? tr::lng_settings_auto_night_mode_on()
  1464. : tr::lng_settings_auto_night_mode_off();
  1465. }) | rpl::flatten_latest();
  1466. AddButtonWithLabel(
  1467. container,
  1468. tr::lng_settings_auto_night_mode(),
  1469. std::move(label),
  1470. st::settingsButton,
  1471. { &st::menuIconNightMode }
  1472. )->setClickedCallback([=] {
  1473. const auto now = !settings->systemDarkModeEnabled();
  1474. if (now && Window::Theme::Background()->editingTheme()) {
  1475. controller->show(Ui::MakeInformBox(
  1476. tr::lng_theme_editor_cant_change_theme()));
  1477. } else {
  1478. settings->setSystemDarkModeEnabled(now);
  1479. Core::App().saveSettingsDelayed();
  1480. }
  1481. });
  1482. }
  1483. const auto family = container->lifetime().make_state<
  1484. rpl::variable<QString>
  1485. >(settings->customFontFamily());
  1486. auto label = family->value() | rpl::map([](QString family) {
  1487. return family.isEmpty()
  1488. ? tr::lng_font_default(tr::now)
  1489. : (family == style::SystemFontTag())
  1490. ? tr::lng_font_system(tr::now)
  1491. : family;
  1492. });
  1493. AddButtonWithLabel(
  1494. container,
  1495. tr::lng_settings_font_family(),
  1496. std::move(label),
  1497. st::settingsButton,
  1498. { &st::menuIconFont }
  1499. )->setClickedCallback([=] {
  1500. const auto save = [=](QString chosen) {
  1501. *family = chosen;
  1502. settings->setCustomFontFamily(chosen);
  1503. Local::writeSettings();
  1504. Core::Restart();
  1505. };
  1506. const auto theme = std::shared_ptr<Ui::ChatTheme>(
  1507. Window::Theme::DefaultChatThemeOn(container->lifetime()));
  1508. const auto generateBg = [=] {
  1509. const auto size = st::boxWidth;
  1510. const auto ratio = style::DevicePixelRatio();
  1511. auto result = QImage(
  1512. QSize(size, size) * ratio,
  1513. QImage::Format_ARGB32_Premultiplied);
  1514. auto p = QPainter(&result);
  1515. Window::SectionWidget::PaintBackground(
  1516. p,
  1517. theme.get(),
  1518. QSize(size, size * 3),
  1519. QRect(0, 0, size, size));
  1520. p.end();
  1521. return result;
  1522. };
  1523. controller->show(
  1524. Box(Ui::ChooseFontBox, generateBg, family->current(), save));
  1525. });
  1526. Ui::AddSkip(container, st::settingsCheckboxesSkip);
  1527. }
  1528. void SetupSupportSwitchSettings(
  1529. not_null<Window::SessionController*> controller,
  1530. not_null<Ui::VerticalLayout*> container) {
  1531. using SwitchType = Support::SwitchSettings;
  1532. const auto group = std::make_shared<Ui::RadioenumGroup<SwitchType>>(
  1533. controller->session().settings().supportSwitch());
  1534. const auto add = [&](SwitchType value, const QString &label) {
  1535. container->add(
  1536. object_ptr<Ui::Radioenum<SwitchType>>(
  1537. container,
  1538. group,
  1539. value,
  1540. label,
  1541. st::settingsSendType),
  1542. st::settingsSendTypePadding);
  1543. };
  1544. add(SwitchType::None, "Just send the reply");
  1545. add(SwitchType::Next, "Send and switch to next");
  1546. add(SwitchType::Previous, "Send and switch to previous");
  1547. group->setChangedCallback([=](SwitchType value) {
  1548. controller->session().settings().setSupportSwitch(value);
  1549. controller->session().saveSettingsDelayed();
  1550. });
  1551. }
  1552. void SetupSupportChatsLimitSlice(
  1553. not_null<Window::SessionController*> controller,
  1554. not_null<Ui::VerticalLayout*> container) {
  1555. constexpr auto kDayDuration = 24 * 60 * 60;
  1556. struct Option {
  1557. int days = 0;
  1558. QString label;
  1559. };
  1560. const auto options = std::vector<Option>{
  1561. { 1, "1 day" },
  1562. { 7, "1 week" },
  1563. { 30, "1 month" },
  1564. { 365, "1 year" },
  1565. { 0, "All of them" },
  1566. };
  1567. const auto current = controller->session().settings().supportChatsTimeSlice();
  1568. const auto days = current / kDayDuration;
  1569. const auto best = ranges::min_element(
  1570. options,
  1571. std::less<>(),
  1572. [&](const Option &option) { return std::abs(option.days - days); });
  1573. const auto group = std::make_shared<Ui::RadiobuttonGroup>(best->days);
  1574. for (const auto &option : options) {
  1575. container->add(
  1576. object_ptr<Ui::Radiobutton>(
  1577. container,
  1578. group,
  1579. option.days,
  1580. option.label,
  1581. st::settingsSendType),
  1582. st::settingsSendTypePadding);
  1583. }
  1584. group->setChangedCallback([=](int days) {
  1585. controller->session().settings().setSupportChatsTimeSlice(
  1586. days * kDayDuration);
  1587. controller->session().saveSettingsDelayed();
  1588. });
  1589. }
  1590. void SetupSupport(
  1591. not_null<Window::SessionController*> controller,
  1592. not_null<Ui::VerticalLayout*> container) {
  1593. Ui::AddSkip(container);
  1594. Ui::AddSubsectionTitle(container, rpl::single(u"Support settings"_q));
  1595. Ui::AddSkip(container, st::settingsSendTypeSkip);
  1596. const auto skip = st::settingsSendTypeSkip;
  1597. auto wrap = object_ptr<Ui::VerticalLayout>(container);
  1598. const auto inner = wrap.data();
  1599. container->add(
  1600. object_ptr<Ui::OverrideMargins>(
  1601. container,
  1602. std::move(wrap),
  1603. QMargins(0, skip, 0, skip)));
  1604. SetupSupportSwitchSettings(controller, inner);
  1605. Ui::AddSkip(inner, st::settingsCheckboxesSkip);
  1606. inner->add(
  1607. object_ptr<Ui::Checkbox>(
  1608. inner,
  1609. "Enable templates autocomplete",
  1610. controller->session().settings().supportTemplatesAutocomplete(),
  1611. st::settingsCheckbox),
  1612. st::settingsSendTypePadding
  1613. )->checkedChanges(
  1614. ) | rpl::start_with_next([=](bool checked) {
  1615. controller->session().settings().setSupportTemplatesAutocomplete(
  1616. checked);
  1617. controller->session().saveSettingsDelayed();
  1618. }, inner->lifetime());
  1619. inner->add(
  1620. object_ptr<Ui::Checkbox>(
  1621. inner,
  1622. "Send all messages without sound",
  1623. controller->session().settings().supportAllSilent(),
  1624. st::settingsCheckbox),
  1625. st::settingsSendTypePadding
  1626. )->checkedChanges(
  1627. ) | rpl::start_with_next([=](bool checked) {
  1628. controller->session().settings().setSupportAllSilent(
  1629. checked);
  1630. controller->session().saveSettingsDelayed();
  1631. }, inner->lifetime());
  1632. Ui::AddSkip(inner, st::settingsCheckboxesSkip);
  1633. Ui::AddSubsectionTitle(inner, rpl::single(u"Load chats for a period"_q));
  1634. SetupSupportChatsLimitSlice(controller, inner);
  1635. Ui::AddSkip(inner, st::settingsCheckboxesSkip);
  1636. Ui::AddSkip(inner);
  1637. }
  1638. Chat::Chat(QWidget *parent, not_null<Window::SessionController*> controller)
  1639. : Section(parent)
  1640. , _controller(controller) {
  1641. setupContent(controller);
  1642. }
  1643. rpl::producer<QString> Chat::title() {
  1644. return tr::lng_settings_section_chat_settings();
  1645. }
  1646. void Chat::fillTopBarMenu(const Ui::Menu::MenuCallback &addAction) {
  1647. const auto window = &_controller->window();
  1648. addAction(
  1649. tr::lng_settings_bg_theme_create(tr::now),
  1650. [=] { window->show(Box(Window::Theme::CreateBox, window)); },
  1651. &st::menuIconChangeColors);
  1652. }
  1653. void Chat::setupContent(not_null<Window::SessionController*> controller) {
  1654. const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
  1655. auto updateOnTick = rpl::single(
  1656. ) | rpl::then(base::timer_each(60 * crl::time(1000)));
  1657. SetupThemeOptions(controller, content);
  1658. SetupThemeSettings(controller, content);
  1659. SetupCloudThemes(controller, content);
  1660. SetupChatBackground(controller, content);
  1661. SetupChatListSwipe(controller, content);
  1662. SetupStickersEmoji(controller, content);
  1663. SetupMessages(controller, content);
  1664. Ui::AddDivider(content);
  1665. SetupSensitiveContent(controller, content, std::move(updateOnTick));
  1666. SetupArchive(controller, content, showOtherMethod());
  1667. Ui::ResizeFitChild(this, content);
  1668. }
  1669. } // namespace Settings