menu_mute.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  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 "menu/menu_mute.h"
  8. #include "boxes/ringtones_box.h"
  9. #include "data/data_session.h"
  10. #include "data/data_thread.h"
  11. #include "data/notify/data_notify_settings.h"
  12. #include "data/notify/data_peer_notify_settings.h"
  13. #include "info/profile/info_profile_values.h"
  14. #include "lang/lang_keys.h"
  15. #include "main/main_session.h"
  16. #include "main/main_session_settings.h"
  17. #include "ui/boxes/choose_time.h"
  18. #include "ui/boxes/confirm_box.h"
  19. #include "ui/boxes/time_picker_box.h"
  20. #include "ui/effects/animation_value.h"
  21. #include "ui/layers/generic_box.h"
  22. #include "ui/text/format_values.h"
  23. #include "ui/widgets/checkbox.h"
  24. #include "ui/widgets/menu/menu_action.h"
  25. #include "ui/widgets/popup_menu.h"
  26. #include "ui/painter.h"
  27. #include "styles/style_boxes.h"
  28. #include "styles/style_info.h" // infoTopBarMenu
  29. #include "styles/style_layers.h"
  30. #include "styles/style_menu_icons.h"
  31. namespace MuteMenu {
  32. namespace {
  33. constexpr auto kMuteDurSecondsDefault = crl::time(8) * 3600;
  34. constexpr auto kMuteForeverValue = std::numeric_limits<TimeId>::max();
  35. class IconWithText final : public Ui::Menu::Action {
  36. public:
  37. using Ui::Menu::Action::Action;
  38. void setData(const QString &text, const QPoint &iconPosition);
  39. protected:
  40. void paintEvent(QPaintEvent *e) override;
  41. private:
  42. QPoint _iconPosition;
  43. QString _text;
  44. };
  45. void IconWithText::setData(const QString &text, const QPoint &iconPosition) {
  46. _iconPosition = iconPosition;
  47. _text = text;
  48. }
  49. void IconWithText::paintEvent(QPaintEvent *e) {
  50. Ui::Menu::Action::paintEvent(e);
  51. auto p = QPainter(this);
  52. p.setFont(st::menuIconMuteForAnyTextFont);
  53. p.setPen(st::menuIconColor);
  54. p.drawText(_iconPosition, _text);
  55. }
  56. class MuteItem final : public Ui::Menu::Action {
  57. public:
  58. MuteItem(
  59. not_null<RpWidget*> parent,
  60. const style::Menu &st,
  61. Descriptor descriptor);
  62. protected:
  63. void paintEvent(QPaintEvent *e) override;
  64. private:
  65. const QPoint _itemIconPosition;
  66. Ui::Animations::Simple _animation;
  67. bool _isMuted = false;
  68. bool _inited;
  69. };
  70. MuteItem::MuteItem(
  71. not_null<RpWidget*> parent,
  72. const style::Menu &st,
  73. Descriptor descriptor)
  74. : Ui::Menu::Action(
  75. parent,
  76. st,
  77. Ui::CreateChild<QAction>(parent.get()),
  78. nullptr,
  79. nullptr)
  80. , _itemIconPosition(st.itemIconPosition) {
  81. descriptor.isMutedValue(
  82. ) | rpl::start_with_next([=](bool isMuted) {
  83. action()->setText(isMuted
  84. ? tr::lng_mute_menu_duration_unmute(tr::now)
  85. : tr::lng_mute_menu_duration_forever(tr::now));
  86. if (_inited && isMuted == _isMuted) {
  87. return;
  88. }
  89. _inited = true;
  90. _isMuted = isMuted;
  91. _animation.start(
  92. [=] { update(); },
  93. isMuted ? 0. : 1.,
  94. isMuted ? 1. : 0.,
  95. st::defaultPopupMenu.showDuration);
  96. }, lifetime());
  97. _animation.stop();
  98. setClickedCallback([=] {
  99. descriptor.updateMutePeriod(_isMuted ? 0 : kMuteForeverValue);
  100. });
  101. }
  102. void MuteItem::paintEvent(QPaintEvent *e) {
  103. Painter p(this);
  104. const auto progress = _animation.value(_isMuted ? 1. : 0.);
  105. const auto color = anim::color(
  106. st::menuIconAttentionColor,
  107. st::boxTextFgGood,
  108. progress);
  109. p.setPen(color);
  110. Action::paintBackground(p, Action::isSelected());
  111. RippleButton::paintRipple(p, 0, 0);
  112. Action::paintText(p);
  113. const auto &icon = _isMuted ? st::menuIconUnmute : st::menuIconMute;
  114. icon.paint(p, _itemIconPosition, width(), color);
  115. }
  116. void MuteBox(not_null<Ui::GenericBox*> box, Descriptor descriptor) {
  117. struct State {
  118. int lastSeconds = 0;
  119. };
  120. auto chooseTimeResult = ChooseTimeWidget(box, kMuteDurSecondsDefault);
  121. box->addRow(std::move(chooseTimeResult.widget));
  122. const auto state = box->lifetime().make_state<State>();
  123. box->setTitle(tr::lng_mute_box_title());
  124. auto confirmText = std::move(
  125. chooseTimeResult.secondsValue
  126. ) | rpl::map([=](int seconds) {
  127. state->lastSeconds = seconds;
  128. return !seconds
  129. ? tr::lng_mute_menu_unmute()
  130. : tr::lng_mute_menu_mute();
  131. }) | rpl::flatten_latest();
  132. Ui::ConfirmBox(box, {
  133. .confirmed = [=] {
  134. descriptor.updateMutePeriod(state->lastSeconds);
  135. box->getDelegate()->hideLayer();
  136. },
  137. .confirmText = std::move(confirmText),
  138. .cancelText = tr::lng_cancel(),
  139. });
  140. }
  141. void PickMuteBox(
  142. not_null<Ui::GenericBox*> box,
  143. Descriptor descriptor) {
  144. struct State {
  145. base::unique_qptr<Ui::PopupMenu> menu;
  146. };
  147. const auto seconds = Ui::DefaultTimePickerValues();
  148. const auto phrases = ranges::views::all(
  149. seconds
  150. ) | ranges::views::transform(Ui::FormatMuteFor) | ranges::to_vector;
  151. const auto state = box->lifetime().make_state<State>();
  152. const auto pickerCallback = TimePickerBox(box, seconds, phrases, 0);
  153. Ui::ConfirmBox(box, {
  154. .confirmed = [=] {
  155. const auto muteFor = pickerCallback();
  156. descriptor.updateMutePeriod(muteFor);
  157. descriptor.session->settings().addMutePeriod(muteFor);
  158. descriptor.session->saveSettings();
  159. box->closeBox();
  160. },
  161. .confirmText = tr::lng_mute_menu_mute(),
  162. .cancelText = tr::lng_cancel(),
  163. });
  164. box->setTitle(tr::lng_mute_box_title());
  165. const auto top = box->addTopButton(st::infoTopBarMenu);
  166. top->setClickedCallback([=] {
  167. if (state->menu) {
  168. return;
  169. }
  170. state->menu = base::make_unique_q<Ui::PopupMenu>(
  171. top,
  172. st::popupMenuWithIcons);
  173. state->menu->addAction(
  174. tr::lng_manage_messages_ttl_after_custom(tr::now),
  175. [=] { box->getDelegate()->show(Box(MuteBox, descriptor)); },
  176. &st::menuIconCustomize);
  177. state->menu->setDestroyedCallback(crl::guard(top, [=] {
  178. top->setForceRippled(false);
  179. }));
  180. top->setForceRippled(true);
  181. state->menu->popup(QCursor::pos());
  182. });
  183. }
  184. } // namespace
  185. Descriptor ThreadDescriptor(not_null<Data::Thread*> thread) {
  186. const auto weak = base::make_weak(thread);
  187. const auto isMutedValue = [=]() -> rpl::producer<bool> {
  188. if (const auto strong = weak.get()) {
  189. return Info::Profile::NotificationsEnabledValue(
  190. strong
  191. ) | rpl::map(!rpl::mappers::_1);
  192. }
  193. return rpl::single(false);
  194. };
  195. const auto currentSound = [=] {
  196. const auto strong = weak.get();
  197. return strong
  198. ? strong->owner().notifySettings().sound(strong)
  199. : std::optional<Data::NotifySound>();
  200. };
  201. const auto updateSound = crl::guard(weak, [=](Data::NotifySound sound) {
  202. thread->owner().notifySettings().update(thread, {}, {}, sound);
  203. });
  204. const auto updateMutePeriod = crl::guard(weak, [=](TimeId mute) {
  205. const auto settings = &thread->owner().notifySettings();
  206. if (!mute) {
  207. settings->update(thread, { .unmute = true });
  208. } else if (mute == kMuteForeverValue) {
  209. settings->update(thread, { .forever = true });
  210. } else {
  211. settings->update(thread, { .period = mute });
  212. }
  213. });
  214. return {
  215. .session = &thread->session(),
  216. .isMutedValue = isMutedValue,
  217. .currentSound = currentSound,
  218. .updateSound = updateSound,
  219. .updateMutePeriod = updateMutePeriod,
  220. };
  221. }
  222. Descriptor DefaultDescriptor(
  223. not_null<Main::Session*> session,
  224. Data::DefaultNotify type) {
  225. const auto settings = &session->data().notifySettings();
  226. const auto isMutedValue = [=]() -> rpl::producer<bool> {
  227. return rpl::single(
  228. rpl::empty
  229. ) | rpl::then(
  230. settings->defaultUpdates(type)
  231. ) | rpl::map([=] {
  232. return settings->isMuted(type);
  233. });
  234. };
  235. const auto currentSound = [=] {
  236. return settings->defaultSettings(type).sound();
  237. };
  238. const auto updateSound = [=](Data::NotifySound sound) {
  239. settings->defaultUpdate(type, {}, {}, sound);
  240. };
  241. const auto updateMutePeriod = [=](TimeId mute) {
  242. if (!mute) {
  243. settings->defaultUpdate(type, { .unmute = true });
  244. } else if (mute == kMuteForeverValue) {
  245. settings->defaultUpdate(type, { .forever = true });
  246. } else {
  247. settings->defaultUpdate(type, { .period = mute });
  248. }
  249. };
  250. return {
  251. .session = session,
  252. .isMutedValue = isMutedValue,
  253. .currentSound = currentSound,
  254. .updateSound = updateSound,
  255. .updateMutePeriod = updateMutePeriod,
  256. };
  257. }
  258. void FillMuteMenu(
  259. not_null<Ui::PopupMenu*> menu,
  260. Descriptor descriptor,
  261. std::shared_ptr<Ui::Show> show) {
  262. const auto session = descriptor.session;
  263. const auto soundSelect = [=] {
  264. if (const auto currentSound = descriptor.currentSound()) {
  265. show->showBox(Box(
  266. RingtonesBox,
  267. session,
  268. *currentSound,
  269. descriptor.updateSound));
  270. }
  271. };
  272. menu->addAction(
  273. tr::lng_mute_menu_sound_select(tr::now),
  274. soundSelect,
  275. &st::menuIconSoundSelect);
  276. const auto soundIsNone = descriptor.currentSound().value_or(
  277. Data::NotifySound()
  278. ).none;
  279. const auto toggleSound = [=] {
  280. if (auto sound = descriptor.currentSound()) {
  281. sound->none = !soundIsNone;
  282. descriptor.updateSound(*sound);
  283. }
  284. };
  285. menu->addAction(
  286. (soundIsNone
  287. ? tr::lng_mute_menu_sound_on(tr::now)
  288. : tr::lng_mute_menu_sound_off(tr::now)),
  289. toggleSound,
  290. soundIsNone ? &st::menuIconSoundOn : &st::menuIconSoundOff);
  291. const auto &st = menu->st().menu;
  292. const auto iconTextPosition = st.itemIconPosition
  293. + st::menuIconMuteForAnyTextPosition;
  294. for (const auto muteFor : session->settings().mutePeriods()) {
  295. const auto callback = [=, update = descriptor.updateMutePeriod] {
  296. update(muteFor);
  297. };
  298. auto item = base::make_unique_q<IconWithText>(
  299. menu,
  300. st,
  301. Ui::Menu::CreateAction(
  302. menu->menu().get(),
  303. tr::lng_mute_menu_duration_any(
  304. tr::now,
  305. lt_duration,
  306. Ui::FormatMuteFor(muteFor)),
  307. callback),
  308. &st::menuIconMuteForAny,
  309. &st::menuIconMuteForAny);
  310. item->setData(Ui::FormatMuteForTiny(muteFor), iconTextPosition);
  311. menu->addAction(std::move(item));
  312. }
  313. menu->addAction(
  314. tr::lng_mute_menu_duration(tr::now),
  315. [=] { show->showBox(Box(PickMuteBox, descriptor)); },
  316. &st::menuIconMuteFor);
  317. menu->addAction(
  318. base::make_unique_q<MuteItem>(menu, menu->st().menu, descriptor));
  319. }
  320. void SetupMuteMenu(
  321. not_null<Ui::RpWidget*> parent,
  322. rpl::producer<> triggers,
  323. Fn<std::optional<Descriptor>()> makeDescriptor,
  324. std::shared_ptr<Ui::Show> show) {
  325. struct State {
  326. base::unique_qptr<Ui::PopupMenu> menu;
  327. };
  328. const auto state = parent->lifetime().make_state<State>();
  329. std::move(
  330. triggers
  331. ) | rpl::start_with_next([=] {
  332. if (state->menu) {
  333. return;
  334. } else if (const auto descriptor = makeDescriptor()) {
  335. state->menu = base::make_unique_q<Ui::PopupMenu>(
  336. parent,
  337. st::popupMenuWithIcons);
  338. FillMuteMenu(state->menu.get(), *descriptor, show);
  339. state->menu->popup(QCursor::pos());
  340. }
  341. }, parent->lifetime());
  342. }
  343. } // namespace MuteMenu