window_main_menu_helpers.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  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_helpers.h"
  8. #include "apiwrap.h"
  9. #include "base/platform/base_platform_info.h"
  10. #include "data/data_channel.h"
  11. #include "data/data_chat.h"
  12. #include "data/data_document.h"
  13. #include "data/data_document_media.h"
  14. #include "data/data_file_origin.h"
  15. #include "data/data_session.h"
  16. #include "data/data_user.h"
  17. #include "inline_bots/bot_attach_web_view.h"
  18. #include "lang/lang_keys.h"
  19. #include "lottie/lottie_icon.h"
  20. #include "main/main_session.h"
  21. #include "ui/controls/userpic_button.h"
  22. #include "ui/layers/generic_box.h"
  23. #include "ui/new_badges.h"
  24. #include "ui/painter.h"
  25. #include "ui/rect.h"
  26. #include "ui/widgets/popup_menu.h"
  27. #include "ui/widgets/tooltip.h"
  28. #include "ui/wrap/slide_wrap.h"
  29. #include "ui/ui_utility.h"
  30. #include "window/window_controller.h"
  31. #include "window/window_session_controller.h"
  32. #include "styles/style_chat.h"
  33. #include "styles/style_info.h"
  34. #include "styles/style_menu_icons.h"
  35. #include "styles/style_window.h"
  36. namespace Window {
  37. namespace {
  38. class VersionLabel final
  39. : public Ui::FlatLabel
  40. , public Ui::AbstractTooltipShower {
  41. public:
  42. using Ui::FlatLabel::FlatLabel;
  43. void clickHandlerActiveChanged(
  44. const ClickHandlerPtr &action,
  45. bool active) override {
  46. update();
  47. if (active && action && !action->dragText().isEmpty()) {
  48. Ui::Tooltip::Show(1000, this);
  49. } else {
  50. Ui::Tooltip::Hide();
  51. }
  52. }
  53. QString tooltipText() const override {
  54. return u"Build date: %1."_q.arg(__DATE__);
  55. }
  56. QPoint tooltipPos() const override {
  57. return QCursor::pos();
  58. }
  59. bool tooltipWindowActive() const override {
  60. return Ui::AppInFocus() && Ui::InFocusChain(window());
  61. }
  62. };
  63. } // namespace
  64. [[nodiscard]] not_null<Ui::FlatLabel*> AddVersionLabel(
  65. not_null<Ui::RpWidget*> parent) {
  66. return (Platform::IsMacStoreBuild() || Platform::IsWindowsStoreBuild())
  67. ? Ui::CreateChild<Ui::FlatLabel>(
  68. parent.get(),
  69. st::mainMenuVersionLabel)
  70. : Ui::CreateChild<VersionLabel>(
  71. parent.get(),
  72. st::mainMenuVersionLabel);
  73. }
  74. not_null<Ui::SettingsButton*> AddMyChannelsBox(
  75. not_null<Ui::SettingsButton*> button,
  76. not_null<SessionController*> controller,
  77. bool chats) {
  78. button->setAcceptBoth(true);
  79. const auto requestIcon = [=, session = &controller->session()](
  80. not_null<Ui::GenericBox*> box,
  81. Fn<void(not_null<DocumentData*>)> done) {
  82. const auto api = box->lifetime().make_state<MTP::Sender>(
  83. &session->mtp());
  84. api->request(MTPmessages_GetStickerSet(
  85. Data::InputStickerSet({
  86. .shortName = u"tg_placeholders_android"_q,
  87. }),
  88. MTP_int(0)
  89. )).done([=](const MTPmessages_StickerSet &result) {
  90. result.match([&](const MTPDmessages_stickerSet &data) {
  91. const auto &v = data.vdocuments().v;
  92. if (v.size() > 1) {
  93. done(session->data().processDocument(v[1]));
  94. }
  95. }, [](const MTPDmessages_stickerSetNotModified &) {
  96. });
  97. }).send();
  98. };
  99. const auto addIcon = [=](not_null<Ui::GenericBox*> box) {
  100. const auto widget = box->addRow(object_ptr<Ui::RpWidget>(box));
  101. widget->paintRequest(
  102. ) | rpl::start_with_next([=] {
  103. auto p = QPainter(widget);
  104. p.setFont(st::boxTextFont);
  105. p.setPen(st::windowSubTextFg);
  106. p.drawText(
  107. widget->rect(),
  108. tr::lng_contacts_loading(tr::now),
  109. style::al_center);
  110. }, widget->lifetime());
  111. widget->resize(Size(st::maxStickerSize));
  112. widget->show();
  113. box->verticalLayout()->resizeToWidth(box->width());
  114. requestIcon(box, [=](not_null<DocumentData*> document) {
  115. const auto view = document->createMediaView();
  116. const auto origin = document->stickerSetOrigin();
  117. controller->session().downloaderTaskFinished(
  118. ) | rpl::take_while([=] {
  119. if (view->bytes().isEmpty()) {
  120. return true;
  121. }
  122. auto owned = Lottie::MakeIcon({
  123. .json = Images::UnpackGzip(view->bytes()),
  124. .sizeOverride = Size(st::maxStickerSize),
  125. });
  126. const auto icon = owned.get();
  127. widget->lifetime().add([kept = std::move(owned)]{});
  128. widget->paintRequest(
  129. ) | rpl::start_with_next([=] {
  130. auto p = QPainter(widget);
  131. icon->paint(p, (widget->width() - icon->width()) / 2, 0);
  132. }, widget->lifetime());
  133. icon->animate(
  134. [=] { widget->update(); },
  135. 0,
  136. icon->framesCount());
  137. return false;
  138. }) | rpl::start(widget->lifetime());
  139. view->automaticLoad(origin, nullptr);
  140. view->videoThumbnailWanted(origin);
  141. });
  142. };
  143. const auto myChannelsBox = [=](not_null<Ui::GenericBox*> box) {
  144. box->setTitle(chats
  145. ? tr::lng_notification_groups()
  146. : tr::lng_notification_channels());
  147. box->addButton(tr::lng_close(), [=] { box->closeBox(); });
  148. const auto st = box->lifetime().make_state<style::UserpicButton>(
  149. st::defaultUserpicButton);
  150. st->photoSize = st::defaultPeerListItem.photoSize;
  151. st->size = QSize(st->photoSize, st->photoSize);
  152. const auto megagroupMark = u"[s] "_q;
  153. const auto add = [&](
  154. not_null<PeerData*> peer,
  155. not_null<Ui::VerticalLayout*> container) {
  156. const auto row = container->add(
  157. object_ptr<Ui::AbstractButton>::fromRaw(
  158. Ui::CreateSimpleSettingsButton(
  159. container,
  160. st::defaultRippleAnimation,
  161. st::defaultSettingsButton.textBgOver)));
  162. row->resize(row->width(), st::defaultPeerListItem.height);
  163. const auto c = peer->asChannel();
  164. const auto g = peer->asChat();
  165. const auto count = c ? c->membersCount() : g->count;
  166. const auto text = std::make_shared<Ui::Text::String>(
  167. st::defaultPeerListItem.nameStyle,
  168. ((c && c->isMegagroup()) ? megagroupMark : QString())
  169. + peer->name());
  170. const auto status = std::make_shared<Ui::Text::String>(
  171. st::defaultTextStyle,
  172. (g && !g->amIn())
  173. ? tr::lng_chat_status_unaccessible(tr::now)
  174. : !peer->username().isEmpty()
  175. ? ('@' + peer->username())
  176. : (count > 0)
  177. ? ((c && !c->isMegagroup())
  178. ? tr::lng_chat_status_subscribers
  179. : tr::lng_chat_status_members)(
  180. tr::now,
  181. lt_count,
  182. count)
  183. : QString());
  184. row->paintRequest() | rpl::start_with_next([=] {
  185. auto p = QPainter(row);
  186. const auto &st = st::defaultPeerListItem;
  187. const auto availableWidth = row->width()
  188. - st::boxRowPadding.right()
  189. - st.namePosition.x();
  190. p.setPen(st.nameFg);
  191. auto context = Ui::Text::PaintContext{
  192. .position = st.namePosition,
  193. .outerWidth = availableWidth,
  194. .availableWidth = availableWidth,
  195. .elisionLines = 1,
  196. };
  197. text->draw(p, context);
  198. p.setPen(st.statusFg);
  199. context.position = st.statusPosition;
  200. status->draw(p, context);
  201. }, row->lifetime());
  202. row->setClickedCallback([=] {
  203. controller->showPeerHistory(peer);
  204. });
  205. using Button = Ui::UserpicButton;
  206. const auto userpic = Ui::CreateChild<Button>(row, peer, *st);
  207. userpic->move(st::defaultPeerListItem.photoPosition);
  208. userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
  209. };
  210. const auto inaccessibleWrap = box->verticalLayout()->add(
  211. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  212. box->verticalLayout(),
  213. object_ptr<Ui::VerticalLayout>(box->verticalLayout())));
  214. inaccessibleWrap->toggle(false, anim::type::instant);
  215. const auto &data = controller->session().data();
  216. auto ids = std::vector<PeerId>();
  217. auto inaccessibleIds = std::vector<PeerId>();
  218. if (chats) {
  219. data.enumerateGroups([&](not_null<PeerData*> peer) {
  220. peer = peer->migrateToOrMe();
  221. if (ranges::contains(ids, peer->id)) {
  222. return;
  223. }
  224. const auto c = peer->asChannel();
  225. const auto g = peer->asChat();
  226. if ((c && c->amCreator()) || (g && g->amCreator())) {
  227. if (g && !g->amIn()) {
  228. inaccessibleIds.push_back(peer->id);
  229. add(peer, inaccessibleWrap->entity());
  230. } else {
  231. add(peer, box->verticalLayout());
  232. }
  233. ids.push_back(peer->id);
  234. }
  235. });
  236. } else {
  237. data.enumerateBroadcasts([&](not_null<ChannelData*> channel) {
  238. if (channel->amCreator()
  239. && !ranges::contains(ids, channel->id)) {
  240. ids.push_back(channel->id);
  241. add(channel, box->verticalLayout());
  242. }
  243. });
  244. }
  245. if (ids.empty()) {
  246. addIcon(box);
  247. }
  248. if (!inaccessibleIds.empty()) {
  249. const auto icon = [=] {
  250. return !inaccessibleWrap->toggled()
  251. ? &st::menuIconGroups
  252. : &st::menuIconGroupsHide;
  253. };
  254. auto button = object_ptr<Ui::IconButton>(box, st::backgroundSwitchToDark);
  255. button->setClickedCallback([=, raw = button.data()] {
  256. inaccessibleWrap->toggle(
  257. !inaccessibleWrap->toggled(),
  258. anim::type::normal);
  259. raw->setIconOverride(icon(), icon());
  260. });
  261. button->setIconOverride(icon(), icon());
  262. box->addTopButton(std::move(button));
  263. }
  264. };
  265. using Menu = base::unique_qptr<Ui::PopupMenu>;
  266. const auto menu = button->lifetime().make_state<Menu>();
  267. button->addClickHandler([=](Qt::MouseButton which) {
  268. if (which != Qt::RightButton) {
  269. return;
  270. }
  271. (*menu) = base::make_unique_q<Ui::PopupMenu>(
  272. button,
  273. st::popupMenuWithIcons);
  274. (*menu)->addAction(
  275. (chats ? tr::lng_menu_my_groups : tr::lng_menu_my_channels)(
  276. tr::now),
  277. [=] { controller->uiShow()->showBox(Box(myChannelsBox)); },
  278. chats ? &st::menuIconGroups : &st::menuIconChannel);
  279. (*menu)->popup(QCursor::pos());
  280. });
  281. return button;
  282. }
  283. void SetupMenuBots(
  284. not_null<Ui::VerticalLayout*> container,
  285. not_null<Window::SessionController*> controller) {
  286. const auto wrap = container->add(
  287. object_ptr<Ui::VerticalLayout>(container));
  288. const auto bots = &controller->session().attachWebView();
  289. const auto iconLoadLifetime = wrap->lifetime().make_state<
  290. rpl::lifetime
  291. >();
  292. rpl::single(
  293. rpl::empty
  294. ) | rpl::then(
  295. bots->attachBotsUpdates()
  296. ) | rpl::start_with_next([=] {
  297. const auto width = container->widthNoMargins();
  298. wrap->clear();
  299. for (const auto &bot : bots->attachBots()) {
  300. const auto user = bot.user;
  301. if (!bot.inMainMenu || !bot.media) {
  302. continue;
  303. } else if (const auto media = bot.media; !media->loaded()) {
  304. if (!*iconLoadLifetime) {
  305. auto &session = user->session();
  306. *iconLoadLifetime = session.downloaderTaskFinished(
  307. ) | rpl::start_with_next([=] {
  308. if (media->loaded()) {
  309. iconLoadLifetime->destroy();
  310. bots->notifyBotIconLoaded();
  311. }
  312. });
  313. }
  314. continue;
  315. }
  316. const auto button = wrap->add(object_ptr<Ui::SettingsButton>(
  317. wrap,
  318. rpl::single(bot.name),
  319. st::mainMenuButton));
  320. const auto menu = button->lifetime().make_state<
  321. base::unique_qptr<Ui::PopupMenu>
  322. >();
  323. const auto icon = Ui::CreateChild<InlineBots::MenuBotIcon>(
  324. button,
  325. bot.media);
  326. button->heightValue(
  327. ) | rpl::start_with_next([=](int height) {
  328. icon->move(
  329. st::mainMenuButton.iconLeft,
  330. (height - icon->height()) / 2);
  331. }, button->lifetime());
  332. const auto weak = Ui::MakeWeak(container);
  333. const auto show = controller->uiShow();
  334. button->setAcceptBoth(true);
  335. button->clicks(
  336. ) | rpl::start_with_next([=](Qt::MouseButton which) {
  337. if (which == Qt::LeftButton) {
  338. bots->open({
  339. .bot = user,
  340. .context = { .controller = controller },
  341. .source = InlineBots::WebViewSourceMainMenu(),
  342. });
  343. if (weak) {
  344. controller->window().hideSettingsAndLayer();
  345. }
  346. } else {
  347. (*menu) = nullptr;
  348. (*menu) = base::make_unique_q<Ui::PopupMenu>(
  349. button,
  350. st::popupMenuWithIcons);
  351. (*menu)->addAction(
  352. tr::lng_bot_remove_from_menu(tr::now),
  353. [=] { bots->removeFromMenu(show, user); },
  354. &st::menuIconDelete);
  355. (*menu)->popup(QCursor::pos());
  356. }
  357. }, button->lifetime());
  358. if (bots->showMainMenuNewBadge(bot)) {
  359. Ui::NewBadge::AddToRight(button);
  360. }
  361. }
  362. wrap->resizeToWidth(width);
  363. }, wrap->lifetime());
  364. }
  365. } // namespace Window