window_main_menu.cpp 30 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082
  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 "window/window_main_menu.h"
  8. #include "apiwrap.h"
  9. #include "base/event_filter.h"
  10. #include "base/qt_signal_producer.h"
  11. #include "boxes/about_box.h"
  12. #include "boxes/peer_list_controllers.h"
  13. #include "boxes/premium_preview_box.h"
  14. #include "calls/calls_box_controller.h"
  15. #include "core/application.h"
  16. #include "core/click_handler_types.h"
  17. #include "data/data_changes.h"
  18. #include "data/data_document_media.h"
  19. #include "data/data_folder.h"
  20. #include "data/data_session.h"
  21. #include "data/data_stories.h"
  22. #include "data/data_user.h"
  23. #include "info/info_memento.h"
  24. #include "info/profile/info_profile_badge.h"
  25. #include "info/profile/info_profile_emoji_status_panel.h"
  26. #include "info/stories/info_stories_widget.h"
  27. #include "lang/lang_keys.h"
  28. #include "main/main_account.h"
  29. #include "main/main_domain.h"
  30. #include "main/main_session.h"
  31. #include "main/main_session_settings.h"
  32. #include "mtproto/mtproto_config.h"
  33. #include "settings/settings_advanced.h"
  34. #include "settings/settings_calls.h"
  35. #include "settings/settings_information.h"
  36. #include "storage/localstorage.h"
  37. #include "storage/storage_account.h"
  38. #include "support/support_templates.h"
  39. #include "ui/boxes/confirm_box.h"
  40. #include "ui/chat/chat_theme.h"
  41. #include "ui/controls/swipe_handler.h"
  42. #include "ui/controls/userpic_button.h"
  43. #include "ui/effects/snowflakes.h"
  44. #include "ui/effects/toggle_arrow.h"
  45. #include "ui/painter.h"
  46. #include "ui/text/text_options.h"
  47. #include "ui/text/text_utilities.h"
  48. #include "ui/ui_utility.h"
  49. #include "ui/unread_badge_paint.h"
  50. #include "ui/vertical_list.h"
  51. #include "ui/widgets/menu/menu_add_action_callback_factory.h"
  52. #include "ui/widgets/popup_menu.h"
  53. #include "ui/widgets/scroll_area.h"
  54. #include "ui/widgets/shadow.h"
  55. #include "ui/wrap/slide_wrap.h"
  56. #include "window/themes/window_theme.h"
  57. #include "window/window_controller.h"
  58. #include "window/window_main_menu_helpers.h"
  59. #include "window/window_peer_menu.h"
  60. #include "window/window_session_controller.h"
  61. #include "styles/style_chat.h" // popupMenuExpandedSeparator
  62. #include "styles/style_info.h" // infoTopBarMenu
  63. #include "styles/style_layers.h"
  64. #include "styles/style_menu_icons.h"
  65. #include "styles/style_settings.h"
  66. #include "styles/style_window.h"
  67. #include <QtGui/QWindow>
  68. #include <QtGui/QScreen>
  69. #include <QtGui/QGuiApplication>
  70. #include <QtGui/QClipboard>
  71. namespace Window {
  72. namespace {
  73. constexpr auto kPlayStatusLimit = 2;
  74. [[nodiscard]] bool CanCheckSpecialEvent() {
  75. static const auto result = [] {
  76. const auto now = QDate::currentDate();
  77. return (now.month() == 12) || (now.month() == 1 && now.day() == 1);
  78. }();
  79. return result;
  80. }
  81. [[nodiscard]] bool CheckSpecialEvent() {
  82. const auto now = QDate::currentDate();
  83. return (now.month() == 12 && now.day() >= 24)
  84. || (now.month() == 1 && now.day() == 1);
  85. }
  86. void ShowCallsBox(not_null<Window::SessionController*> window) {
  87. struct State {
  88. State(not_null<Window::SessionController*> window)
  89. : callsController(window)
  90. , groupCallsController(window) {
  91. }
  92. Calls::BoxController callsController;
  93. PeerListContentDelegateSimple callsDelegate;
  94. Calls::GroupCalls::ListController groupCallsController;
  95. PeerListContentDelegateSimple groupCallsDelegate;
  96. base::unique_qptr<Ui::PopupMenu> menu;
  97. };
  98. window->show(Box([=](not_null<Ui::GenericBox*> box) {
  99. const auto state = box->lifetime().make_state<State>(window);
  100. const auto groupCalls = box->addRow(
  101. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  102. box,
  103. object_ptr<Ui::VerticalLayout>(box)),
  104. {});
  105. groupCalls->hide(anim::type::instant);
  106. groupCalls->toggleOn(state->groupCallsController.shownValue());
  107. Ui::AddSubsectionTitle(
  108. groupCalls->entity(),
  109. tr::lng_call_box_groupcalls_subtitle());
  110. state->groupCallsDelegate.setContent(groupCalls->entity()->add(
  111. object_ptr<PeerListContent>(box, &state->groupCallsController),
  112. {}));
  113. state->groupCallsController.setDelegate(&state->groupCallsDelegate);
  114. Ui::AddSkip(groupCalls->entity());
  115. Ui::AddDivider(groupCalls->entity());
  116. Ui::AddSkip(groupCalls->entity());
  117. const auto content = box->addRow(
  118. object_ptr<PeerListContent>(box, &state->callsController),
  119. {});
  120. state->callsDelegate.setContent(content);
  121. state->callsController.setDelegate(&state->callsDelegate);
  122. box->setWidth(state->callsController.contentWidth());
  123. state->callsController.boxHeightValue(
  124. ) | rpl::start_with_next([=](int height) {
  125. box->setMinHeight(height);
  126. }, box->lifetime());
  127. box->setTitle(tr::lng_call_box_title());
  128. box->addButton(tr::lng_close(), [=] {
  129. box->closeBox();
  130. });
  131. const auto menuButton = box->addTopButton(st::infoTopBarMenu);
  132. menuButton->setClickedCallback([=] {
  133. state->menu = base::make_unique_q<Ui::PopupMenu>(
  134. menuButton,
  135. st::popupMenuWithIcons);
  136. const auto showSettings = [=] {
  137. window->showSettings(
  138. Settings::Calls::Id(),
  139. Window::SectionShow(anim::type::instant));
  140. };
  141. const auto clearAll = crl::guard(box, [=] {
  142. box->uiShow()->showBox(Box(Calls::ClearCallsBox, window));
  143. });
  144. state->menu->addAction(
  145. tr::lng_settings_section_call_settings(tr::now),
  146. showSettings,
  147. &st::menuIconSettings);
  148. if (state->callsDelegate.peerListFullRowsCount() > 0) {
  149. Ui::Menu::CreateAddActionCallback(state->menu)({
  150. .text = tr::lng_call_box_clear_all(tr::now),
  151. .handler = clearAll,
  152. .icon = &st::menuIconDeleteAttention,
  153. .isAttention = true,
  154. });
  155. }
  156. state->menu->popup(QCursor::pos());
  157. return true;
  158. });
  159. }));
  160. }
  161. [[nodiscard]] rpl::producer<TextWithEntities> SetStatusLabel(
  162. not_null<Main::Session*> session) {
  163. const auto self = session->user();
  164. return session->changes().peerFlagsValue(
  165. self,
  166. Data::PeerUpdate::Flag::EmojiStatus
  167. ) | rpl::map([=] {
  168. return !!self->emojiStatusId();
  169. }) | rpl::distinct_until_changed() | rpl::map([](bool has) {
  170. const auto makeLink = [](const QString &text) {
  171. return Ui::Text::Link(text);
  172. };
  173. return (has
  174. ? tr::lng_menu_change_status
  175. : tr::lng_menu_set_status)(makeLink);
  176. }) | rpl::flatten_latest();
  177. }
  178. } // namespace
  179. class MainMenu::ToggleAccountsButton final : public Ui::AbstractButton {
  180. public:
  181. ToggleAccountsButton(QWidget *parent, not_null<Main::Account*> current);
  182. [[nodiscard]] int rightSkip() const {
  183. return _rightSkip.current();
  184. }
  185. [[nodiscard]] rpl::producer<int> rightSkipValue() const {
  186. return _rightSkip.value();
  187. }
  188. private:
  189. void paintEvent(QPaintEvent *e) override;
  190. void paintUnreadBadge(Painter &p);
  191. void validateUnreadBadge();
  192. [[nodiscard]] QString computeUnreadBadge() const;
  193. const not_null<Main::Account*> _current;
  194. rpl::variable<int> _rightSkip = 0;
  195. Ui::Animations::Simple _toggledAnimation;
  196. bool _toggled = false;
  197. QString _unreadBadge;
  198. bool _unreadBadgeStale = false;
  199. };
  200. class MainMenu::ResetScaleButton final : public Ui::AbstractButton {
  201. public:
  202. ResetScaleButton(QWidget *parent);
  203. protected:
  204. void paintEvent(QPaintEvent *e) override;
  205. static constexpr auto kText = "100%";
  206. };
  207. MainMenu::ToggleAccountsButton::ToggleAccountsButton(
  208. QWidget *parent,
  209. not_null<Main::Account*> current)
  210. : AbstractButton(parent)
  211. , _current(current) {
  212. rpl::single(rpl::empty) | rpl::then(
  213. Core::App().unreadBadgeChanges()
  214. ) | rpl::start_with_next([=] {
  215. _unreadBadgeStale = true;
  216. if (!_toggled) {
  217. validateUnreadBadge();
  218. update();
  219. }
  220. }, lifetime());
  221. auto &settings = Core::App().settings();
  222. if (Core::App().domain().accounts().size() < 2
  223. && settings.mainMenuAccountsShown()) {
  224. settings.setMainMenuAccountsShown(false);
  225. }
  226. settings.mainMenuAccountsShownValue(
  227. ) | rpl::filter([=](bool value) {
  228. return (_toggled != value);
  229. }) | rpl::start_with_next([=](bool value) {
  230. _toggled = value;
  231. _toggledAnimation.start(
  232. [=] { update(); },
  233. _toggled ? 0. : 1.,
  234. _toggled ? 1. : 0.,
  235. st::slideWrapDuration);
  236. validateUnreadBadge();
  237. }, lifetime());
  238. _toggledAnimation.stop();
  239. }
  240. void MainMenu::ToggleAccountsButton::paintEvent(QPaintEvent *e) {
  241. auto p = Painter(this);
  242. const auto path = Ui::ToggleUpDownArrowPath(
  243. 0. + width() - st::mainMenuTogglePosition.x(),
  244. 0. + height() - st::mainMenuTogglePosition.y(),
  245. st::mainMenuToggleSize,
  246. st::mainMenuToggleFourStrokes,
  247. _toggledAnimation.value(_toggled ? 1. : 0.));
  248. auto hq = PainterHighQualityEnabler(p);
  249. p.fillPath(path, st::windowSubTextFg);
  250. paintUnreadBadge(p);
  251. }
  252. void MainMenu::ToggleAccountsButton::paintUnreadBadge(Painter &p) {
  253. const auto progress = 1. - _toggledAnimation.value(_toggled ? 1. : 0.);
  254. if (!progress) {
  255. return;
  256. }
  257. validateUnreadBadge();
  258. if (_unreadBadge.isEmpty()) {
  259. return;
  260. }
  261. auto st = Settings::Badge::Style();
  262. const auto right = width()
  263. - st::mainMenuTogglePosition.x()
  264. - st::mainMenuToggleSize * 3;
  265. const auto top = height()
  266. - st::mainMenuTogglePosition.y()
  267. - st::mainMenuBadgeSize / 2;
  268. p.setOpacity(progress);
  269. Ui::PaintUnreadBadge(p, _unreadBadge, right, top, st);
  270. }
  271. void MainMenu::ToggleAccountsButton::validateUnreadBadge() {
  272. const auto base = st::mainMenuTogglePosition.x()
  273. + 2 * st::mainMenuToggleSize;
  274. if (_toggled) {
  275. _rightSkip = base;
  276. return;
  277. } else if (!_unreadBadgeStale) {
  278. return;
  279. }
  280. _unreadBadge = computeUnreadBadge();
  281. auto skip = base;
  282. if (!_unreadBadge.isEmpty()) {
  283. const auto st = Settings::Badge::Style();
  284. skip += 2 * st::mainMenuToggleSize
  285. + Ui::CountUnreadBadgeSize(_unreadBadge, st).width();
  286. }
  287. _rightSkip = skip;
  288. }
  289. QString MainMenu::ToggleAccountsButton::computeUnreadBadge() const {
  290. const auto state = OtherAccountsUnreadStateCurrent(_current);
  291. return state.allMuted
  292. ? QString()
  293. : (state.count > 0)
  294. ? Lang::FormatCountToShort(state.count).string
  295. : QString();
  296. }
  297. MainMenu::ResetScaleButton::ResetScaleButton(QWidget *parent)
  298. : AbstractButton(parent) {
  299. const auto margin = st::mainMenuCloudButton.height
  300. - st::mainMenuCloudSize;
  301. const auto textWidth = st::mainMenuResetScaleFont->width(kText);
  302. const auto innerWidth = st::mainMenuResetScaleLeft
  303. + textWidth
  304. + st::mainMenuResetScaleRight;
  305. const auto width = margin + innerWidth;
  306. resize(width, st::mainMenuCloudButton.height);
  307. }
  308. void MainMenu::ResetScaleButton::paintEvent(QPaintEvent *e) {
  309. Painter p(this);
  310. const auto innerHeight = st::mainMenuCloudSize;
  311. const auto radius = innerHeight / 2;
  312. const auto margin = st::mainMenuCloudButton.height
  313. - st::mainMenuCloudSize;
  314. const auto textWidth = st::mainMenuResetScaleFont->width(kText);
  315. const auto innerWidth = st::mainMenuResetScaleLeft
  316. + textWidth
  317. + st::mainMenuResetScaleRight;
  318. const auto left = margin / 2;
  319. const auto top = margin / 2;
  320. p.setPen(Qt::NoPen);
  321. p.setBrush(st::mainMenuCloudBg);
  322. p.drawRoundedRect(left, top, innerWidth, innerHeight, radius, radius);
  323. st::settingsIconInterfaceScale.paint(
  324. p,
  325. left + st::mainMenuResetScaleIconLeft,
  326. top + ((innerHeight - st::settingsIconInterfaceScale.height()) / 2),
  327. width(),
  328. st::mainMenuCloudFg->c);
  329. p.setFont(st::mainMenuResetScaleFont);
  330. p.setPen(st::mainMenuCloudFg);
  331. p.drawText(
  332. left + st::mainMenuResetScaleLeft,
  333. top + st::mainMenuResetScaleTop + st::mainMenuResetScaleFont->ascent,
  334. kText);
  335. }
  336. MainMenu::MainMenu(
  337. QWidget *parent,
  338. not_null<SessionController*> controller)
  339. : LayerWidget(parent)
  340. , _controller(controller)
  341. , _userpicButton(
  342. this,
  343. _controller->session().user(),
  344. st::mainMenuUserpic)
  345. , _toggleAccounts(this, &controller->session().account())
  346. , _setEmojiStatus(this, SetStatusLabel(&controller->session()))
  347. , _emojiStatusPanel(std::make_unique<Info::Profile::EmojiStatusPanel>())
  348. , _badge(std::make_unique<Info::Profile::Badge>(
  349. this,
  350. st::settingsInfoPeerBadge,
  351. &controller->session(),
  352. Info::Profile::BadgeContentForPeer(controller->session().user()),
  353. _emojiStatusPanel.get(),
  354. [=] { return controller->isGifPausedAtLeastFor(GifPauseReason::Layer); },
  355. kPlayStatusLimit,
  356. Info::Profile::BadgeType::Premium))
  357. , _scroll(this, st::defaultSolidScroll)
  358. , _inner(_scroll->setOwnedWidget(
  359. object_ptr<Ui::VerticalLayout>(_scroll.data())))
  360. , _topShadowSkip(_inner->add(
  361. object_ptr<Ui::FixedHeightWidget>(_inner.get(), st::lineWidth)))
  362. , _accounts(_inner->add(object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  363. _inner.get(),
  364. object_ptr<Ui::VerticalLayout>(_inner.get()))))
  365. , _shadow(_inner->add(object_ptr<Ui::SlideWrap<Ui::PlainShadow>>(
  366. _inner.get(),
  367. object_ptr<Ui::PlainShadow>(_inner.get()))))
  368. , _menu(_inner->add(
  369. object_ptr<Ui::VerticalLayout>(_inner.get()),
  370. { 0, st::mainMenuSkip, 0, 0 }))
  371. , _footer(_inner->add(object_ptr<Ui::RpWidget>(_inner.get())))
  372. , _telegram(
  373. Ui::CreateChild<Ui::FlatLabel>(_footer.get(), st::mainMenuTelegramLabel))
  374. , _version(AddVersionLabel(_footer)) {
  375. setAttribute(Qt::WA_OpaquePaintEvent);
  376. setupUserpicButton();
  377. setupAccountsToggle();
  378. setupSetEmojiStatus();
  379. setupAccounts();
  380. setupArchive();
  381. setupMenu();
  382. const auto shadow = Ui::CreateChild<Ui::PlainShadow>(this);
  383. widthValue(
  384. ) | rpl::start_with_next([=](int width) {
  385. const auto line = st::lineWidth;
  386. shadow->setGeometry(0, st::mainMenuCoverHeight - line, width, line);
  387. }, shadow->lifetime());
  388. _nightThemeSwitch.setCallback([this] {
  389. Expects(_nightThemeToggle != nullptr);
  390. const auto nightMode = Window::Theme::IsNightMode();
  391. if (_nightThemeToggle->toggled() != nightMode) {
  392. Window::Theme::ToggleNightMode();
  393. Window::Theme::KeepApplied();
  394. }
  395. });
  396. _footer->heightValue(
  397. ) | rpl::start_with_next([=] {
  398. _telegram->moveToLeft(st::mainMenuFooterLeft, _footer->height() - st::mainMenuTelegramBottom - _telegram->height());
  399. _version->moveToLeft(st::mainMenuFooterLeft, _footer->height() - st::mainMenuVersionBottom - _version->height());
  400. }, _footer->lifetime());
  401. rpl::combine(
  402. heightValue(),
  403. _inner->heightValue()
  404. ) | rpl::start_with_next([=] {
  405. updateInnerControlsGeometry();
  406. }, _inner->lifetime());
  407. parentResized();
  408. _telegram->setMarkedText(Ui::Text::Link(
  409. u"Telegram Desktop"_q,
  410. u"https://desktop.telegram.org"_q));
  411. _telegram->setLinksTrusted();
  412. _version->setMarkedText(
  413. Ui::Text::Link(
  414. tr::lng_settings_current_version(
  415. tr::now,
  416. lt_version,
  417. currentVersionText()),
  418. 1) // Link 1.
  419. .append(QChar(' '))
  420. .append(QChar(8211))
  421. .append(QChar(' '))
  422. .append(Ui::Text::Link(tr::lng_menu_about(tr::now), 2))); // Link 2.
  423. _version->setLink(
  424. 1,
  425. std::make_shared<UrlClickHandler>(Core::App().changelogLink()));
  426. _version->setLink(
  427. 2,
  428. std::make_shared<LambdaClickHandler>([=] {
  429. controller->show(Box<AboutBox>());
  430. }));
  431. rpl::combine(
  432. _toggleAccounts->rightSkipValue(),
  433. rpl::single(rpl::empty) | rpl::then(_badge->updated())
  434. ) | rpl::start_with_next([=] {
  435. moveBadge();
  436. }, lifetime());
  437. _badge->setPremiumClickCallback([=] {
  438. chooseEmojiStatus();
  439. });
  440. _controller->session().downloaderTaskFinished(
  441. ) | rpl::start_with_next([=] {
  442. update();
  443. }, lifetime());
  444. initResetScaleButton();
  445. if (CanCheckSpecialEvent() && CheckSpecialEvent()) {
  446. const auto snowLifetime = lifetime().make_state<rpl::lifetime>();
  447. const auto rebuild = [=] {
  448. const auto snowRaw = Ui::CreateChild<Ui::RpWidget>(this);
  449. const auto snow = snowLifetime->make_state<Ui::Snowflakes>(
  450. [=](const QRect &r) { snowRaw->update(r); });
  451. snow->setBrush(QColor(230, 230, 230));
  452. _showFinished.value(
  453. ) | rpl::start_with_next([=](bool shown) {
  454. snow->setPaused(!shown);
  455. }, snowRaw->lifetime());
  456. snowRaw->paintRequest(
  457. ) | rpl::start_with_next([=](const QRect &r) {
  458. auto p = Painter(snowRaw);
  459. p.fillRect(r, st::mainMenuBg);
  460. drawName(p);
  461. snow->paint(p, snowRaw->rect());
  462. }, snowRaw->lifetime());
  463. widthValue(
  464. ) | rpl::start_with_next([=](int width) {
  465. snowRaw->setGeometry(0, 0, width, st::mainMenuCoverHeight);
  466. }, snowRaw->lifetime());
  467. snowRaw->show();
  468. snowRaw->lower();
  469. snowRaw->setAttribute(Qt::WA_TransparentForMouseEvents);
  470. snowLifetime->add([=] { base::unique_qptr{ snowRaw }; });
  471. };
  472. Window::Theme::IsNightModeValue(
  473. ) | rpl::start_with_next([=](bool isNightMode) {
  474. snowLifetime->destroy();
  475. if (isNightMode) {
  476. rebuild();
  477. }
  478. }, lifetime());
  479. }
  480. setupSwipe();
  481. }
  482. MainMenu::~MainMenu() = default;
  483. void MainMenu::moveBadge() {
  484. if (!_badge->widget()) {
  485. return;
  486. }
  487. const auto available = width()
  488. - st::mainMenuCoverNameLeft
  489. - _toggleAccounts->rightSkip()
  490. - _badge->widget()->width();
  491. const auto left = st::mainMenuCoverNameLeft
  492. + std::min(_name.maxWidth() + st::semiboldFont->spacew, available);
  493. _badge->move(
  494. left,
  495. st::mainMenuCoverNameTop,
  496. st::mainMenuCoverNameTop + st::semiboldFont->height);
  497. }
  498. void MainMenu::setupArchive() {
  499. using namespace Settings;
  500. const auto controller = _controller;
  501. const auto folder = [=] {
  502. return controller->session().data().folderLoaded(Data::Folder::kId);
  503. };
  504. const auto showArchive = [=](Qt::KeyboardModifiers modifiers) {
  505. if (const auto f = folder()) {
  506. if (modifiers & Qt::ControlModifier) {
  507. controller->showInNewWindow(Window::SeparateId(
  508. Window::SeparateType::Archive,
  509. &controller->session()));
  510. } else {
  511. controller->openFolder(f);
  512. }
  513. controller->window().hideSettingsAndLayer();
  514. }
  515. };
  516. const auto checkArchive = [=] {
  517. const auto f = folder();
  518. return f
  519. && (!f->chatsList()->empty() || f->storiesCount() > 0)
  520. && controller->session().settings().archiveInMainMenu();
  521. };
  522. const auto wrap = _menu->add(
  523. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  524. _menu,
  525. object_ptr<Ui::VerticalLayout>(_menu)));
  526. const auto inner = wrap->entity();
  527. wrap->toggle(checkArchive(), anim::type::instant);
  528. const auto button = AddButtonWithIcon(
  529. inner,
  530. tr::lng_archived_name(),
  531. st::mainMenuButton,
  532. { &st::menuIconArchiveOpen });
  533. inner->add(
  534. object_ptr<Ui::PlainShadow>(inner),
  535. { 0, st::mainMenuSkip, 0, st::mainMenuSkip });
  536. button->setAcceptBoth(true);
  537. button->clicks(
  538. ) | rpl::start_with_next([=](Qt::MouseButton which) {
  539. if (which == Qt::LeftButton) {
  540. showArchive(button->clickModifiers());
  541. return;
  542. } else if (which != Qt::RightButton) {
  543. return;
  544. }
  545. _contextMenu = base::make_unique_q<Ui::PopupMenu>(
  546. this,
  547. st::popupMenuExpandedSeparator);
  548. Window::FillDialogsEntryMenu(
  549. _controller,
  550. Dialogs::EntryState{
  551. .key = folder(),
  552. .section = Dialogs::EntryState::Section::ContextMenu,
  553. },
  554. Ui::Menu::CreateAddActionCallback(_contextMenu));
  555. _contextMenu->popup(QCursor::pos());
  556. }, button->lifetime());
  557. const auto now = folder();
  558. auto folderValue = now
  559. ? (rpl::single(now) | rpl::type_erased())
  560. : controller->session().data().chatsListChanges(
  561. ) | rpl::filter([](Data::Folder *folder) {
  562. return folder && (folder->id() == Data::Folder::kId);
  563. }) | rpl::take(1);
  564. using namespace Settings;
  565. Badge::AddUnread(button, rpl::single(rpl::empty) | rpl::then(std::move(
  566. folderValue
  567. ) | rpl::map([=](not_null<Data::Folder*> folder) {
  568. return folder->owner().chatsList(folder)->unreadStateChanges();
  569. }) | rpl::flatten_latest() | rpl::to_empty) | rpl::map([=] {
  570. const auto loaded = folder();
  571. const auto state = loaded
  572. ? loaded->chatListBadgesState()
  573. : Dialogs::BadgesState();
  574. return Badge::UnreadBadge{ state.unreadCounter, true };
  575. }));
  576. rpl::merge(
  577. controller->session().data().chatsListChanges(
  578. ) | rpl::filter([](Data::Folder *folder) {
  579. return folder && (folder->id() == Data::Folder::kId);
  580. }) | rpl::to_empty,
  581. controller->session().data().stories().sourcesChanged(
  582. Data::StorySourcesList::Hidden
  583. )
  584. ) | rpl::start_with_next([=] {
  585. const auto isArchiveVisible = checkArchive();
  586. wrap->toggle(isArchiveVisible, anim::type::normal);
  587. if (!isArchiveVisible) {
  588. _contextMenu = nullptr;
  589. }
  590. update();
  591. }, lifetime());
  592. }
  593. void MainMenu::setupUserpicButton() {
  594. _userpicButton->setClickedCallback([=] { toggleAccounts(); });
  595. _userpicButton->show();
  596. }
  597. void MainMenu::toggleAccounts() {
  598. auto &settings = Core::App().settings();
  599. const auto shown = !settings.mainMenuAccountsShown();
  600. settings.setMainMenuAccountsShown(shown);
  601. Core::App().saveSettingsDelayed();
  602. }
  603. void MainMenu::setupAccounts() {
  604. const auto inner = _accounts->entity();
  605. inner->add(object_ptr<Ui::FixedHeightWidget>(inner, st::mainMenuSkip));
  606. auto events = Settings::SetupAccounts(inner, _controller);
  607. inner->add(object_ptr<Ui::FixedHeightWidget>(inner, st::mainMenuSkip));
  608. std::move(
  609. events.closeRequests
  610. ) | rpl::start_with_next([=] {
  611. closeLayer();
  612. }, inner->lifetime());
  613. _accounts->toggleOn(Core::App().settings().mainMenuAccountsShownValue());
  614. _accounts->finishAnimating();
  615. _shadow->setDuration(0)->toggleOn(_accounts->shownValue());
  616. }
  617. void MainMenu::setupAccountsToggle() {
  618. _toggleAccounts->show();
  619. _toggleAccounts->setAcceptBoth();
  620. _toggleAccounts->addClickHandler([=](Qt::MouseButton button) {
  621. if (button == Qt::LeftButton) {
  622. toggleAccounts();
  623. }
  624. });
  625. }
  626. void MainMenu::setupSetEmojiStatus() {
  627. _setEmojiStatus->overrideLinkClickHandler([=] {
  628. chooseEmojiStatus();
  629. });
  630. }
  631. void MainMenu::parentResized() {
  632. resize(st::mainMenuWidth, parentWidget()->height());
  633. }
  634. void MainMenu::showFinished() {
  635. _showFinished = true;
  636. }
  637. void MainMenu::setupMenu() {
  638. using namespace Settings;
  639. const auto controller = _controller;
  640. const auto addAction = [&](
  641. rpl::producer<QString> text,
  642. IconDescriptor &&descriptor) {
  643. return AddButtonWithIcon(
  644. _menu,
  645. std::move(text),
  646. st::mainMenuButton,
  647. std::move(descriptor));
  648. };
  649. if (!_controller->session().supportMode()) {
  650. _menu->add(
  651. CreateButtonWithIcon(
  652. _menu,
  653. tr::lng_menu_my_profile(),
  654. st::mainMenuButton,
  655. { &st::menuIconProfile })
  656. )->setClickedCallback([=] {
  657. controller->showSection(
  658. Info::Stories::Make(controller->session().user()));
  659. });
  660. SetupMenuBots(_menu, controller);
  661. _menu->add(
  662. object_ptr<Ui::PlainShadow>(_menu),
  663. { 0, st::mainMenuSkip, 0, st::mainMenuSkip });
  664. AddMyChannelsBox(addAction(
  665. tr::lng_create_group_title(),
  666. { &st::menuIconGroups }
  667. ), controller, true)->addClickHandler([=](Qt::MouseButton which) {
  668. if (which == Qt::LeftButton) {
  669. controller->showNewGroup();
  670. }
  671. });
  672. AddMyChannelsBox(addAction(
  673. tr::lng_create_channel_title(),
  674. { &st::menuIconChannel }
  675. ), controller, false)->addClickHandler([=](Qt::MouseButton which) {
  676. if (which == Qt::LeftButton) {
  677. controller->showNewChannel();
  678. }
  679. });
  680. addAction(
  681. tr::lng_menu_contacts(),
  682. { &st::menuIconUserShow }
  683. )->setClickedCallback([=] {
  684. controller->show(PrepareContactsBox(controller));
  685. });
  686. addAction(
  687. tr::lng_menu_calls(),
  688. { &st::menuIconPhone }
  689. )->setClickedCallback([=] {
  690. ShowCallsBox(controller);
  691. });
  692. addAction(
  693. tr::lng_saved_messages(),
  694. { &st::menuIconSavedMessages }
  695. )->setClickedCallback([=] {
  696. controller->showPeerHistory(controller->session().user());
  697. });
  698. } else {
  699. addAction(
  700. tr::lng_profile_add_contact(),
  701. { &st::menuIconProfile }
  702. )->setClickedCallback([=] {
  703. controller->showAddContact();
  704. });
  705. addAction(
  706. rpl::single(u"Fix chats order"_q),
  707. { &st::menuIconPin }
  708. )->toggleOn(rpl::single(
  709. _controller->session().settings().supportFixChatsOrder()
  710. ))->toggledChanges(
  711. ) | rpl::start_with_next([=](bool fix) {
  712. _controller->session().settings().setSupportFixChatsOrder(fix);
  713. _controller->session().saveSettings();
  714. }, _menu->lifetime());
  715. addAction(
  716. rpl::single(u"Reload templates"_q),
  717. { &st::menuIconRestore }
  718. )->setClickedCallback([=] {
  719. _controller->session().supportTemplates().reload();
  720. });
  721. }
  722. addAction(
  723. tr::lng_menu_settings(),
  724. { &st::menuIconSettings }
  725. )->setClickedCallback([=] {
  726. controller->showSettings();
  727. });
  728. _nightThemeToggle = addAction(
  729. tr::lng_menu_night_mode(),
  730. { &st::menuIconNightMode }
  731. )->toggleOn(_nightThemeSwitches.events_starting_with(
  732. Window::Theme::IsNightMode()
  733. ));
  734. _nightThemeToggle->toggledChanges(
  735. ) | rpl::filter([=](bool night) {
  736. return (night != Window::Theme::IsNightMode());
  737. }) | rpl::start_with_next([=](bool night) {
  738. if (Window::Theme::Background()->editingTheme()) {
  739. _nightThemeSwitches.fire(!night);
  740. controller->show(Ui::MakeInformBox(
  741. tr::lng_theme_editor_cant_change_theme()));
  742. return;
  743. }
  744. const auto weak = MakeWeak(this);
  745. const auto toggle = [=] {
  746. if (!weak) {
  747. Window::Theme::ToggleNightMode();
  748. Window::Theme::KeepApplied();
  749. } else {
  750. _nightThemeSwitch.callOnce(st::mainMenu.itemToggle.duration);
  751. }
  752. };
  753. Window::Theme::ToggleNightModeWithConfirmation(
  754. &_controller->window(),
  755. toggle);
  756. }, _nightThemeToggle->lifetime());
  757. Core::App().settings().systemDarkModeValue(
  758. ) | rpl::start_with_next([=](std::optional<bool> darkMode) {
  759. const auto darkModeEnabled
  760. = Core::App().settings().systemDarkModeEnabled();
  761. if (darkModeEnabled && darkMode.has_value()) {
  762. _nightThemeSwitches.fire_copy(*darkMode);
  763. }
  764. }, _nightThemeToggle->lifetime());
  765. }
  766. void MainMenu::resizeEvent(QResizeEvent *e) {
  767. _inner->resizeToWidth(width());
  768. updateControlsGeometry();
  769. }
  770. void MainMenu::updateControlsGeometry() {
  771. _userpicButton->moveToLeft(
  772. st::mainMenuUserpicLeft,
  773. st::mainMenuUserpicTop);
  774. if (_resetScaleButton) {
  775. _resetScaleButton->moveToRight(0, 0);
  776. }
  777. _setEmojiStatus->moveToLeft(
  778. st::mainMenuCoverStatusLeft,
  779. st::mainMenuCoverStatusTop,
  780. width());
  781. _toggleAccounts->setGeometry(
  782. 0,
  783. st::mainMenuCoverNameTop,
  784. width(),
  785. st::mainMenuCoverHeight - st::mainMenuCoverNameTop);
  786. // Allow cover shadow over the scrolled content.
  787. const auto top = st::mainMenuCoverHeight - st::lineWidth;
  788. _scroll->setGeometry(0, top, width(), height() - top);
  789. updateInnerControlsGeometry();
  790. }
  791. void MainMenu::updateInnerControlsGeometry() {
  792. const auto contentHeight = _accounts->height()
  793. + _shadow->height()
  794. + st::mainMenuSkip
  795. + _menu->height();
  796. const auto available = height() - st::mainMenuCoverHeight - contentHeight;
  797. const auto footerHeight = std::max(
  798. available,
  799. st::mainMenuFooterHeightMin);
  800. if (_footer->height() != footerHeight) {
  801. _footer->resize(_footer->width(), footerHeight);
  802. }
  803. }
  804. void MainMenu::chooseEmojiStatus() {
  805. if (const auto widget = _badge->widget()) {
  806. _emojiStatusPanel->show(_controller, widget, _badge->sizeTag());
  807. } else {
  808. ShowPremiumPreviewBox(_controller, PremiumFeature::EmojiStatus);
  809. }
  810. }
  811. bool MainMenu::eventHook(QEvent *event) {
  812. const auto type = event->type();
  813. if (type == QEvent::TouchBegin
  814. || type == QEvent::TouchUpdate
  815. || type == QEvent::TouchEnd
  816. || type == QEvent::TouchCancel) {
  817. QGuiApplication::sendEvent(_inner, event);
  818. }
  819. return RpWidget::eventHook(event);
  820. }
  821. void MainMenu::paintEvent(QPaintEvent *e) {
  822. auto p = Painter(this);
  823. const auto clip = e->rect();
  824. const auto cover = QRect(0, 0, width(), st::mainMenuCoverHeight);
  825. p.fillRect(clip, st::mainMenuBg);
  826. if (cover.intersects(clip)) {
  827. drawName(p);
  828. }
  829. }
  830. void MainMenu::drawName(Painter &p) {
  831. const auto widthText = width()
  832. - st::mainMenuCoverNameLeft
  833. - _toggleAccounts->rightSkip();
  834. const auto user = _controller->session().user();
  835. if (_nameVersion < user->nameVersion()) {
  836. _nameVersion = user->nameVersion();
  837. _name.setText(
  838. st::semiboldTextStyle,
  839. user->name(),
  840. Ui::NameTextOptions());
  841. moveBadge();
  842. }
  843. p.setFont(st::semiboldFont);
  844. p.setPen(st::windowBoldFg);
  845. _name.drawLeftElided(
  846. p,
  847. st::mainMenuCoverNameLeft,
  848. st::mainMenuCoverNameTop,
  849. (widthText
  850. - (_badge->widget()
  851. ? (st::semiboldFont->spacew + _badge->widget()->width())
  852. : 0)),
  853. width());
  854. }
  855. void MainMenu::initResetScaleButton() {
  856. _controller->widget()->screenValue(
  857. ) | rpl::map([](not_null<QScreen*> screen) {
  858. return rpl::single(
  859. screen->availableGeometry()
  860. ) | rpl::then(
  861. base::qt_signal_producer(
  862. screen.get(),
  863. &QScreen::availableGeometryChanged
  864. )
  865. );
  866. }) | rpl::flatten_latest(
  867. ) | rpl::map([](QRect available) {
  868. return (available.width() >= st::windowMinWidth)
  869. && (available.height() >= st::windowMinHeight);
  870. }) | rpl::distinct_until_changed(
  871. ) | rpl::start_with_next([=](bool good) {
  872. if (good) {
  873. _resetScaleButton.destroy();
  874. } else {
  875. _resetScaleButton.create(this);
  876. _resetScaleButton->addClickHandler([] {
  877. cSetConfigScale(style::kScaleDefault);
  878. Local::writeSettings();
  879. Core::Restart();
  880. });
  881. _resetScaleButton->show();
  882. updateControlsGeometry();
  883. }
  884. }, lifetime());
  885. }
  886. OthersUnreadState OtherAccountsUnreadStateCurrent(
  887. not_null<Main::Account*> current) {
  888. auto &domain = Core::App().domain();
  889. auto counter = 0;
  890. auto allMuted = true;
  891. for (const auto &[index, account] : domain.accounts()) {
  892. if (account.get() == current) {
  893. continue;
  894. } else if (const auto session = account->maybeSession()) {
  895. counter += session->data().unreadBadge();
  896. if (!session->data().unreadBadgeMuted()) {
  897. allMuted = false;
  898. }
  899. }
  900. }
  901. return {
  902. .count = counter,
  903. .allMuted = allMuted,
  904. };
  905. }
  906. rpl::producer<OthersUnreadState> OtherAccountsUnreadState(
  907. not_null<Main::Account*> current) {
  908. return rpl::single(rpl::empty) | rpl::then(
  909. Core::App().unreadBadgeChanges()
  910. ) | rpl::map([=] {
  911. return OtherAccountsUnreadStateCurrent(current);
  912. });
  913. }
  914. base::EventFilterResult MainMenu::redirectToInnerChecked(not_null<QEvent*> e) {
  915. if (_insideEventRedirect) {
  916. return base::EventFilterResult::Continue;
  917. }
  918. const auto weak = Ui::MakeWeak(this);
  919. _insideEventRedirect = true;
  920. QGuiApplication::sendEvent(_inner, e);
  921. if (weak) {
  922. _insideEventRedirect = false;
  923. }
  924. return base::EventFilterResult::Cancel;
  925. }
  926. void MainMenu::setupSwipe() {
  927. const auto outer = _controller->widget()->body();
  928. base::install_event_filter(this, outer, [=](not_null<QEvent*> e) {
  929. const auto type = e->type();
  930. if (type == QEvent::TouchBegin
  931. || type == QEvent::TouchUpdate
  932. || type == QEvent::TouchEnd
  933. || type == QEvent::TouchCancel) {
  934. return redirectToInnerChecked(e);
  935. } else if (type == QEvent::Wheel) {
  936. const auto w = static_cast<QWheelEvent*>(e.get());
  937. const auto d = Ui::ScrollDeltaF(w);
  938. if (std::abs(d.x()) > std::abs(d.y())) {
  939. return redirectToInnerChecked(e);
  940. }
  941. }
  942. return base::EventFilterResult::Continue;
  943. });
  944. const auto handles = outer->testAttribute(Qt::WA_AcceptTouchEvents);
  945. if (!handles) {
  946. outer->setAttribute(Qt::WA_AcceptTouchEvents);
  947. lifetime().add([=] {
  948. outer->setAttribute(Qt::WA_AcceptTouchEvents, false);
  949. });
  950. }
  951. Ui::Controls::SetupSwipeHandler(_inner, _scroll.data(), [=](
  952. Ui::Controls::SwipeContextData data) {
  953. if (data.translation < 0) {
  954. if (!_swipeBackData.callback) {
  955. _swipeBackData = Ui::Controls::SetupSwipeBack(
  956. this,
  957. [=]() -> std::pair<QColor, QColor> {
  958. return {
  959. st::historyForwardChooseBg->c,
  960. st::historyForwardChooseFg->c,
  961. };
  962. });
  963. }
  964. _swipeBackData.callback(data);
  965. return;
  966. } else if (_swipeBackData.lifetime) {
  967. _swipeBackData = {};
  968. }
  969. }, [=](int, Qt::LayoutDirection direction) {
  970. if (direction != Qt::LeftToRight) {
  971. return Ui::Controls::SwipeHandlerFinishData();
  972. }
  973. return Ui::Controls::DefaultSwipeBackHandlerFinishData([=] {
  974. closeLayer();
  975. });
  976. });
  977. }
  978. } // namespace Window