From e120ae6ae6bf526e21125d8fd86f09589c49a65e Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Fri, 10 May 2024 15:00:30 +0400
Subject: [PATCH] Pass effect to API for sending.

---
 Telegram/SourceFiles/api/api_polls.cpp        |   3 +
 Telegram/SourceFiles/api/api_sending.cpp      |   9 +
 Telegram/SourceFiles/apiwrap.cpp              |  13 +-
 .../media/stories/media_stories_share.cpp     |  15 +-
 Telegram/SourceFiles/menu/menu_send.cpp       | 165 +++++++++++++++++-
 5 files changed, 194 insertions(+), 11 deletions(-)

diff --git a/Telegram/SourceFiles/api/api_polls.cpp b/Telegram/SourceFiles/api/api_polls.cpp
index b2422af25..52a5f6d6f 100644
--- a/Telegram/SourceFiles/api/api_polls.cpp
+++ b/Telegram/SourceFiles/api/api_polls.cpp
@@ -68,6 +68,9 @@ void Polls::create(
 	if (action.options.shortcutId) {
 		sendFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut;
 	}
+	if (action.options.effectId) {
+		sendFlags |= MTPmessages_SendMedia::Flag::f_effect;
+	}
 	const auto sendAs = action.options.sendAs;
 	if (sendAs) {
 		sendFlags |= MTPmessages_SendMedia::Flag::f_send_as;
diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp
index 9528ae446..cbe617b75 100644
--- a/Telegram/SourceFiles/api/api_sending.cpp
+++ b/Telegram/SourceFiles/api/api_sending.cpp
@@ -133,6 +133,9 @@ void SendExistingMedia(
 		flags |= MessageFlag::ShortcutMessage;
 		sendFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut;
 	}
+	if (action.options.effectId) {
+		sendFlags |= MTPmessages_SendMedia::Flag::f_effect;
+	}
 
 	session->data().registerMessageRandomId(randomId, newId);
 
@@ -144,6 +147,7 @@ void SendExistingMedia(
 		.date = HistoryItem::NewMessageDate(action.options),
 		.shortcutId = action.options.shortcutId,
 		.postAuthor = messagePostAuthor,
+		.effectId = action.options.effectId,
 	}, media, caption);
 
 	const auto performRequest = [=](const auto &repeatRequest) -> void {
@@ -307,6 +311,9 @@ bool SendDice(MessageToSend &message) {
 		flags |= MessageFlag::ShortcutMessage;
 		sendFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut;
 	}
+	if (action.options.effectId) {
+		sendFlags |= MTPmessages_SendMedia::Flag::f_effect;
+	}
 
 	session->data().registerMessageRandomId(randomId, newId);
 
@@ -318,6 +325,7 @@ bool SendDice(MessageToSend &message) {
 		.date = HistoryItem::NewMessageDate(action.options),
 		.shortcutId = action.options.shortcutId,
 		.postAuthor = messagePostAuthor,
+		.effectId = action.options.effectId,
 	}, TextWithEntities(), MTP_messageMediaDice(
 		MTP_int(0),
 		MTP_string(emoji)));
@@ -512,6 +520,7 @@ void SendConfirmedFile(
 			.shortcutId = file->to.options.shortcutId,
 			.postAuthor = messagePostAuthor,
 			.groupedId = groupId,
+			.effectId = file->to.options.effectId,
 		}, caption, media);
 	}
 
diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp
index c16926c73..0e96bcec1 100644
--- a/Telegram/SourceFiles/apiwrap.cpp
+++ b/Telegram/SourceFiles/apiwrap.cpp
@@ -3383,6 +3383,7 @@ void ApiWrap::forwardMessages(
 				.date = HistoryItem::NewMessageDate(action.options),
 				.shortcutId = action.options.shortcutId,
 				.postAuthor = messagePostAuthor,
+				.effectId = action.options.effectId,
 			}, item);
 			_session->data().registerMessageRandomId(randomId, newId);
 			if (!localIds) {
@@ -3483,6 +3484,7 @@ void ApiWrap::sendSharedContact(
 		.date = HistoryItem::NewMessageDate(action.options),
 		.shortcutId = action.options.shortcutId,
 		.postAuthor = messagePostAuthor,
+		.effectId = action.options.effectId,
 	}, TextWithEntities(), MTP_messageMediaContact(
 		MTP_string(phone),
 		MTP_string(firstName),
@@ -3816,6 +3818,10 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
 			sendFlags |= MTPmessages_SendMessage::Flag::f_quick_reply_shortcut;
 			mediaFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut;
 		}
+		if (action.options.effectId) {
+			sendFlags |= MTPmessages_SendMessage::Flag::f_effect;
+			mediaFlags |= MTPmessages_SendMedia::Flag::f_effect;
+		}
 		lastMessage = history->addNewLocalMessage({
 			.id = newId.msg,
 			.flags = flags,
@@ -3824,6 +3830,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
 			.date = HistoryItem::NewMessageDate(action.options),
 			.shortcutId = action.options.shortcutId,
 			.postAuthor = messagePostAuthor,
+			.effectId = action.options.effectId,
 		}, sending, media);
 		const auto done = [=](
 				const MTPUpdates &result,
@@ -4162,7 +4169,8 @@ void ApiWrap::sendMediaWithRandomId(
 		| (!sentEntities.v.isEmpty() ? Flag::f_entities : Flag(0))
 		| (options.scheduled ? Flag::f_schedule_date : Flag(0))
 		| (options.sendAs ? Flag::f_send_as : Flag(0))
-		| (options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0));
+		| (options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0))
+		| (options.effectId ? Flag::f_effect : Flag(0));
 
 	auto &histories = history->owner().histories();
 	const auto peer = history->peer;
@@ -4271,7 +4279,8 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
 		| (sendAs ? Flag::f_send_as : Flag(0))
 		| (album->options.shortcutId
 			? Flag::f_quick_reply_shortcut
-			: Flag(0));
+			: Flag(0))
+		| (album->options.effectId ? Flag::f_effect : Flag(0));
 	auto &histories = history->owner().histories();
 	const auto peer = history->peer;
 	histories.sendPreparedMessage(
diff --git a/Telegram/SourceFiles/media/stories/media_stories_share.cpp b/Telegram/SourceFiles/media/stories/media_stories_share.cpp
index bfd0e5c00..9d32a126a 100644
--- a/Telegram/SourceFiles/media/stories/media_stories_share.cpp
+++ b/Telegram/SourceFiles/media/stories/media_stories_share.cpp
@@ -128,9 +128,7 @@ namespace Media::Stories {
 			if (action.replyTo) {
 				sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to;
 			}
-			const auto silentPost = ShouldSendSilent(
-				threadPeer,
-				action.options);
+			const auto silentPost = ShouldSendSilent(threadPeer, options);
 			if (silentPost) {
 				sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
 			}
@@ -140,6 +138,9 @@ namespace Media::Stories {
 			if (options.shortcutId) {
 				sendFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut;
 			}
+			if (options.effectId) {
+				sendFlags |= MTPmessages_SendMedia::Flag::f_effect;
+			}
 			const auto done = [=] {
 				if (!--state->requests) {
 					if (show->valid()) {
@@ -161,12 +162,10 @@ namespace Media::Stories {
 					MTP_long(randomId),
 					MTPReplyMarkup(),
 					MTPVector<MTPMessageEntity>(),
-					MTP_int(action.options.scheduled),
+					MTP_int(options.scheduled),
 					MTP_inputPeerEmpty(),
-					Data::ShortcutIdToMTP(
-						session,
-						action.options.shortcutId),
-					MTP_long(action.options.effectId)
+					Data::ShortcutIdToMTP(session, options.shortcutId),
+					MTP_long(options.effectId)
 				), [=](
 						const MTPUpdates &result,
 						const MTP::Response &response) {
diff --git a/Telegram/SourceFiles/menu/menu_send.cpp b/Telegram/SourceFiles/menu/menu_send.cpp
index 1f216122b..6b3955915 100644
--- a/Telegram/SourceFiles/menu/menu_send.cpp
+++ b/Telegram/SourceFiles/menu/menu_send.cpp
@@ -12,9 +12,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "boxes/abstract_box.h"
 #include "chat_helpers/compose/compose_show.h"
 #include "core/shortcuts.h"
+#include "history/view/media/history_view_sticker.h"
 #include "history/view/reactions/history_view_reactions_selector.h"
 #include "history/view/history_view_schedule_box.h"
 #include "lang/lang_keys.h"
+#include "ui/chat/chat_style.h"
+#include "ui/chat/chat_theme.h"
+#include "ui/widgets/buttons.h"
 #include "ui/widgets/popup_menu.h"
 #include "data/data_peer.h"
 #include "data/data_forum.h"
@@ -25,6 +29,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "history/history.h"
 #include "history/history_unread_things.h"
 #include "apiwrap.h"
+#include "window/themes/window_theme.h"
+#include "window/section_widget.h"
+#include "styles/style_chat.h"
 #include "styles/style_chat_helpers.h"
 #include "styles/style_menu_icons.h"
 
@@ -33,6 +40,33 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 namespace SendMenu {
 namespace {
 
+class EffectPreview final : public Ui::RpWidget {
+public:
+	EffectPreview(
+		not_null<QWidget*> parent,
+		std::shared_ptr<ChatHelpers::Show> show,
+		Details details,
+		QPoint position,
+		const Data::Reaction &effect,
+		Fn<void(Action, Details)> action);
+private:
+	void paintEvent(QPaintEvent *e) override;
+	void mousePressEvent(QMouseEvent *e) override;
+
+	void setupGeometry(QPoint position);
+	void setupBackground();
+	void setupSend(Details details);
+
+	const std::shared_ptr<ChatHelpers::Show> _show;
+	const std::shared_ptr<Ui::ChatTheme> _theme;
+	const std::unique_ptr<Ui::ChatStyle> _chatStyle;
+	const std::unique_ptr<Ui::FlatButton> _send;
+	const Fn<void(Action, Details)> _actionWithEffect;
+	QRect _inner;
+	QImage _bg;
+
+};
+
 [[nodiscard]] Data::PossibleItemReactionsRef LookupPossibleEffects(
 		not_null<Main::Session*> session) {
 	auto result = Data::PossibleItemReactionsRef();
@@ -51,6 +85,124 @@ namespace {
 	return result;
 }
 
+void ShowEffectPreview(
+		not_null<QWidget*> parent,
+		std::shared_ptr<ChatHelpers::Show> show,
+		Details details,
+		QPoint position,
+		const Data::Reaction &effect,
+		Fn<void(Action, Details)> action) {
+	const auto widget = Ui::CreateChild<EffectPreview>(
+		parent,
+		show,
+		details,
+		position,
+		effect,
+		action);
+	widget->raise();
+	widget->show();
+}
+
+[[nodiscard]] Fn<void(Action, Details)> ComposeActionWithEffect(
+		Fn<void(Action, Details)> sendAction,
+		EffectId id) {
+	if (!id) {
+		return sendAction;
+	}
+	return [=](Action action, Details details) {
+		if (const auto options = std::get_if<Api::SendOptions>(&action)) {
+			options->effectId = id;
+		}
+		sendAction(action, details);
+	};
+}
+
+EffectPreview::EffectPreview(
+	not_null<QWidget*> parent,
+	std::shared_ptr<ChatHelpers::Show> show,
+	Details details,
+	QPoint position,
+	const Data::Reaction &effect,
+	Fn<void(Action, Details)> action)
+: RpWidget(parent)
+, _show(show)
+, _theme(Window::Theme::DefaultChatThemeOn(lifetime()))
+, _chatStyle(
+	std::make_unique<Ui::ChatStyle>(
+		_show->session().colorIndicesValue()))
+, _send(
+	std::make_unique<Ui::FlatButton>(
+		this,
+		u"Send with Effect"_q,AssertIsDebug()
+		st::previewMarkRead))
+, _actionWithEffect(ComposeActionWithEffect(action, effect.id.custom())) {
+	setupGeometry(position);
+	setupBackground();
+	setupSend(details);
+}
+
+void EffectPreview::paintEvent(QPaintEvent *e) {
+	auto p = QPainter(this);
+	p.drawImage(0, 0, _bg);
+}
+
+void EffectPreview::mousePressEvent(QMouseEvent *e) {
+	delete this;
+}
+
+void EffectPreview::setupGeometry(QPoint position) {
+	const auto parent = parentWidget();
+	const auto innerSize = HistoryView::Sticker::MessageEffectSize();
+	const auto shadow = st::previewMenu.shadow;
+	const auto extend = shadow.extend;
+	_inner = QRect(QPoint(extend.left(), extend.top()), innerSize);
+	const auto size = _inner.marginsAdded(extend).size();
+	const auto left = std::max(
+		std::min(
+			position.x() - size.width() / 2,
+			parent->width() - size.width()),
+		0);
+	const auto topMin = std::min((parent->height() - size.height()) / 2, 0);
+	const auto top = std::max(
+		std::min(
+			position.y() - size.height() / 2,
+			parent->height() - size.height()),
+		topMin);
+	setGeometry(left, top, size.width(), size.height() + _send->height());
+	_send->setGeometry(0, size.height(), size.width(), _send->height());
+}
+
+void EffectPreview::setupBackground() {
+	const auto ratio = style::DevicePixelRatio();
+	_bg = QImage(
+		_inner.size() * ratio,
+		QImage::Format_ARGB32_Premultiplied);
+
+	const auto paint = [=] {
+		auto p = QPainter(&_bg);
+		Window::SectionWidget::PaintBackground(
+			p,
+			_theme.get(),
+			QSize(width(), height() * 5),
+			QRect(QPoint(), size()));
+	};
+	paint();
+	_theme->repaintBackgroundRequests() | rpl::start_with_next([=] {
+		paint();
+		update();
+	}, lifetime());
+}
+
+void EffectPreview::setupSend(Details details) {
+	_send->setClickedCallback([=] {
+		_actionWithEffect(Api::SendOptions(), details);
+	});
+	const auto type = details.type;
+	SetupMenuAndShortcuts(_send.get(), _show, [=] {
+		return Details{ .type = type };
+	}, _actionWithEffect);
+}
+
 } // namespace
 
 Fn<void(Action, Details)> DefaultCallback(
@@ -131,7 +283,18 @@ FillMenuResult FillSendMenu(
 
 	(*selector)->chosen(
 	) | rpl::start_with_next([=](ChosenReaction chosen) {
-
+		const auto &reactions = showForEffect->session().data().reactions();
+		const auto &effects = reactions.list(Data::Reactions::Type::Effects);
+		const auto i = ranges::find(effects, chosen.id, &Data::Reaction::id);
+		if (i != end(effects)) {
+			ShowEffectPreview(
+				menu,
+				showForEffect,
+				details,
+				menu->mapFromGlobal(chosen.globalGeometry.center()),
+				*i,
+				action);
+		}
 	}, menu->lifetime());
 
 	return FillMenuResult::Prepared;