settings_notifications.cpp 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323
  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_notifications.h"
  8. #include "settings/settings_notifications_type.h"
  9. #include "ui/boxes/confirm_box.h"
  10. #include "ui/controls/chat_service_checkbox.h"
  11. #include "ui/effects/animations.h"
  12. #include "ui/text/text_utilities.h"
  13. #include "ui/wrap/vertical_layout.h"
  14. #include "ui/wrap/slide_wrap.h"
  15. #include "ui/widgets/box_content_divider.h"
  16. #include "ui/widgets/checkbox.h"
  17. #include "ui/widgets/buttons.h"
  18. #include "ui/widgets/discrete_sliders.h"
  19. #include "ui/painter.h"
  20. #include "ui/vertical_list.h"
  21. #include "ui/ui_utility.h"
  22. #include "lang/lang_keys.h"
  23. #include "window/notifications_manager.h"
  24. #include "window/window_session_controller.h"
  25. #include "window/section_widget.h"
  26. #include "platform/platform_specific.h"
  27. #include "platform/platform_notifications_manager.h"
  28. #include "base/platform/base_platform_info.h"
  29. #include "mainwindow.h"
  30. #include "core/application.h"
  31. #include "main/main_session.h"
  32. #include "main/main_account.h"
  33. #include "main/main_domain.h"
  34. #include "api/api_authorizations.h"
  35. #include "api/api_ringtones.h"
  36. #include "data/data_chat_filters.h"
  37. #include "data/data_session.h"
  38. #include "data/data_document.h"
  39. #include "data/notify/data_notify_settings.h"
  40. #include "boxes/ringtones_box.h"
  41. #include "apiwrap.h"
  42. #include "styles/style_settings.h"
  43. #include "styles/style_boxes.h"
  44. #include "styles/style_layers.h"
  45. #include "styles/style_menu_icons.h"
  46. #include "styles/style_chat.h"
  47. #include "styles/style_window.h"
  48. #include "styles/style_dialogs.h"
  49. #include <QSvgRenderer>
  50. namespace Settings {
  51. namespace {
  52. constexpr auto kMaxNotificationsCount = 5;
  53. [[nodiscard]] int CurrentCount() {
  54. return std::clamp(
  55. Core::App().settings().notificationsCount(),
  56. 1,
  57. kMaxNotificationsCount);
  58. }
  59. using ChangeType = Window::Notifications::ChangeType;
  60. class NotificationsCount : public Ui::RpWidget {
  61. public:
  62. NotificationsCount(
  63. QWidget *parent,
  64. not_null<Window::SessionController*> controller);
  65. void setCount(int count);
  66. ~NotificationsCount();
  67. protected:
  68. void paintEvent(QPaintEvent *e) override;
  69. void mousePressEvent(QMouseEvent *e) override;
  70. void mouseMoveEvent(QMouseEvent *e) override;
  71. void leaveEventHook(QEvent *e) override;
  72. void mouseReleaseEvent(QMouseEvent *e) override;
  73. int resizeGetHeight(int newWidth) override;
  74. private:
  75. using ScreenCorner = Core::Settings::ScreenCorner;
  76. void setOverCorner(ScreenCorner corner);
  77. void clearOverCorner();
  78. class SampleWidget;
  79. void removeSample(SampleWidget *widget);
  80. QRect getScreenRect() const;
  81. QRect getScreenRect(int width) const;
  82. int getContentLeft() const;
  83. void prepareNotificationSampleSmall();
  84. void prepareNotificationSampleLarge();
  85. void prepareNotificationSampleUserpic();
  86. const not_null<Window::SessionController*> _controller;
  87. QPixmap _notificationSampleUserpic;
  88. QPixmap _notificationSampleSmall;
  89. QPixmap _notificationSampleLarge;
  90. ScreenCorner _chosenCorner;
  91. std::vector<Ui::Animations::Simple> _sampleOpacities;
  92. bool _isOverCorner = false;
  93. ScreenCorner _overCorner = ScreenCorner::TopLeft;
  94. bool _isDownCorner = false;
  95. ScreenCorner _downCorner = ScreenCorner::TopLeft;
  96. int _oldCount;
  97. std::vector<SampleWidget*> _cornerSamples[4];
  98. };
  99. class NotificationsCount::SampleWidget : public QWidget {
  100. public:
  101. SampleWidget(NotificationsCount *owner, const QPixmap &cache);
  102. void detach();
  103. void showFast();
  104. void hideFast();
  105. protected:
  106. void paintEvent(QPaintEvent *e) override;
  107. private:
  108. void startAnimation();
  109. void animationCallback();
  110. NotificationsCount *_owner;
  111. QPixmap _cache;
  112. Ui::Animations::Simple _opacity;
  113. bool _hiding = false;
  114. };
  115. void AddTypeButton(
  116. not_null<Ui::VerticalLayout*> container,
  117. not_null<Window::SessionController*> controller,
  118. Data::DefaultNotify type,
  119. Fn<void(Type)> showOther) {
  120. using Type = Data::DefaultNotify;
  121. auto label = [&] {
  122. switch (type) {
  123. case Type::User: return tr::lng_notification_private_chats();
  124. case Type::Group: return tr::lng_notification_groups();
  125. case Type::Broadcast: return tr::lng_notification_channels();
  126. }
  127. Unexpected("Type value in AddTypeButton.");
  128. }();
  129. const auto icon = [&] {
  130. switch (type) {
  131. case Type::User: return &st::menuIconProfile;
  132. case Type::Group: return &st::menuIconGroups;
  133. case Type::Broadcast: return &st::menuIconChannel;
  134. }
  135. Unexpected("Type value in AddTypeButton.");
  136. }();
  137. const auto button = AddButtonWithIcon(
  138. container,
  139. std::move(label),
  140. st::settingsNotificationType,
  141. { icon });
  142. button->setClickedCallback([=] {
  143. showOther(NotificationsType::Id(type));
  144. });
  145. const auto session = &controller->session();
  146. const auto settings = &session->data().notifySettings();
  147. const auto &st = st::settingsNotificationType;
  148. auto status = rpl::combine(
  149. NotificationsEnabledForTypeValue(session, type),
  150. rpl::single(
  151. type
  152. ) | rpl::then(settings->exceptionsUpdates(
  153. ) | rpl::filter(rpl::mappers::_1 == type))
  154. ) | rpl::map([=](bool enabled, const auto &) {
  155. const auto count = int(settings->exceptions(type).size());
  156. return !count
  157. ? tr::lng_notification_click_to_change()
  158. : (enabled
  159. ? tr::lng_notification_on
  160. : tr::lng_notification_off)(
  161. lt_exceptions,
  162. tr::lng_notification_exceptions(
  163. lt_count,
  164. rpl::single(float64(count))));
  165. }) | rpl::flatten_latest();
  166. const auto details = Ui::CreateChild<Ui::FlatLabel>(
  167. button.get(),
  168. std::move(status),
  169. st::settingsNotificationTypeDetails);
  170. details->show();
  171. details->moveToLeft(
  172. st.padding.left(),
  173. st.padding.top() + st.height - details->height());
  174. details->setAttribute(Qt::WA_TransparentForMouseEvents);
  175. const auto toggleButton = Ui::CreateChild<Ui::SettingsButton>(
  176. container.get(),
  177. nullptr,
  178. st);
  179. const auto checkView = button->lifetime().make_state<Ui::ToggleView>(
  180. st.toggle,
  181. NotificationsEnabledForType(session, type),
  182. [=] { toggleButton->update(); });
  183. const auto separator = Ui::CreateChild<Ui::RpWidget>(container.get());
  184. separator->paintRequest(
  185. ) | rpl::start_with_next([=, bg = st.textBgOver] {
  186. auto p = QPainter(separator);
  187. p.fillRect(separator->rect(), bg);
  188. }, separator->lifetime());
  189. const auto separatorHeight = st.height - 2 * st.toggle.border;
  190. button->geometryValue(
  191. ) | rpl::start_with_next([=](const QRect &r) {
  192. const auto w = st::rightsButtonToggleWidth;
  193. toggleButton->setGeometry(
  194. r.x() + r.width() - w,
  195. r.y(),
  196. w,
  197. r.height());
  198. separator->setGeometry(
  199. toggleButton->x() - st::lineWidth,
  200. r.y() + (r.height() - separatorHeight) / 2,
  201. st::lineWidth,
  202. separatorHeight);
  203. }, toggleButton->lifetime());
  204. const auto checkWidget = Ui::CreateChild<Ui::RpWidget>(toggleButton);
  205. checkWidget->resize(checkView->getSize());
  206. checkWidget->paintRequest(
  207. ) | rpl::start_with_next([=] {
  208. auto p = QPainter(checkWidget);
  209. checkView->paint(p, 0, 0, checkWidget->width());
  210. }, checkWidget->lifetime());
  211. toggleButton->sizeValue(
  212. ) | rpl::start_with_next([=](const QSize &s) {
  213. checkWidget->moveToRight(
  214. st.toggleSkip,
  215. (s.height() - checkWidget->height()) / 2);
  216. }, toggleButton->lifetime());
  217. const auto toggle = crl::guard(toggleButton, [=] {
  218. const auto enabled = !checkView->checked();
  219. checkView->setChecked(enabled, anim::type::normal);
  220. settings->defaultUpdate(type, Data::MuteValue{
  221. .unmute = enabled,
  222. .forever = !enabled,
  223. });
  224. });
  225. toggleButton->clicks(
  226. ) | rpl::start_with_next([=] {
  227. const auto count = int(settings->exceptions(type).size());
  228. if (!count) {
  229. toggle();
  230. } else {
  231. controller->show(Box([=](not_null<Ui::GenericBox*> box) {
  232. const auto phrase = [&] {
  233. switch (type) {
  234. case Type::User:
  235. return tr::lng_notification_about_private_chats;
  236. case Type::Group:
  237. return tr::lng_notification_about_groups;
  238. case Type::Broadcast:
  239. return tr::lng_notification_about_channels;
  240. }
  241. Unexpected("Type in AddTypeButton.");
  242. }();
  243. Ui::ConfirmBox(box, {
  244. .text = phrase(
  245. lt_count,
  246. rpl::single(float64(count)),
  247. Ui::Text::RichLangValue),
  248. .confirmed = [=](auto close) { toggle(); close(); },
  249. .confirmText = tr::lng_box_ok(),
  250. .title = tr::lng_notification_exceptions_title(),
  251. .inform = true,
  252. });
  253. box->addLeftButton(
  254. tr::lng_notification_exceptions_view(),
  255. [=] {
  256. box->closeBox();
  257. showOther(NotificationsType::Id(type));
  258. });
  259. }));
  260. }
  261. }, toggleButton->lifetime());
  262. }
  263. NotificationsCount::NotificationsCount(
  264. QWidget *parent,
  265. not_null<Window::SessionController*> controller)
  266. : _controller(controller)
  267. , _chosenCorner(Core::App().settings().notificationsCorner())
  268. , _oldCount(CurrentCount()) {
  269. setMouseTracking(true);
  270. _sampleOpacities.resize(kMaxNotificationsCount);
  271. prepareNotificationSampleSmall();
  272. prepareNotificationSampleLarge();
  273. }
  274. void NotificationsCount::paintEvent(QPaintEvent *e) {
  275. Painter p(this);
  276. auto contentLeft = getContentLeft();
  277. auto screenRect = getScreenRect();
  278. p.fillRect(
  279. screenRect.x(),
  280. screenRect.y(),
  281. st::notificationsBoxScreenSize.width(),
  282. st::notificationsBoxScreenSize.height(),
  283. st::notificationsBoxScreenBg);
  284. auto monitorTop = 0;
  285. st::notificationsBoxMonitor.paint(p, contentLeft, monitorTop, width());
  286. for (int corner = 0; corner != 4; ++corner) {
  287. auto screenCorner = static_cast<ScreenCorner>(corner);
  288. auto isLeft = Core::Settings::IsLeftCorner(screenCorner);
  289. auto isTop = Core::Settings::IsTopCorner(screenCorner);
  290. auto sampleLeft = isLeft ? (screenRect.x() + st::notificationsSampleSkip) : (screenRect.x() + screenRect.width() - st::notificationsSampleSkip - st::notificationSampleSize.width());
  291. auto sampleTop = isTop ? (screenRect.y() + st::notificationsSampleTopSkip) : (screenRect.y() + screenRect.height() - st::notificationsSampleBottomSkip - st::notificationSampleSize.height());
  292. if (corner == static_cast<int>(_chosenCorner)) {
  293. auto count = _oldCount;
  294. for (int i = 0; i != kMaxNotificationsCount; ++i) {
  295. auto opacity = _sampleOpacities[i].value((i < count) ? 1. : 0.);
  296. p.setOpacity(opacity);
  297. p.drawPixmapLeft(sampleLeft, sampleTop, width(), _notificationSampleSmall);
  298. sampleTop += (isTop ? 1 : -1) * (st::notificationSampleSize.height() + st::notificationsSampleMargin);
  299. }
  300. p.setOpacity(1.);
  301. } else {
  302. p.setOpacity(st::notificationSampleOpacity);
  303. p.drawPixmapLeft(sampleLeft, sampleTop, width(), _notificationSampleSmall);
  304. p.setOpacity(1.);
  305. }
  306. }
  307. }
  308. void NotificationsCount::setCount(int count) {
  309. auto moreSamples = (count > _oldCount);
  310. auto from = moreSamples ? 0. : 1.;
  311. auto to = moreSamples ? 1. : 0.;
  312. auto indexDelta = moreSamples ? 1 : -1;
  313. auto animatedDelta = moreSamples ? 0 : -1;
  314. for (; _oldCount != count; _oldCount += indexDelta) {
  315. _sampleOpacities[_oldCount + animatedDelta].start([this] { update(); }, from, to, st::notifyFastAnim);
  316. }
  317. if (count != Core::App().settings().notificationsCount()) {
  318. Core::App().settings().setNotificationsCount(count);
  319. Core::App().saveSettingsDelayed();
  320. Core::App().notifications().notifySettingsChanged(
  321. ChangeType::MaxCount);
  322. }
  323. }
  324. int NotificationsCount::getContentLeft() const {
  325. return (width() - st::notificationsBoxMonitor.width()) / 2;
  326. }
  327. QRect NotificationsCount::getScreenRect() const {
  328. return getScreenRect(width());
  329. }
  330. QRect NotificationsCount::getScreenRect(int width) const {
  331. auto screenLeft = (width - st::notificationsBoxScreenSize.width()) / 2;
  332. auto screenTop = st::notificationsBoxScreenTop;
  333. return QRect(screenLeft, screenTop, st::notificationsBoxScreenSize.width(), st::notificationsBoxScreenSize.height());
  334. }
  335. int NotificationsCount::resizeGetHeight(int newWidth) {
  336. update();
  337. return st::notificationsBoxMonitor.height();
  338. }
  339. void NotificationsCount::prepareNotificationSampleSmall() {
  340. auto width = st::notificationSampleSize.width();
  341. auto height = st::notificationSampleSize.height();
  342. auto sampleImage = QImage(
  343. QSize(width, height) * style::DevicePixelRatio(),
  344. QImage::Format_ARGB32_Premultiplied);
  345. sampleImage.setDevicePixelRatio(style::DevicePixelRatio());
  346. sampleImage.fill(st::notificationBg->c);
  347. {
  348. Painter p(&sampleImage);
  349. PainterHighQualityEnabler hq(p);
  350. p.setPen(Qt::NoPen);
  351. auto padding = height / 8;
  352. auto userpicSize = height - 2 * padding;
  353. p.setBrush(st::notificationSampleUserpicFg);
  354. p.drawEllipse(style::rtlrect(padding, padding, userpicSize, userpicSize, width));
  355. auto rowLeft = height;
  356. auto rowHeight = padding;
  357. auto nameTop = (height - 5 * padding) / 2;
  358. auto nameWidth = height;
  359. p.setBrush(st::notificationSampleNameFg);
  360. p.drawRoundedRect(style::rtlrect(rowLeft, nameTop, nameWidth, rowHeight, width), rowHeight / 2, rowHeight / 2);
  361. auto rowWidth = (width - rowLeft - 3 * padding);
  362. auto rowTop = nameTop + rowHeight + padding;
  363. p.setBrush(st::notificationSampleTextFg);
  364. p.drawRoundedRect(style::rtlrect(rowLeft, rowTop, rowWidth, rowHeight, width), rowHeight / 2, rowHeight / 2);
  365. rowTop += rowHeight + padding;
  366. p.drawRoundedRect(style::rtlrect(rowLeft, rowTop, rowWidth, rowHeight, width), rowHeight / 2, rowHeight / 2);
  367. auto closeLeft = width - 2 * padding;
  368. p.fillRect(style::rtlrect(closeLeft, padding, padding, padding, width), st::notificationSampleCloseFg);
  369. }
  370. _notificationSampleSmall = Ui::PixmapFromImage(std::move(sampleImage));
  371. _notificationSampleSmall.setDevicePixelRatio(style::DevicePixelRatio());
  372. }
  373. void NotificationsCount::prepareNotificationSampleUserpic() {
  374. if (_notificationSampleUserpic.isNull()) {
  375. _notificationSampleUserpic = Ui::PixmapFromImage(
  376. Window::LogoNoMargin().scaled(
  377. st::notifyPhotoSize * style::DevicePixelRatio(),
  378. st::notifyPhotoSize * style::DevicePixelRatio(),
  379. Qt::IgnoreAspectRatio,
  380. Qt::SmoothTransformation));
  381. _notificationSampleUserpic.setDevicePixelRatio(
  382. style::DevicePixelRatio());
  383. }
  384. }
  385. void NotificationsCount::prepareNotificationSampleLarge() {
  386. int w = st::notifyWidth, h = st::notifyMinHeight;
  387. auto sampleImage = QImage(
  388. w * style::DevicePixelRatio(),
  389. h * style::DevicePixelRatio(),
  390. QImage::Format_ARGB32_Premultiplied);
  391. sampleImage.setDevicePixelRatio(style::DevicePixelRatio());
  392. sampleImage.fill(st::notificationBg->c);
  393. {
  394. Painter p(&sampleImage);
  395. p.fillRect(0, 0, w - st::notifyBorderWidth, st::notifyBorderWidth, st::notifyBorder->b);
  396. p.fillRect(w - st::notifyBorderWidth, 0, st::notifyBorderWidth, h - st::notifyBorderWidth, st::notifyBorder->b);
  397. p.fillRect(st::notifyBorderWidth, h - st::notifyBorderWidth, w - st::notifyBorderWidth, st::notifyBorderWidth, st::notifyBorder->b);
  398. p.fillRect(0, st::notifyBorderWidth, st::notifyBorderWidth, h - st::notifyBorderWidth, st::notifyBorder->b);
  399. prepareNotificationSampleUserpic();
  400. p.drawPixmap(st::notifyPhotoPos.x(), st::notifyPhotoPos.y(), _notificationSampleUserpic);
  401. int itemWidth = w - st::notifyPhotoPos.x() - st::notifyPhotoSize - st::notifyTextLeft - st::notifyClosePos.x() - st::notifyClose.width;
  402. auto rectForName = style::rtlrect(st::notifyPhotoPos.x() + st::notifyPhotoSize + st::notifyTextLeft, st::notifyTextTop, itemWidth, st::msgNameFont->height, w);
  403. auto notifyText = st::dialogsTextFont->elided(tr::lng_notification_sample(tr::now), itemWidth);
  404. p.setFont(st::dialogsTextFont);
  405. p.setPen(st::dialogsTextFgService);
  406. p.drawText(st::notifyPhotoPos.x() + st::notifyPhotoSize + st::notifyTextLeft, st::notifyItemTop + st::msgNameFont->height + st::dialogsTextFont->ascent, notifyText);
  407. p.setPen(st::dialogsNameFg);
  408. p.setFont(st::msgNameFont);
  409. auto notifyTitle = st::msgNameFont->elided(u"Telegram Desktop"_q, rectForName.width());
  410. p.drawText(rectForName.left(), rectForName.top() + st::msgNameFont->ascent, notifyTitle);
  411. st::notifyClose.icon.paint(p, w - st::notifyClosePos.x() - st::notifyClose.width + st::notifyClose.iconPosition.x(), st::notifyClosePos.y() + st::notifyClose.iconPosition.y(), w);
  412. }
  413. _notificationSampleLarge = Ui::PixmapFromImage(std::move(sampleImage));
  414. }
  415. void NotificationsCount::removeSample(SampleWidget *widget) {
  416. for (auto &samples : _cornerSamples) {
  417. for (int i = 0, size = samples.size(); i != size; ++i) {
  418. if (samples[i] == widget) {
  419. for (int j = i + 1; j != size; ++j) {
  420. samples[j]->detach();
  421. }
  422. samples.resize(i);
  423. break;
  424. }
  425. }
  426. }
  427. }
  428. void NotificationsCount::mouseMoveEvent(QMouseEvent *e) {
  429. auto screenRect = getScreenRect();
  430. auto cornerWidth = screenRect.width() / 3;
  431. auto cornerHeight = screenRect.height() / 3;
  432. auto topLeft = style::rtlrect(screenRect.x(), screenRect.y(), cornerWidth, cornerHeight, width());
  433. auto topRight = style::rtlrect(screenRect.x() + screenRect.width() - cornerWidth, screenRect.y(), cornerWidth, cornerHeight, width());
  434. auto bottomRight = style::rtlrect(screenRect.x() + screenRect.width() - cornerWidth, screenRect.y() + screenRect.height() - cornerHeight, cornerWidth, cornerHeight, width());
  435. auto bottomLeft = style::rtlrect(screenRect.x(), screenRect.y() + screenRect.height() - cornerHeight, cornerWidth, cornerHeight, width());
  436. if (topLeft.contains(e->pos())) {
  437. setOverCorner(ScreenCorner::TopLeft);
  438. } else if (topRight.contains(e->pos())) {
  439. setOverCorner(ScreenCorner::TopRight);
  440. } else if (bottomRight.contains(e->pos())) {
  441. setOverCorner(ScreenCorner::BottomRight);
  442. } else if (bottomLeft.contains(e->pos())) {
  443. setOverCorner(ScreenCorner::BottomLeft);
  444. } else {
  445. clearOverCorner();
  446. }
  447. }
  448. void NotificationsCount::leaveEventHook(QEvent *e) {
  449. clearOverCorner();
  450. }
  451. void NotificationsCount::setOverCorner(ScreenCorner corner) {
  452. if (_isOverCorner) {
  453. if (corner == _overCorner) {
  454. return;
  455. }
  456. const auto index = static_cast<int>(_overCorner);
  457. for (const auto widget : _cornerSamples[index]) {
  458. widget->hideFast();
  459. }
  460. } else {
  461. _isOverCorner = true;
  462. setCursor(style::cur_pointer);
  463. Core::App().notifications().notifySettingsChanged(
  464. ChangeType::DemoIsShown);
  465. }
  466. _overCorner = corner;
  467. auto &samples = _cornerSamples[static_cast<int>(_overCorner)];
  468. auto samplesAlready = int(samples.size());
  469. auto samplesNeeded = _oldCount;
  470. auto samplesLeave = qMin(samplesAlready, samplesNeeded);
  471. for (int i = 0; i != samplesLeave; ++i) {
  472. samples[i]->showFast();
  473. }
  474. if (samplesNeeded > samplesLeave) {
  475. auto r = _controller->widget()->desktopRect();
  476. auto isLeft = Core::Settings::IsLeftCorner(_overCorner);
  477. auto isTop = Core::Settings::IsTopCorner(_overCorner);
  478. auto sampleLeft = (isLeft == rtl()) ? (r.x() + r.width() - st::notifyWidth - st::notifyDeltaX) : (r.x() + st::notifyDeltaX);
  479. auto sampleTop = isTop ? (r.y() + st::notifyDeltaY) : (r.y() + r.height() - st::notifyDeltaY - st::notifyMinHeight);
  480. for (int i = samplesLeave; i != samplesNeeded; ++i) {
  481. auto widget = std::make_unique<SampleWidget>(this, _notificationSampleLarge);
  482. widget->move(sampleLeft, sampleTop + (isTop ? 1 : -1) * i * (st::notifyMinHeight + st::notifyDeltaY));
  483. widget->showFast();
  484. samples.push_back(widget.release());
  485. }
  486. } else {
  487. for (int i = samplesLeave; i != samplesAlready; ++i) {
  488. samples[i]->hideFast();
  489. }
  490. }
  491. }
  492. void NotificationsCount::clearOverCorner() {
  493. if (_isOverCorner) {
  494. _isOverCorner = false;
  495. setCursor(style::cur_default);
  496. Core::App().notifications().notifySettingsChanged(
  497. ChangeType::DemoIsHidden);
  498. for (const auto &samples : _cornerSamples) {
  499. for (const auto widget : samples) {
  500. widget->hideFast();
  501. }
  502. }
  503. }
  504. }
  505. void NotificationsCount::mousePressEvent(QMouseEvent *e) {
  506. _isDownCorner = _isOverCorner;
  507. _downCorner = _overCorner;
  508. }
  509. void NotificationsCount::mouseReleaseEvent(QMouseEvent *e) {
  510. auto isDownCorner = base::take(_isDownCorner);
  511. if (isDownCorner
  512. && _isOverCorner
  513. && _downCorner == _overCorner
  514. && _downCorner != _chosenCorner) {
  515. _chosenCorner = _downCorner;
  516. update();
  517. if (_chosenCorner != Core::App().settings().notificationsCorner()) {
  518. Core::App().settings().setNotificationsCorner(_chosenCorner);
  519. Core::App().saveSettingsDelayed();
  520. Core::App().notifications().notifySettingsChanged(
  521. ChangeType::Corner);
  522. }
  523. }
  524. }
  525. NotificationsCount::~NotificationsCount() {
  526. for (const auto &samples : _cornerSamples) {
  527. for (const auto widget : samples) {
  528. widget->detach();
  529. }
  530. }
  531. clearOverCorner();
  532. }
  533. NotificationsCount::SampleWidget::SampleWidget(
  534. NotificationsCount *owner,
  535. const QPixmap &cache)
  536. : _owner(owner)
  537. , _cache(cache) {
  538. setFixedSize(
  539. cache.width() / cache.devicePixelRatio(),
  540. cache.height() / cache.devicePixelRatio());
  541. setWindowFlags(Qt::WindowFlags(Qt::FramelessWindowHint)
  542. | Qt::WindowStaysOnTopHint
  543. | Qt::BypassWindowManagerHint
  544. | Qt::NoDropShadowWindowHint
  545. | Qt::Tool);
  546. setAttribute(Qt::WA_MacAlwaysShowToolWindow);
  547. setAttribute(Qt::WA_TransparentForMouseEvents);
  548. setAttribute(Qt::WA_OpaquePaintEvent);
  549. setWindowOpacity(0.);
  550. show();
  551. }
  552. void NotificationsCount::SampleWidget::detach() {
  553. _owner = nullptr;
  554. hideFast();
  555. }
  556. void NotificationsCount::SampleWidget::showFast() {
  557. _hiding = false;
  558. startAnimation();
  559. }
  560. void NotificationsCount::SampleWidget::hideFast() {
  561. _hiding = true;
  562. startAnimation();
  563. }
  564. void NotificationsCount::SampleWidget::paintEvent(QPaintEvent *e) {
  565. Painter p(this);
  566. p.drawPixmap(0, 0, _cache);
  567. }
  568. void NotificationsCount::SampleWidget::startAnimation() {
  569. _opacity.start(
  570. [=] { animationCallback(); },
  571. _hiding ? 1. : 0.,
  572. _hiding ? 0. : 1.,
  573. st::notifyFastAnim);
  574. }
  575. void NotificationsCount::SampleWidget::animationCallback() {
  576. setWindowOpacity(_opacity.value(_hiding ? 0. : 1.));
  577. if (!_opacity.animating() && _hiding) {
  578. if (_owner) {
  579. _owner->removeSample(this);
  580. }
  581. hide();
  582. deleteLater();
  583. }
  584. }
  585. class NotifyPreview final {
  586. public:
  587. NotifyPreview(bool nameShown, bool previewShown);
  588. void setNameShown(bool shown);
  589. void setPreviewShown(bool shown);
  590. int resizeGetHeight(int newWidth);
  591. void paint(Painter &p, int x, int y);
  592. private:
  593. int _width = 0;
  594. int _height = 0;
  595. bool _nameShown = false;
  596. bool _previewShown = false;
  597. Ui::RoundRect _roundRect;
  598. Ui::Text::String _name, _title;
  599. Ui::Text::String _text, _preview;
  600. QSvgRenderer _userpic;
  601. QImage _logo;
  602. };
  603. NotifyPreview::NotifyPreview(bool nameShown, bool previewShown)
  604. : _nameShown(nameShown)
  605. , _previewShown(previewShown)
  606. , _roundRect(st::boxRadius, st::msgInBg)
  607. , _userpic(u":/gui/icons/settings/dino.svg"_q)
  608. , _logo(Window::LogoNoMargin()) {
  609. const auto ratio = style::DevicePixelRatio();
  610. _logo = _logo.scaledToWidth(
  611. st::notifyPreviewUserpicSize * ratio,
  612. Qt::SmoothTransformation);
  613. _logo.setDevicePixelRatio(ratio);
  614. _name.setText(
  615. st::defaultSubsectionTitle.style,
  616. tr::lng_notification_preview_title(tr::now));
  617. _title.setText(st::defaultSubsectionTitle.style, AppName.utf16());
  618. _text.setText(
  619. st::boxTextStyle,
  620. tr::lng_notification_preview_text(tr::now));
  621. _preview.setText(
  622. st::boxTextStyle,
  623. tr::lng_notification_preview(tr::now));
  624. }
  625. void NotifyPreview::setNameShown(bool shown) {
  626. _nameShown = shown;
  627. }
  628. void NotifyPreview::setPreviewShown(bool shown) {
  629. _previewShown = shown;
  630. }
  631. int NotifyPreview::resizeGetHeight(int newWidth) {
  632. _width = newWidth;
  633. _height = st::notifyPreviewUserpicPosition.y()
  634. + st::notifyPreviewUserpicSize
  635. + st::notifyPreviewUserpicPosition.y();
  636. const auto available = _width
  637. - st::notifyPreviewTextPosition.x()
  638. - st::notifyPreviewUserpicPosition.x();
  639. if (std::max(_text.maxWidth(), _preview.maxWidth()) >= available) {
  640. _height += st::defaultTextStyle.font->height;
  641. }
  642. return _height;
  643. }
  644. void NotifyPreview::paint(Painter &p, int x, int y) {
  645. if (!_width || !_height) {
  646. return;
  647. }
  648. p.translate(x, y);
  649. const auto guard = gsl::finally([&] { p.translate(-x, -y); });
  650. _roundRect.paint(p, { 0, 0, _width, _height });
  651. const auto userpic = QRect(
  652. st::notifyPreviewUserpicPosition,
  653. QSize{ st::notifyPreviewUserpicSize, st::notifyPreviewUserpicSize });
  654. if (_nameShown) {
  655. _userpic.render(&p, QRectF(userpic));
  656. } else {
  657. p.drawImage(userpic.topLeft(), _logo);
  658. }
  659. p.setPen(st::historyTextInFg);
  660. const auto &title = _nameShown ? _name : _title;
  661. title.drawElided(
  662. p,
  663. st::notifyPreviewTitlePosition.x(),
  664. st::notifyPreviewTitlePosition.y(),
  665. _width - st::notifyPreviewTitlePosition.x() - userpic.x());
  666. const auto &text = _previewShown ? _text : _preview;
  667. text.drawElided(
  668. p,
  669. st::notifyPreviewTextPosition.x(),
  670. st::notifyPreviewTextPosition.y(),
  671. _width - st::notifyPreviewTextPosition.x() - userpic.x(),
  672. 2);
  673. }
  674. struct NotifyViewCheckboxes {
  675. not_null<Ui::SlideWrap<>*> wrap;
  676. not_null<Ui::Checkbox*> name;
  677. not_null<Ui::Checkbox*> preview;
  678. };
  679. NotifyViewCheckboxes SetupNotifyViewOptions(
  680. not_null<Window::SessionController*> controller,
  681. not_null<Ui::VerticalLayout*> container,
  682. bool nameShown,
  683. bool previewShown) {
  684. using namespace rpl::mappers;
  685. auto wrap = container->add(object_ptr<Ui::SlideWrap<>>(
  686. container,
  687. object_ptr<Ui::RpWidget>(container)));
  688. const auto widget = wrap->entity();
  689. const auto makeCheckbox = [&](const QString &text, bool checked) {
  690. return Ui::MakeChatServiceCheckbox(
  691. widget,
  692. text,
  693. st::backgroundCheckbox,
  694. st::backgroundCheck,
  695. checked).release();
  696. };
  697. const auto name = makeCheckbox(
  698. tr::lng_notification_show_name(tr::now),
  699. nameShown);
  700. const auto preview = makeCheckbox(
  701. tr::lng_notification_show_text(tr::now),
  702. previewShown);
  703. const auto view = widget->lifetime().make_state<NotifyPreview>(
  704. nameShown,
  705. previewShown);
  706. widget->widthValue(
  707. ) | rpl::filter(
  708. _1 >= (st::historyMinimalWidth / 2)
  709. ) | rpl::start_with_next([=](int width) {
  710. const auto margins = st::notifyPreviewMargins;
  711. const auto bubblew = width - margins.left() - margins.right();
  712. const auto bubbleh = view->resizeGetHeight(bubblew);
  713. const auto height = bubbleh + margins.top() + margins.bottom();
  714. widget->resize(width, height);
  715. const auto skip = st::notifyPreviewChecksSkip;
  716. const auto checksWidth = name->width() + skip + preview->width();
  717. const auto checksLeft = (width - checksWidth) / 2;
  718. const auto checksTop = height
  719. - (margins.bottom() + name->height()) / 2;
  720. name->move(checksLeft, checksTop);
  721. preview->move(checksLeft + name->width() + skip, checksTop);
  722. }, widget->lifetime());
  723. widget->paintRequest(
  724. ) | rpl::start_with_next([=](QRect rect) {
  725. Window::SectionWidget::PaintBackground(
  726. controller,
  727. controller->defaultChatTheme().get(), // #TODO themes
  728. widget,
  729. rect);
  730. Painter p(widget);
  731. view->paint(
  732. p,
  733. st::notifyPreviewMargins.left(),
  734. st::notifyPreviewMargins.top());
  735. }, widget->lifetime());
  736. name->checkedChanges(
  737. ) | rpl::start_with_next([=](bool checked) {
  738. view->setNameShown(checked);
  739. widget->update();
  740. }, name->lifetime());
  741. preview->checkedChanges(
  742. ) | rpl::start_with_next([=](bool checked) {
  743. view->setPreviewShown(checked);
  744. widget->update();
  745. }, preview->lifetime());
  746. return {
  747. .wrap = wrap,
  748. .name = name,
  749. .preview = preview,
  750. };
  751. }
  752. void SetupAdvancedNotifications(
  753. not_null<Window::SessionController*> controller,
  754. not_null<Ui::VerticalLayout*> container) {
  755. if (Platform::IsWindows()) {
  756. const auto skipInFocus = container->add(object_ptr<Button>(
  757. container,
  758. tr::lng_settings_skip_in_focus(),
  759. st::settingsButtonNoIcon
  760. ))->toggleOn(rpl::single(Core::App().settings().skipToastsInFocus()));
  761. skipInFocus->toggledChanges(
  762. ) | rpl::filter([](bool checked) {
  763. return (checked != Core::App().settings().skipToastsInFocus());
  764. }) | rpl::start_with_next([=](bool checked) {
  765. Core::App().settings().setSkipToastsInFocus(checked);
  766. Core::App().saveSettingsDelayed();
  767. if (checked && Platform::Notifications::SkipToastForCustom()) {
  768. using Change = Window::Notifications::ChangeType;
  769. Core::App().notifications().notifySettingsChanged(
  770. Change::DesktopEnabled);
  771. }
  772. }, skipInFocus->lifetime());
  773. }
  774. Ui::AddSkip(container, st::settingsCheckboxesSkip);
  775. Ui::AddDivider(container);
  776. Ui::AddSkip(container, st::settingsCheckboxesSkip);
  777. Ui::AddSubsectionTitle(
  778. container,
  779. tr::lng_settings_notifications_position());
  780. Ui::AddSkip(container, st::settingsCheckboxesSkip);
  781. const auto position = container->add(
  782. object_ptr<NotificationsCount>(container, controller));
  783. Ui::AddSkip(container, st::settingsCheckboxesSkip);
  784. Ui::AddSubsectionTitle(container, tr::lng_settings_notifications_count());
  785. const auto count = container->add(
  786. object_ptr<Ui::SettingsSlider>(container, st::settingsSlider),
  787. st::settingsBigScalePadding);
  788. for (int i = 0; i != kMaxNotificationsCount; ++i) {
  789. count->addSection(QString::number(i + 1));
  790. }
  791. count->setActiveSectionFast(CurrentCount() - 1);
  792. count->sectionActivated(
  793. ) | rpl::start_with_next([=](int section) {
  794. position->setCount(section + 1);
  795. }, count->lifetime());
  796. Ui::AddSkip(container, st::settingsCheckboxesSkip);
  797. }
  798. void SetupMultiAccountNotifications(
  799. not_null<Window::SessionController*> controller,
  800. not_null<Ui::VerticalLayout*> container) {
  801. if (Core::App().domain().accounts().size() < 2) {
  802. return;
  803. }
  804. Ui::AddSubsectionTitle(container, tr::lng_settings_show_from());
  805. const auto fromAll = container->add(object_ptr<Button>(
  806. container,
  807. tr::lng_settings_notify_all(),
  808. st::settingsButtonNoIcon
  809. ))->toggleOn(rpl::single(Core::App().settings().notifyFromAll()));
  810. fromAll->toggledChanges(
  811. ) | rpl::filter([](bool checked) {
  812. return (checked != Core::App().settings().notifyFromAll());
  813. }) | rpl::start_with_next([=](bool checked) {
  814. Core::App().settings().setNotifyFromAll(checked);
  815. Core::App().saveSettingsDelayed();
  816. if (!checked) {
  817. auto &notifications = Core::App().notifications();
  818. const auto &list = Core::App().domain().accounts();
  819. for (const auto &[index, account] : list) {
  820. if (account.get() == &Core::App().domain().active()) {
  821. continue;
  822. } else if (const auto session = account->maybeSession()) {
  823. notifications.clearFromSession(session);
  824. }
  825. }
  826. }
  827. }, fromAll->lifetime());
  828. Ui::AddSkip(container);
  829. Ui::AddDividerText(container, tr::lng_settings_notify_all_about());
  830. Ui::AddSkip(container);
  831. }
  832. void SetupNotificationsContent(
  833. not_null<Window::SessionController*> controller,
  834. not_null<Ui::VerticalLayout*> container,
  835. Fn<void(Type)> showOther) {
  836. using namespace rpl::mappers;
  837. Ui::AddSkip(container, st::settingsPrivacySkip);
  838. using NotifyView = Core::Settings::NotifyView;
  839. SetupMultiAccountNotifications(controller, container);
  840. AddSubsectionTitle(container, tr::lng_settings_notify_global());
  841. const auto session = &controller->session();
  842. const auto checkbox = [&](
  843. rpl::producer<QString> label,
  844. IconDescriptor &&descriptor,
  845. rpl::producer<bool> checked) {
  846. auto result = CreateButtonWithIcon(
  847. container,
  848. std::move(label),
  849. st::settingsButton,
  850. std::move(descriptor)
  851. );
  852. result->toggleOn(std::move(checked));
  853. return result;
  854. };
  855. const auto addCheckbox = [&](
  856. rpl::producer<QString> label,
  857. IconDescriptor &&descriptor,
  858. rpl::producer<bool> checked) {
  859. return container->add(
  860. checkbox(
  861. std::move(label),
  862. std::move(descriptor),
  863. std::move(checked)));
  864. };
  865. const auto &settings = Core::App().settings();
  866. const auto desktopToggles = container->lifetime(
  867. ).make_state<rpl::event_stream<bool>>();
  868. const auto desktop = addCheckbox(
  869. tr::lng_settings_desktop_notify(),
  870. { &st::menuIconNotifications },
  871. desktopToggles->events_starting_with(settings.desktopNotify()));
  872. const auto flashbounceToggles = container->lifetime(
  873. ).make_state<rpl::event_stream<bool>>();
  874. const auto flashbounce = addCheckbox(
  875. (Platform::IsWindows()
  876. ? tr::lng_settings_alert_windows
  877. : Platform::IsMac()
  878. ? tr::lng_settings_alert_mac
  879. : tr::lng_settings_alert_linux)(),
  880. { &st::menuIconDockBounce },
  881. flashbounceToggles->events_starting_with(
  882. settings.flashBounceNotify()));
  883. const auto soundAllowed = container->lifetime(
  884. ).make_state<rpl::event_stream<bool>>();
  885. const auto allowed = [=] {
  886. return Core::App().settings().soundNotify();
  887. };
  888. const auto sound = addCheckbox(
  889. tr::lng_settings_sound_allowed(),
  890. { &st::menuIconUnmute },
  891. soundAllowed->events_starting_with(allowed()));
  892. Ui::AddSkip(container);
  893. const auto checkboxes = SetupNotifyViewOptions(
  894. controller,
  895. container,
  896. (settings.notifyView() <= NotifyView::ShowName),
  897. (settings.notifyView() <= NotifyView::ShowPreview));
  898. const auto name = checkboxes.name;
  899. const auto preview = checkboxes.preview;
  900. const auto previewWrap = checkboxes.wrap;
  901. const auto previewDivider = container->add(
  902. object_ptr<Ui::SlideWrap<Ui::BoxContentDivider>>(
  903. container,
  904. object_ptr<Ui::BoxContentDivider>(container)));
  905. previewWrap->toggle(settings.desktopNotify(), anim::type::instant);
  906. previewDivider->toggle(!settings.desktopNotify(), anim::type::instant);
  907. controller->session().data().notifySettings().loadExceptions();
  908. Ui::AddSkip(container, st::notifyPreviewBottomSkip);
  909. Ui::AddSubsectionTitle(container, tr::lng_settings_notify_title());
  910. const auto addType = [&](Data::DefaultNotify type) {
  911. AddTypeButton(container, controller, type, showOther);
  912. };
  913. addType(Data::DefaultNotify::User);
  914. addType(Data::DefaultNotify::Group);
  915. addType(Data::DefaultNotify::Broadcast);
  916. Ui::AddSkip(container, st::settingsCheckboxesSkip);
  917. Ui::AddDivider(container);
  918. Ui::AddSkip(container, st::settingsCheckboxesSkip);
  919. Ui::AddSubsectionTitle(container, tr::lng_settings_events_title());
  920. auto joinSilent = rpl::single(
  921. session->api().contactSignupSilentCurrent().value_or(false)
  922. ) | rpl::then(session->api().contactSignupSilent());
  923. const auto joined = addCheckbox(
  924. tr::lng_settings_events_joined(),
  925. { &st::menuIconInvite },
  926. std::move(joinSilent) | rpl::map(!_1));
  927. joined->toggledChanges(
  928. ) | rpl::filter([=](bool enabled) {
  929. const auto silent = session->api().contactSignupSilentCurrent();
  930. return (enabled == silent.value_or(false));
  931. }) | rpl::start_with_next([=](bool enabled) {
  932. session->api().saveContactSignupSilent(!enabled);
  933. }, joined->lifetime());
  934. const auto pinned = addCheckbox(
  935. tr::lng_settings_events_pinned(),
  936. { &st::menuIconPin },
  937. rpl::single(
  938. settings.notifyAboutPinned()
  939. ) | rpl::then(settings.notifyAboutPinnedChanges()));
  940. pinned->toggledChanges(
  941. ) | rpl::filter([=](bool notify) {
  942. return (notify != Core::App().settings().notifyAboutPinned());
  943. }) | rpl::start_with_next([=](bool notify) {
  944. Core::App().settings().setNotifyAboutPinned(notify);
  945. Core::App().saveSettingsDelayed();
  946. }, joined->lifetime());
  947. Ui::AddSkip(container, st::settingsCheckboxesSkip);
  948. Ui::AddDivider(container);
  949. Ui::AddSkip(container, st::settingsCheckboxesSkip);
  950. Ui::AddSubsectionTitle(
  951. container,
  952. tr::lng_settings_notifications_calls_title());
  953. const auto authorizations = &session->api().authorizations();
  954. const auto acceptCalls = addCheckbox(
  955. tr::lng_settings_call_accept_calls(),
  956. { &st::menuIconCallsReceive },
  957. authorizations->callsDisabledHereValue() | rpl::map(!_1));
  958. acceptCalls->toggledChanges(
  959. ) | rpl::filter([=](bool toggled) {
  960. return (toggled == authorizations->callsDisabledHere());
  961. }) | rpl::start_with_next([=](bool toggled) {
  962. authorizations->toggleCallsDisabledHere(!toggled);
  963. }, container->lifetime());
  964. Ui::AddSkip(container, st::settingsCheckboxesSkip);
  965. Ui::AddDivider(container);
  966. Ui::AddSkip(container, st::settingsCheckboxesSkip);
  967. Ui::AddSubsectionTitle(container, tr::lng_settings_badge_title());
  968. const auto muted = container->add(object_ptr<Button>(
  969. container,
  970. tr::lng_settings_include_muted(),
  971. st::settingsButtonNoIcon));
  972. muted->toggleOn(rpl::single(settings.includeMutedCounter()));
  973. const auto mutedFolders = session->data().chatsFilters().has()
  974. ? container->add(object_ptr<Button>(
  975. container,
  976. tr::lng_settings_include_muted_folders(),
  977. st::settingsButtonNoIcon))
  978. : nullptr;
  979. if (mutedFolders) {
  980. mutedFolders->toggleOn(
  981. rpl::single(settings.includeMutedCounterFolders()));
  982. }
  983. const auto count = container->add(object_ptr<Button>(
  984. container,
  985. tr::lng_settings_count_unread(),
  986. st::settingsButtonNoIcon));
  987. count->toggleOn(rpl::single(settings.countUnreadMessages()));
  988. auto nativeText = [&] {
  989. if (!Platform::Notifications::Supported()
  990. || Platform::Notifications::Enforced()) {
  991. return rpl::producer<QString>();
  992. } else if (Platform::IsWindows()) {
  993. return tr::lng_settings_use_windows();
  994. }
  995. return tr::lng_settings_use_native_notifications();
  996. }();
  997. const auto native = [&]() -> Ui::SettingsButton* {
  998. if (!nativeText) {
  999. return nullptr;
  1000. }
  1001. Ui::AddSkip(container, st::settingsCheckboxesSkip);
  1002. Ui::AddDivider(container);
  1003. Ui::AddSkip(container, st::settingsCheckboxesSkip);
  1004. Ui::AddSubsectionTitle(container, tr::lng_settings_native_title());
  1005. return container->add(object_ptr<Button>(
  1006. container,
  1007. std::move(nativeText),
  1008. st::settingsButtonNoIcon
  1009. ))->toggleOn(rpl::single(settings.nativeNotifications()));
  1010. }();
  1011. const auto advancedSlide = !Platform::Notifications::Enforced()
  1012. ? container->add(
  1013. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  1014. container,
  1015. object_ptr<Ui::VerticalLayout>(container)))
  1016. : nullptr;
  1017. const auto advancedWrap = advancedSlide
  1018. ? advancedSlide->entity()
  1019. : nullptr;
  1020. if (advancedWrap) {
  1021. SetupAdvancedNotifications(controller, advancedWrap);
  1022. }
  1023. if (native && advancedSlide && settings.nativeNotifications()) {
  1024. advancedSlide->hide(anim::type::instant);
  1025. }
  1026. using Change = Window::Notifications::ChangeType;
  1027. const auto changed = [=](Change change) {
  1028. Core::App().saveSettingsDelayed();
  1029. Core::App().notifications().notifySettingsChanged(change);
  1030. };
  1031. desktop->toggledChanges(
  1032. ) | rpl::filter([](bool checked) {
  1033. return (checked != Core::App().settings().desktopNotify());
  1034. }) | rpl::start_with_next([=](bool checked) {
  1035. Core::App().settings().setDesktopNotify(checked);
  1036. changed(Change::DesktopEnabled);
  1037. }, desktop->lifetime());
  1038. sound->toggledChanges(
  1039. ) | rpl::filter([](bool checked) {
  1040. return (checked != Core::App().settings().soundNotify());
  1041. }) | rpl::start_with_next([=](bool checked) {
  1042. Core::App().settings().setSoundNotify(checked);
  1043. changed(Change::SoundEnabled);
  1044. }, sound->lifetime());
  1045. name->checkedChanges(
  1046. ) | rpl::map([=](bool checked) {
  1047. if (!checked) {
  1048. preview->setChecked(false);
  1049. return NotifyView::ShowNothing;
  1050. } else if (!preview->checked()) {
  1051. return NotifyView::ShowName;
  1052. }
  1053. return NotifyView::ShowPreview;
  1054. }) | rpl::filter([=](NotifyView value) {
  1055. return (value != Core::App().settings().notifyView());
  1056. }) | rpl::start_with_next([=](NotifyView value) {
  1057. Core::App().settings().setNotifyView(value);
  1058. changed(Change::ViewParams);
  1059. }, name->lifetime());
  1060. preview->checkedChanges(
  1061. ) | rpl::map([=](bool checked) {
  1062. if (checked) {
  1063. name->setChecked(true);
  1064. return NotifyView::ShowPreview;
  1065. } else if (name->checked()) {
  1066. return NotifyView::ShowName;
  1067. }
  1068. return NotifyView::ShowNothing;
  1069. }) | rpl::filter([=](NotifyView value) {
  1070. return (value != Core::App().settings().notifyView());
  1071. }) | rpl::start_with_next([=](NotifyView value) {
  1072. Core::App().settings().setNotifyView(value);
  1073. changed(Change::ViewParams);
  1074. }, preview->lifetime());
  1075. flashbounce->toggledChanges(
  1076. ) | rpl::filter([](bool checked) {
  1077. return (checked != Core::App().settings().flashBounceNotify());
  1078. }) | rpl::start_with_next([=](bool checked) {
  1079. Core::App().settings().setFlashBounceNotify(checked);
  1080. changed(Change::FlashBounceEnabled);
  1081. }, flashbounce->lifetime());
  1082. muted->toggledChanges(
  1083. ) | rpl::filter([=](bool checked) {
  1084. return (checked != Core::App().settings().includeMutedCounter());
  1085. }) | rpl::start_with_next([=](bool checked) {
  1086. Core::App().settings().setIncludeMutedCounter(checked);
  1087. changed(Change::IncludeMuted);
  1088. }, muted->lifetime());
  1089. if (mutedFolders) {
  1090. mutedFolders->toggledChanges(
  1091. ) | rpl::filter([=](bool checked) {
  1092. return (checked
  1093. != Core::App().settings().includeMutedCounterFolders());
  1094. }) | rpl::start_with_next([=](bool checked) {
  1095. Core::App().settings().setIncludeMutedCounterFolders(checked);
  1096. changed(Change::IncludeMuted);
  1097. }, mutedFolders->lifetime());
  1098. }
  1099. count->toggledChanges(
  1100. ) | rpl::filter([=](bool checked) {
  1101. return (checked != Core::App().settings().countUnreadMessages());
  1102. }) | rpl::start_with_next([=](bool checked) {
  1103. Core::App().settings().setCountUnreadMessages(checked);
  1104. changed(Change::CountMessages);
  1105. }, count->lifetime());
  1106. Core::App().notifications().settingsChanged(
  1107. ) | rpl::start_with_next([=](Change change) {
  1108. if (change == Change::DesktopEnabled) {
  1109. desktopToggles->fire(Core::App().settings().desktopNotify());
  1110. previewWrap->toggle(
  1111. Core::App().settings().desktopNotify(),
  1112. anim::type::normal);
  1113. previewDivider->toggle(
  1114. !Core::App().settings().desktopNotify(),
  1115. anim::type::normal);
  1116. } else if (change == Change::ViewParams) {
  1117. //
  1118. } else if (change == Change::SoundEnabled) {
  1119. soundAllowed->fire(allowed());
  1120. } else if (change == Change::FlashBounceEnabled) {
  1121. flashbounceToggles->fire(
  1122. Core::App().settings().flashBounceNotify());
  1123. }
  1124. }, desktop->lifetime());
  1125. if (native) {
  1126. native->toggledChanges(
  1127. ) | rpl::filter([](bool checked) {
  1128. return (checked != Core::App().settings().nativeNotifications());
  1129. }) | rpl::start_with_next([=](bool checked) {
  1130. Core::App().settings().setNativeNotifications(checked);
  1131. Core::App().saveSettingsDelayed();
  1132. Core::App().notifications().createManager();
  1133. if (advancedSlide) {
  1134. advancedSlide->toggle(
  1135. !Core::App().settings().nativeNotifications(),
  1136. anim::type::normal);
  1137. }
  1138. }, native->lifetime());
  1139. }
  1140. }
  1141. void SetupNotifications(
  1142. not_null<Window::SessionController*> controller,
  1143. not_null<Ui::VerticalLayout*> container,
  1144. Fn<void(Type)> showOther) {
  1145. SetupNotificationsContent(controller, container, std::move(showOther));
  1146. }
  1147. } // namespace
  1148. Notifications::Notifications(
  1149. QWidget *parent,
  1150. not_null<Window::SessionController*> controller)
  1151. : Section(parent) {
  1152. setupContent(controller);
  1153. }
  1154. rpl::producer<QString> Notifications::title() {
  1155. return tr::lng_settings_section_notify();
  1156. }
  1157. void Notifications::setupContent(
  1158. not_null<Window::SessionController*> controller) {
  1159. const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
  1160. SetupNotifications(controller, content, showOtherMethod());
  1161. Ui::ResizeFitChild(this, content);
  1162. }
  1163. } // namespace Settings