401 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			401 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
| This file is part of Telegram Desktop,
 | |
| the official desktop application for the Telegram messaging service.
 | |
| 
 | |
| For license and copyright information please follow this link:
 | |
| https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | |
| */
 | |
| #include "window/window_main_menu_helpers.h"
 | |
| 
 | |
| #include "apiwrap.h"
 | |
| #include "base/platform/base_platform_info.h"
 | |
| #include "data/data_channel.h"
 | |
| #include "data/data_chat.h"
 | |
| #include "data/data_document.h"
 | |
| #include "data/data_document_media.h"
 | |
| #include "data/data_file_origin.h"
 | |
| #include "data/data_session.h"
 | |
| #include "data/data_user.h"
 | |
| #include "inline_bots/bot_attach_web_view.h"
 | |
| #include "lang/lang_keys.h"
 | |
| #include "lottie/lottie_icon.h"
 | |
| #include "main/main_session.h"
 | |
| #include "ui/controls/userpic_button.h"
 | |
| #include "ui/layers/generic_box.h"
 | |
| #include "ui/new_badges.h"
 | |
| #include "ui/painter.h"
 | |
| #include "ui/rect.h"
 | |
| #include "ui/widgets/popup_menu.h"
 | |
| #include "ui/widgets/tooltip.h"
 | |
| #include "ui/wrap/slide_wrap.h"
 | |
| #include "window/window_controller.h"
 | |
| #include "window/window_session_controller.h"
 | |
| #include "styles/style_chat.h"
 | |
| #include "styles/style_info.h"
 | |
| #include "styles/style_menu_icons.h"
 | |
| #include "styles/style_window.h"
 | |
| 
 | |
| namespace Window {
 | |
| namespace {
 | |
| 
 | |
| class VersionLabel final
 | |
| 	: public Ui::FlatLabel
 | |
| 	, public Ui::AbstractTooltipShower {
 | |
| public:
 | |
| 	using Ui::FlatLabel::FlatLabel;
 | |
| 
 | |
| 	void clickHandlerActiveChanged(
 | |
| 			const ClickHandlerPtr &action,
 | |
| 			bool active) override {
 | |
| 		update();
 | |
| 		if (active && action && !action->dragText().isEmpty()) {
 | |
| 			Ui::Tooltip::Show(1000, this);
 | |
| 		} else {
 | |
| 			Ui::Tooltip::Hide();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	QString tooltipText() const override {
 | |
| 		return u"Build date: %1."_q.arg(__DATE__);
 | |
| 	}
 | |
| 
 | |
| 	QPoint tooltipPos() const override {
 | |
| 		return QCursor::pos();
 | |
| 	}
 | |
| 
 | |
| 	bool tooltipWindowActive() const override {
 | |
| 		return Ui::AppInFocus() && Ui::InFocusChain(window());
 | |
| 	}
 | |
| 
 | |
| };
 | |
| 
 | |
| } // namespace
 | |
| 
 | |
| [[nodiscard]] not_null<Ui::FlatLabel*> AddVersionLabel(
 | |
| 		not_null<Ui::RpWidget*> parent) {
 | |
| 	return (Platform::IsMacStoreBuild() || Platform::IsWindowsStoreBuild())
 | |
| 		? Ui::CreateChild<Ui::FlatLabel>(
 | |
| 			parent.get(),
 | |
| 			st::mainMenuVersionLabel)
 | |
| 		: Ui::CreateChild<VersionLabel>(
 | |
| 			parent.get(),
 | |
| 			st::mainMenuVersionLabel);
 | |
| }
 | |
| 
 | |
| not_null<Ui::SettingsButton*> AddMyChannelsBox(
 | |
| 		not_null<Ui::SettingsButton*> button,
 | |
| 		not_null<SessionController*> controller,
 | |
| 		bool chats) {
 | |
| 	button->setAcceptBoth(true);
 | |
| 
 | |
| 	const auto requestIcon = [=, session = &controller->session()](
 | |
| 			not_null<Ui::GenericBox*> box,
 | |
| 			Fn<void(not_null<DocumentData*>)> done) {
 | |
| 		const auto api = box->lifetime().make_state<MTP::Sender>(
 | |
| 			&session->mtp());
 | |
| 		api->request(MTPmessages_GetStickerSet(
 | |
| 			Data::InputStickerSet({
 | |
| 				.shortName = u"tg_placeholders_android"_q,
 | |
| 			}),
 | |
| 			MTP_int(0)
 | |
| 		)).done([=](const MTPmessages_StickerSet &result) {
 | |
| 			result.match([&](const MTPDmessages_stickerSet &data) {
 | |
| 				const auto &v = data.vdocuments().v;
 | |
| 				if (v.size() > 1) {
 | |
| 					done(session->data().processDocument(v[1]));
 | |
| 				}
 | |
| 			}, [](const MTPDmessages_stickerSetNotModified &) {
 | |
| 			});
 | |
| 		}).send();
 | |
| 	};
 | |
| 	const auto addIcon = [=](not_null<Ui::GenericBox*> box) {
 | |
| 		const auto widget = box->addRow(object_ptr<Ui::RpWidget>(box));
 | |
| 		widget->paintRequest(
 | |
| 		) | rpl::start_with_next([=] {
 | |
| 			auto p = QPainter(widget);
 | |
| 			p.setFont(st::boxTextFont);
 | |
| 			p.setPen(st::windowSubTextFg);
 | |
| 			p.drawText(
 | |
| 				widget->rect(),
 | |
| 				tr::lng_contacts_loading(tr::now),
 | |
| 				style::al_center);
 | |
| 		}, widget->lifetime());
 | |
| 		widget->resize(Size(st::maxStickerSize));
 | |
| 		widget->show();
 | |
| 		box->verticalLayout()->resizeToWidth(box->width());
 | |
| 		requestIcon(box, [=](not_null<DocumentData*> document) {
 | |
| 			const auto view = document->createMediaView();
 | |
| 			const auto origin = document->stickerSetOrigin();
 | |
| 			controller->session().downloaderTaskFinished(
 | |
| 			) | rpl::take_while([=] {
 | |
| 				if (view->bytes().isEmpty()) {
 | |
| 					return true;
 | |
| 				}
 | |
| 				auto owned = Lottie::MakeIcon({
 | |
| 					.json = Images::UnpackGzip(view->bytes()),
 | |
| 					.sizeOverride = Size(st::maxStickerSize),
 | |
| 				});
 | |
| 				const auto icon = owned.get();
 | |
| 				widget->lifetime().add([kept = std::move(owned)]{});
 | |
| 				widget->paintRequest(
 | |
| 				) | rpl::start_with_next([=] {
 | |
| 					auto p = QPainter(widget);
 | |
| 					icon->paint(p, (widget->width() - icon->width()) / 2, 0);
 | |
| 				}, widget->lifetime());
 | |
| 				icon->animate(
 | |
| 					[=] { widget->update(); },
 | |
| 					0,
 | |
| 					icon->framesCount());
 | |
| 				return false;
 | |
| 			}) | rpl::start(widget->lifetime());
 | |
| 			view->automaticLoad(origin, nullptr);
 | |
| 			view->videoThumbnailWanted(origin);
 | |
| 		});
 | |
| 	};
 | |
| 
 | |
| 	const auto myChannelsBox = [=](not_null<Ui::GenericBox*> box) {
 | |
| 		box->setTitle(chats
 | |
| 			? tr::lng_notification_groups()
 | |
| 			: tr::lng_notification_channels());
 | |
| 		box->addButton(tr::lng_close(), [=] { box->closeBox(); });
 | |
| 
 | |
| 		const auto st = box->lifetime().make_state<style::UserpicButton>(
 | |
| 			st::defaultUserpicButton);
 | |
| 		st->photoSize = st::defaultPeerListItem.photoSize;
 | |
| 		st->size = QSize(st->photoSize, st->photoSize);
 | |
| 
 | |
| 		class Button final : public Ui::SettingsButton {
 | |
| 		public:
 | |
| 			using Ui::SettingsButton::SettingsButton;
 | |
| 
 | |
| 			void setPeer(not_null<PeerData*> p) {
 | |
| 				const auto c = p->asChannel();
 | |
| 				const auto g = p->asChat();
 | |
| 				_text.setText(
 | |
| 					st::defaultPeerListItem.nameStyle,
 | |
| 					((c && c->isMegagroup()) ? u"[s] "_q : QString())
 | |
| 						+ p->name());
 | |
| 				const auto count = c ? c->membersCount() : g->count;
 | |
| 				_status.setText(
 | |
| 					st::defaultTextStyle,
 | |
| 					(g && !g->amIn())
 | |
| 						? tr::lng_chat_status_unaccessible(tr::now)
 | |
| 						: !p->username().isEmpty()
 | |
| 						? ('@' + p->username())
 | |
| 						: (count > 0)
 | |
| 						? ((c && !c->isMegagroup())
 | |
| 							? tr::lng_chat_status_subscribers
 | |
| 							: tr::lng_chat_status_members)(
 | |
| 								tr::now,
 | |
| 								lt_count,
 | |
| 								count)
 | |
| 						: QString());
 | |
| 			}
 | |
| 
 | |
| 			int resizeGetHeight(int) override {
 | |
| 				return st::defaultPeerListItem.height;
 | |
| 			}
 | |
| 
 | |
| 			void paintEvent(QPaintEvent *e) override {
 | |
| 				Ui::SettingsButton::paintEvent(e);
 | |
| 				auto p = Painter(this);
 | |
| 				const auto &st = st::defaultPeerListItem;
 | |
| 				const auto availableWidth = width()
 | |
| 					- st::boxRowPadding.right()
 | |
| 					- st.namePosition.x();
 | |
| 				p.setPen(st.nameFg);
 | |
| 				auto context = Ui::Text::PaintContext{
 | |
| 					.position = st.namePosition,
 | |
| 					.outerWidth = availableWidth,
 | |
| 					.availableWidth = availableWidth,
 | |
| 					.elisionLines = 1,
 | |
| 				};
 | |
| 				_text.draw(p, context);
 | |
| 				p.setPen(st.statusFg);
 | |
| 				context.position = st.statusPosition;
 | |
| 				_status.draw(p, context);
 | |
| 			}
 | |
| 
 | |
| 		private:
 | |
| 			Ui::Text::String _text;
 | |
| 			Ui::Text::String _status;
 | |
| 
 | |
| 		};
 | |
| 
 | |
| 		const auto add = [&](
 | |
| 				not_null<PeerData*> peer,
 | |
| 				not_null<Ui::VerticalLayout*> container) {
 | |
| 			const auto row = container->add(
 | |
| 				object_ptr<Button>(container, rpl::single(QString())));
 | |
| 			row->setPeer(peer);
 | |
| 			row->setClickedCallback([=] {
 | |
| 				controller->showPeerHistory(peer);
 | |
| 			});
 | |
| 			using Button = Ui::UserpicButton;
 | |
| 			const auto userpic = Ui::CreateChild<Button>(row, peer, *st);
 | |
| 			userpic->move(st::defaultPeerListItem.photoPosition);
 | |
| 			userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
 | |
| 		};
 | |
| 
 | |
| 		const auto inaccessibleWrap = box->verticalLayout()->add(
 | |
| 			object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
 | |
| 				box->verticalLayout(),
 | |
| 				object_ptr<Ui::VerticalLayout>(box->verticalLayout())));
 | |
| 		inaccessibleWrap->toggle(false, anim::type::instant);
 | |
| 
 | |
| 		const auto &data = controller->session().data();
 | |
| 		auto ids = std::vector<PeerId>();
 | |
| 		auto inaccessibleIds = std::vector<PeerId>();
 | |
| 
 | |
| 		if (chats) {
 | |
| 			data.enumerateGroups([&](not_null<PeerData*> peer) {
 | |
| 				peer = peer->migrateToOrMe();
 | |
| 				if (ranges::contains(ids, peer->id)) {
 | |
| 					return;
 | |
| 				}
 | |
| 				const auto c = peer->asChannel();
 | |
| 				const auto g = peer->asChat();
 | |
| 				if ((c && c->amCreator()) || (g && g->amCreator())) {
 | |
| 					if (g && !g->amIn()) {
 | |
| 						inaccessibleIds.push_back(peer->id);
 | |
| 						add(peer, inaccessibleWrap->entity());
 | |
| 					} else {
 | |
| 						add(peer, box->verticalLayout());
 | |
| 					}
 | |
| 					ids.push_back(peer->id);
 | |
| 				}
 | |
| 			});
 | |
| 		} else {
 | |
| 			data.enumerateBroadcasts([&](not_null<ChannelData*> channel) {
 | |
| 				if (channel->amCreator()
 | |
| 					&& !ranges::contains(ids, channel->id)) {
 | |
| 					ids.push_back(channel->id);
 | |
| 					add(channel, box->verticalLayout());
 | |
| 				}
 | |
| 			});
 | |
| 		}
 | |
| 		if (ids.empty()) {
 | |
| 			addIcon(box);
 | |
| 		}
 | |
| 		if (!inaccessibleIds.empty()) {
 | |
| 			const auto icon = [=] {
 | |
| 				return !inaccessibleWrap->toggled()
 | |
| 					? &st::menuIconGroups
 | |
| 					: &st::menuIconGroupsHide;
 | |
| 			};
 | |
| 			auto button = object_ptr<Ui::IconButton>(box, st::backgroundSwitchToDark);
 | |
| 			button->setClickedCallback([=, raw = button.data()] {
 | |
| 				inaccessibleWrap->toggle(
 | |
| 					!inaccessibleWrap->toggled(),
 | |
| 					anim::type::normal);
 | |
| 				raw->setIconOverride(icon(), icon());
 | |
| 			});
 | |
| 			button->setIconOverride(icon(), icon());
 | |
| 			box->addTopButton(std::move(button));
 | |
| 		}
 | |
| 	};
 | |
| 
 | |
| 	using Menu = base::unique_qptr<Ui::PopupMenu>;
 | |
| 	const auto menu = button->lifetime().make_state<Menu>();
 | |
| 	button->addClickHandler([=](Qt::MouseButton which) {
 | |
| 		if (which != Qt::RightButton) {
 | |
| 			return;
 | |
| 		}
 | |
| 
 | |
| 		(*menu) = base::make_unique_q<Ui::PopupMenu>(
 | |
| 			button,
 | |
| 			st::popupMenuWithIcons);
 | |
| 		(*menu)->addAction(
 | |
| 			(chats ? tr::lng_menu_my_groups : tr::lng_menu_my_channels)(
 | |
| 				tr::now),
 | |
| 			[=] { controller->uiShow()->showBox(Box(myChannelsBox)); },
 | |
| 			chats ? &st::menuIconGroups : &st::menuIconChannel);
 | |
| 		(*menu)->popup(QCursor::pos());
 | |
| 	});
 | |
| 
 | |
| 	return button;
 | |
| }
 | |
| 
 | |
| void SetupMenuBots(
 | |
| 		not_null<Ui::VerticalLayout*> container,
 | |
| 		not_null<Window::SessionController*> controller) {
 | |
| 	const auto wrap = container->add(
 | |
| 		object_ptr<Ui::VerticalLayout>(container));
 | |
| 	const auto bots = &controller->session().attachWebView();
 | |
| 	const auto iconLoadLifetime = wrap->lifetime().make_state<
 | |
| 		rpl::lifetime
 | |
| 	>();
 | |
| 
 | |
| 	rpl::single(
 | |
| 		rpl::empty
 | |
| 	) | rpl::then(
 | |
| 		bots->attachBotsUpdates()
 | |
| 	) | rpl::start_with_next([=] {
 | |
| 		const auto width = container->widthNoMargins();
 | |
| 		wrap->clear();
 | |
| 		for (const auto &bot : bots->attachBots()) {
 | |
| 			const auto user = bot.user;
 | |
| 			if (!bot.inMainMenu || !bot.media) {
 | |
| 				continue;
 | |
| 			} else if (const auto media = bot.media; !media->loaded()) {
 | |
| 				if (!*iconLoadLifetime) {
 | |
| 					auto &session = user->session();
 | |
| 					*iconLoadLifetime = session.downloaderTaskFinished(
 | |
| 					) | rpl::start_with_next([=] {
 | |
| 						if (media->loaded()) {
 | |
| 							iconLoadLifetime->destroy();
 | |
| 							bots->notifyBotIconLoaded();
 | |
| 						}
 | |
| 					});
 | |
| 				}
 | |
| 				continue;
 | |
| 			}
 | |
| 			const auto button = wrap->add(object_ptr<Ui::SettingsButton>(
 | |
| 				wrap,
 | |
| 				rpl::single(bot.name),
 | |
| 				st::mainMenuButton));
 | |
| 			const auto menu = button->lifetime().make_state<
 | |
| 				base::unique_qptr<Ui::PopupMenu>
 | |
| 			>();
 | |
| 			const auto icon = Ui::CreateChild<InlineBots::MenuBotIcon>(
 | |
| 				button,
 | |
| 				bot.media);
 | |
| 			button->heightValue(
 | |
| 			) | rpl::start_with_next([=](int height) {
 | |
| 				icon->move(
 | |
| 					st::mainMenuButton.iconLeft,
 | |
| 					(height - icon->height()) / 2);
 | |
| 			}, button->lifetime());
 | |
| 			const auto weak = Ui::MakeWeak(container);
 | |
| 			button->setAcceptBoth(true);
 | |
| 			button->clicks(
 | |
| 			) | rpl::start_with_next([=](Qt::MouseButton which) {
 | |
| 				if (which == Qt::LeftButton) {
 | |
| 					bots->requestSimple(controller, user, {
 | |
| 						.fromMainMenu = true,
 | |
| 					});
 | |
| 					if (weak) {
 | |
| 						controller->window().hideSettingsAndLayer();
 | |
| 					}
 | |
| 				} else {
 | |
| 					(*menu) = nullptr;
 | |
| 					(*menu) = base::make_unique_q<Ui::PopupMenu>(
 | |
| 						button,
 | |
| 						st::popupMenuWithIcons);
 | |
| 					(*menu)->addAction(
 | |
| 						tr::lng_bot_remove_from_menu(tr::now),
 | |
| 						[=] { bots->removeFromMenu(user); },
 | |
| 						&st::menuIconDelete);
 | |
| 					(*menu)->popup(QCursor::pos());
 | |
| 				}
 | |
| 			}, button->lifetime());
 | |
| 
 | |
| 			if (bots->showMainMenuNewBadge(bot)) {
 | |
| 				Ui::NewBadge::AddToRight(button);
 | |
| 			}
 | |
| 		}
 | |
| 		wrap->resizeToWidth(width);
 | |
| 	}, wrap->lifetime());
 | |
| }
 | |
| 
 | |
| } // namespace Window
 | 
