From 1207e84dcb4fdccb836c93f22ef85e031cea98a6 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Tue, 8 Aug 2023 17:59:13 +0200
Subject: [PATCH] Add reaction menu to story context menu.

---
 .../history_view_reactions_selector.cpp       | 79 ++++++++++++-------
 .../history_view_reactions_selector.h         | 14 +++-
 .../stories/media_stories_controller.cpp      |  7 ++
 .../media/stories/media_stories_controller.h  |  7 ++
 .../media/stories/media_stories_reactions.cpp | 34 ++++++++
 .../media/stories/media_stories_reactions.h   |  7 ++
 .../media/stories/media_stories_view.cpp      |  7 ++
 .../media/stories/media_stories_view.h        | 13 +++
 .../SourceFiles/media/view/media_view.style   | 51 +++++-------
 .../media/view/media_view_overlay_widget.cpp  | 34 +++++---
 10 files changed, 182 insertions(+), 71 deletions(-)

diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp
index 0f2874c4e..e24577962 100644
--- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp
+++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp
@@ -1036,21 +1036,65 @@ AttachSelectorResult AttachSelectorToMenu(
 		Fn<void(ChosenReaction)> chosen,
 		Fn<void(FullMsgId)> showPremiumPromo,
 		IconFactory iconFactory) {
-	auto reactions = Data::LookupPossibleReactions(item);
+	const auto result = AttachSelectorToMenu(
+		menu,
+		desiredPosition,
+		st::reactPanelEmojiPan,
+		controller->uiShow(),
+		Data::LookupPossibleReactions(item),
+		std::move(iconFactory));
+	if (!result) {
+		return result.error();
+	}
+	const auto selector = *result;
+	const auto itemId = item->fullId();
+
+	selector->chosen() | rpl::start_with_next([=](ChosenReaction reaction) {
+		menu->hideMenu();
+		reaction.context = itemId;
+		chosen(std::move(reaction));
+	}, selector->lifetime());
+
+	selector->premiumPromoChosen() | rpl::start_with_next([=] {
+		menu->hideMenu();
+		showPremiumPromo(itemId);
+	}, selector->lifetime());
+
+	const auto weak = base::make_weak(controller);
+	controller->enableGifPauseReason(
+		Window::GifPauseReason::MediaPreview);
+	QObject::connect(menu.get(), &QObject::destroyed, [weak] {
+		if (const auto strong = weak.get()) {
+			strong->disableGifPauseReason(
+				Window::GifPauseReason::MediaPreview);
+		}
+	});
+
+	return AttachSelectorResult::Attached;
+}
+
+auto AttachSelectorToMenu(
+	not_null<Ui::PopupMenu*> menu,
+	QPoint desiredPosition,
+	const style::EmojiPan &st,
+	std::shared_ptr<ChatHelpers::Show> show,
+	const Data::PossibleItemReactionsRef &reactions,
+	IconFactory iconFactory)
+-> base::expected<not_null<Selector*>, AttachSelectorResult> {
 	if (reactions.recent.empty() && !reactions.morePremiumAvailable) {
-		return AttachSelectorResult::Skipped;
+		return base::make_unexpected(AttachSelectorResult::Skipped);
 	}
 	const auto withSearch = reactions.customAllowed;
 	const auto selector = Ui::CreateChild<Selector>(
 		menu.get(),
-		st::reactPanelEmojiPan,
-		controller->uiShow(),
+		st,
+		std::move(show),
 		std::move(reactions),
 		std::move(iconFactory),
 		[=](bool fast) { menu->hideMenu(fast); },
 		false); // child
 	if (!AdjustMenuGeometryForSelector(menu, desiredPosition, selector)) {
-		return AttachSelectorResult::Failed;
+		return base::make_unexpected(AttachSelectorResult::Failed);
 	}
 	if (withSearch) {
 		Ui::Platform::FixPopupMenuNativeEmojiPopup(menu);
@@ -1067,19 +1111,6 @@ AttachSelectorResult AttachSelectorToMenu(
 	selector->initGeometry(selectorInnerTop);
 	selector->show();
 
-	const auto itemId = item->fullId();
-
-	selector->chosen() | rpl::start_with_next([=](ChosenReaction reaction) {
-		menu->hideMenu();
-		reaction.context = itemId;
-		chosen(std::move(reaction));
-	}, selector->lifetime());
-
-	selector->premiumPromoChosen() | rpl::start_with_next([=] {
-		menu->hideMenu();
-		showPremiumPromo(itemId);
-	}, selector->lifetime());
-
 	const auto correctTop = selector->y();
 	menu->showStateValue(
 	) | rpl::start_with_next([=](Ui::PopupMenu::ShowState state) {
@@ -1100,17 +1131,7 @@ AttachSelectorResult AttachSelectorToMenu(
 			state.toggling);
 	}, selector->lifetime());
 
-	const auto weak = base::make_weak(controller);
-	controller->enableGifPauseReason(
-		Window::GifPauseReason::MediaPreview);
-	QObject::connect(menu.get(), &QObject::destroyed, [weak] {
-		if (const auto strong = weak.get()) {
-			strong->disableGifPauseReason(
-				Window::GifPauseReason::MediaPreview);
-		}
-	});
-
-	return AttachSelectorResult::Attached;
+	return selector;
 }
 
 } // namespace HistoryView::Reactions
diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h
index 270644bdc..8d3b34a03 100644
--- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h
+++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h
@@ -7,9 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #pragma once
 
-#include "history/view/reactions/history_view_reactions_strip.h"
-#include "data/data_message_reactions.h"
+#include "base/expected.h"
 #include "base/unique_qptr.h"
+#include "data/data_message_reactions.h"
+#include "history/view/reactions/history_view_reactions_strip.h"
 #include "ui/effects/animation_value.h"
 #include "ui/effects/round_area_with_shadow.h"
 #include "ui/rp_widget.h"
@@ -210,4 +211,13 @@ AttachSelectorResult AttachSelectorToMenu(
 	Fn<void(FullMsgId)> showPremiumPromo,
 	IconFactory iconFactory);
 
+[[nodiscard]] auto AttachSelectorToMenu(
+	not_null<Ui::PopupMenu*> menu,
+	QPoint desiredPosition,
+	const style::EmojiPan &st,
+	std::shared_ptr<ChatHelpers::Show> show,
+	const Data::PossibleItemReactionsRef &reactions,
+	IconFactory iconFactory
+) -> base::expected<not_null<Selector*>, AttachSelectorResult>;
+
 } // namespace HistoryView::Reactions
diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp
index b12e56585..0e776caaa 100644
--- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp
+++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp
@@ -1552,6 +1552,13 @@ void Controller::setupStealthMode() {
 	SetupStealthMode(uiShow());
 }
 
+auto Controller::attachReactionsToMenu(
+	not_null<Ui::PopupMenu*> menu,
+	QPoint desiredPosition)
+-> AttachStripResult {
+	return _reactions->attachToMenu(menu, desiredPosition);
+}
+
 rpl::lifetime &Controller::lifetime() {
 	return _lifetime;
 }
diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.h b/Telegram/SourceFiles/media/stories/media_stories_controller.h
index d7a9c37ff..f932c40a6 100644
--- a/Telegram/SourceFiles/media/stories/media_stories_controller.h
+++ b/Telegram/SourceFiles/media/stories/media_stories_controller.h
@@ -32,11 +32,13 @@ class DocumentMedia;
 namespace HistoryView::Reactions {
 class CachedIconFactory;
 struct ChosenReaction;
+enum class AttachSelectorResult;
 } // namespace HistoryView::Reactions
 
 namespace Ui {
 class RpWidget;
 class BoxContent;
+class PopupMenu;
 } // namespace Ui
 
 namespace Ui::Toast {
@@ -167,6 +169,11 @@ public:
 	[[nodiscard]] bool allowStealthMode() const;
 	void setupStealthMode();
 
+	using AttachStripResult = HistoryView::Reactions::AttachSelectorResult;
+	[[nodiscard]] AttachStripResult attachReactionsToMenu(
+		not_null<Ui::PopupMenu*> menu,
+		QPoint desiredPosition);
+
 	[[nodiscard]] rpl::lifetime &lifetime();
 
 private:
diff --git a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp
index e9162d891..02b42d593 100644
--- a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp
+++ b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp
@@ -14,12 +14,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_document.h"
 #include "data/data_document_media.h"
 #include "data/data_message_reactions.h"
+#include "data/data_peer.h"
 #include "data/data_session.h"
 #include "history/view/reactions/history_view_reactions_selector.h"
 #include "main/main_session.h"
 #include "media/stories/media_stories_controller.h"
 #include "ui/effects/emoji_fly_animation.h"
 #include "ui/effects/reaction_fly_animation.h"
+#include "ui/widgets/popup_menu.h"
 #include "ui/animated_icon.h"
 #include "ui/painter.h"
 #include "styles/style_chat_helpers.h"
@@ -389,6 +391,38 @@ void Reactions::attachToReactionButton(not_null<Ui::RpWidget*> button) {
 	_panel->attachToReactionButton(button);
 }
 
+auto Reactions::attachToMenu(
+	not_null<Ui::PopupMenu*> menu,
+	QPoint desiredPosition)
+-> AttachStripResult {
+	using namespace HistoryView::Reactions;
+
+	const auto story = _controller->story();
+	if (!story || story->peer()->isSelf()) {
+		return AttachStripResult::Skipped;
+	}
+
+	const auto show = _controller->uiShow();
+	const auto result = AttachSelectorToMenu(
+		menu,
+		desiredPosition,
+		st::storiesReactionsPan,
+		show,
+		LookupPossibleReactions(&show->session()),
+		_controller->cachedReactionIconFactory().createMethod());
+	if (!result) {
+		return result.error();
+	}
+	const auto selector = *result;
+
+	selector->chosen() | rpl::start_with_next([=](ChosenReaction reaction) {
+		menu->hideMenu();
+		animateAndProcess({ reaction, ReactionsMode::Reaction });
+	}, selector->lifetime());
+
+	return AttachSelectorResult::Attached;
+}
+
 Data::ReactionId Reactions::liked() const {
 	return _liked.current();
 }
diff --git a/Telegram/SourceFiles/media/stories/media_stories_reactions.h b/Telegram/SourceFiles/media/stories/media_stories_reactions.h
index 5a16943a7..fbcf34b3c 100644
--- a/Telegram/SourceFiles/media/stories/media_stories_reactions.h
+++ b/Telegram/SourceFiles/media/stories/media_stories_reactions.h
@@ -20,6 +20,7 @@ class Story;
 namespace HistoryView::Reactions {
 class Selector;
 struct ChosenReaction;
+enum class AttachSelectorResult;
 } // namespace HistoryView::Reactions
 
 namespace Ui {
@@ -27,6 +28,7 @@ class RpWidget;
 struct ReactionFlyAnimationArgs;
 struct ReactionFlyCenter;
 class EmojiFlyAnimation;
+class PopupMenu;
 } // namespace Ui
 
 namespace Media::Stories {
@@ -69,6 +71,11 @@ public:
 		rpl::producer<bool> hasSendText);
 	void attachToReactionButton(not_null<Ui::RpWidget*> button);
 
+	using AttachStripResult = HistoryView::Reactions::AttachSelectorResult;
+	[[nodiscard]] AttachStripResult attachToMenu(
+		not_null<Ui::PopupMenu*> menu,
+		QPoint desiredPosition);
+
 private:
 	class Panel;
 
diff --git a/Telegram/SourceFiles/media/stories/media_stories_view.cpp b/Telegram/SourceFiles/media/stories/media_stories_view.cpp
index 1aa545726..41390c3c4 100644
--- a/Telegram/SourceFiles/media/stories/media_stories_view.cpp
+++ b/Telegram/SourceFiles/media/stories/media_stories_view.cpp
@@ -123,6 +123,13 @@ void View::setupStealthMode() {
 	_controller->setupStealthMode();
 }
 
+auto View::attachReactionsToMenu(
+	not_null<Ui::PopupMenu*> menu,
+	QPoint desiredPosition)
+-> AttachStripResult {
+	return _controller->attachReactionsToMenu(menu, desiredPosition);
+}
+
 SiblingView View::sibling(SiblingType type) const {
 	return _controller->sibling(type);
 }
diff --git a/Telegram/SourceFiles/media/stories/media_stories_view.h b/Telegram/SourceFiles/media/stories/media_stories_view.h
index e70b0a6c9..730488253 100644
--- a/Telegram/SourceFiles/media/stories/media_stories_view.h
+++ b/Telegram/SourceFiles/media/stories/media_stories_view.h
@@ -17,6 +17,14 @@ namespace Media::Player {
 struct TrackState;
 } // namespace Media::Player
 
+namespace HistoryView::Reactions {
+enum class AttachSelectorResult;
+} // namespace HistoryView::Reactions
+
+namespace Ui {
+class PopupMenu;
+} // namespace Ui
+
 namespace Media::Stories {
 
 class Delegate;
@@ -95,6 +103,11 @@ public:
 	[[nodiscard]] bool allowStealthMode() const;
 	void setupStealthMode();
 
+	using AttachStripResult = HistoryView::Reactions::AttachSelectorResult;
+	[[nodiscard]] AttachStripResult attachReactionsToMenu(
+		not_null<Ui::PopupMenu*> menu,
+		QPoint desiredPosition);
+
 	[[nodiscard]] rpl::lifetime &lifetime();
 
 private:
diff --git a/Telegram/SourceFiles/media/view/media_view.style b/Telegram/SourceFiles/media/view/media_view.style
index db01d30fa..a2426a990 100644
--- a/Telegram/SourceFiles/media/view/media_view.style
+++ b/Telegram/SourceFiles/media/view/media_view.style
@@ -145,29 +145,30 @@ mediaviewFileIconSize: 80px;
 mediaviewFileLink: defaultLinkButton;
 
 mediaviewMenuSeparator: MenuSeparator(defaultMenuSeparator) {
-	fg: mediaviewMenuFg;
+	fg: groupCallMenuBgOver;
 }
 mediaviewMenu: Menu(menuWithIcons) {
-	itemBg: mediaviewMenuBg;
-	itemBgOver: mediaviewMenuBgOver;
-	itemFg: mediaviewMenuFg;
-	itemFgOver: mediaviewMenuFg;
-	itemFgDisabled: mediaviewMenuFg;
-	itemFgShortcut: mediaviewMenuFg;
-	itemFgShortcutOver: mediaviewMenuFg;
-	itemFgShortcutDisabled: mediaviewMenuFg;
+	itemBg: groupCallMenuBg;
+	itemBgOver: groupCallMenuBgOver;
+	itemFg: groupCallMembersFg;
+	itemFgOver: groupCallMembersFg;
+	itemFgDisabled: groupCallMemberNotJoinedStatus;
+	itemFgShortcut: groupCallMemberNotJoinedStatus;
+	itemFgShortcutOver: groupCallMemberNotJoinedStatus;
+	itemFgShortcutDisabled: groupCallMemberNotJoinedStatus;
 
 	separator: mediaviewMenuSeparator;
+	arrow: icon {{ "menu/submenu_arrow", groupCallMemberNotJoinedStatus }};
 
 	ripple: RippleAnimation(defaultRippleAnimation) {
-		color: mediaviewMenuBgRipple;
+		color: groupCallMenuBgRipple;
 	}
 }
 mediaviewMenuShadow: Shadow(defaultEmptyShadow) {
-	fallback: mediaviewMenuBg;
+	fallback: groupCallMenuBg;
 }
 mediaviewPanelAnimation: PanelAnimation(defaultPanelAnimation) {
-	fadeBg: mediaviewMenuBg;
+	fadeBg: groupCallMenuBg;
 	shadow: mediaviewMenuShadow;
 }
 mediaviewPopupMenu: PopupMenu(defaultPopupMenu) {
@@ -178,7 +179,7 @@ mediaviewPopupMenu: PopupMenu(defaultPopupMenu) {
 mediaviewDropdownMenu: DropdownMenu(defaultDropdownMenu) {
 	menu: mediaviewMenu;
 	wrap: InnerDropdown(defaultInnerDropdown) {
-		bg: mediaviewMenuBg;
+		bg: groupCallMenuBg;
 		animation: mediaviewPanelAnimation;
 		scrollPadding: margins(0px, 8px, 0px, 8px);
 		shadow: mediaviewMenuShadow;
@@ -312,7 +313,7 @@ mediaviewSpeedMenu: MediaSpeedMenu(mediaPlayerSpeedMenu) {
 	dropdown: DropdownMenu(mediaviewDropdownMenu) {
 		menu: Menu(mediaviewMenu) {
 			separator: MenuSeparator(mediaviewMenuSeparator) {
-				fg: mediaviewMenuBgOver;
+				fg: groupCallMenuBgOver;
 				padding: margins(0px, 4px, 0px, 4px);
 				width: 6px;
 			}
@@ -478,9 +479,7 @@ storiesRemoveSet: IconButton(stickerPanRemoveSet) {
 	iconOver: icon {{ "simple_close", storiesComposeGrayIcon }};
 	ripple: storiesComposeRippleLight;
 }
-storiesMenuSeparator: MenuSeparator(defaultMenuSeparator) {
-	fg: groupCallMenuBgOver;
-}
+storiesMenuSeparator: mediaviewMenuSeparator;
 storiesMenu: Menu(defaultMenu) {
 	itemBg: groupCallMenuBg;
 	itemBgOver: groupCallMenuBgOver;
@@ -498,22 +497,14 @@ storiesMenu: Menu(defaultMenu) {
 		color: groupCallMenuBgRipple;
 	}
 }
-storiesMenuShadow: Shadow(defaultEmptyShadow) {
-	fallback: groupCallMenuBg;
-}
-storiesMenuAnimation: PanelAnimation(defaultPanelAnimation) {
-	fadeBg: groupCallMenuBg;
-	shadow: storiesMenuShadow;
-}
+storiesMenuShadow: mediaviewMenuShadow;
+storiesMenuAnimation: mediaviewPanelAnimation;
 storiesPopupMenu: PopupMenu(defaultPopupMenu) {
 	shadow: storiesMenuShadow;
 	menu: storiesMenu;
 	animation: storiesMenuAnimation;
 }
-storiesMenuWithIcons: Menu(storiesMenu) {
-	itemIconPosition: point(15px, 5px);
-	itemPadding: margins(54px, 8px, 17px, 8px);
-}
+storiesMenuWithIcons: mediaviewMenu;
 storiesPopupMenuWithIcons: PopupMenu(storiesPopupMenu) {
 	scrollPadding: margins(0px, 5px, 0px, 5px);
 	menu: storiesMenuWithIcons;
@@ -779,8 +770,8 @@ storiesViewsMenu: PopupMenu(storiesPopupMenuWithIcons) {
 	maxHeight: 320px;
 	menu: Menu(storiesMenuWithIcons) {
 		itemPadding: margins(54px, 8px, 17px, 8px);
-		widthMin: 215px;
-		widthMax: 250px;
+		widthMin: 240px;
+		widthMax: 240px;
 	}
 	radius: 7px;
 }
diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp
index 01e6ba1fc..277538cad 100644
--- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp
+++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp
@@ -56,6 +56,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "history/history_item_helpers.h"
 #include "history/view/media/history_view_media.h"
 #include "history/view/reactions/history_view_reactions_strip.h"
+#include "history/view/reactions/history_view_reactions_selector.h"
 #include "data/data_media_types.h"
 #include "data/data_session.h"
 #include "data/data_stories.h"
@@ -5849,20 +5850,33 @@ bool OverlayWidget::handleContextMenu(std::optional<QPoint> position) {
 			const style::icon *icon) {
 		_menu->addAction(text, std::move(handler), icon);
 	});
+
 	if (_menu->empty()) {
 		_menu = nullptr;
-	} else {
+		return true;
+	}
+	if (_stories) {
+		_stories->menuShown(true);
+	}
+	_menu->setDestroyedCallback(crl::guard(_widget, [=] {
 		if (_stories) {
-			_stories->menuShown(true);
+			_stories->menuShown(false);
 		}
-		_menu->setDestroyedCallback(crl::guard(_widget, [=] {
-			if (_stories) {
-				_stories->menuShown(false);
-			}
-			activateControls();
-			_receiveMouse = false;
-			InvokeQueued(_widget, [=] { receiveMouse(); });
-		}));
+		activateControls();
+		_receiveMouse = false;
+		InvokeQueued(_widget, [=] { receiveMouse(); });
+	}));
+
+	using HistoryView::Reactions::AttachSelectorResult;
+	const auto attached = _stories
+		? _stories->attachReactionsToMenu(_menu.get(), QCursor::pos())
+		: AttachSelectorResult::Skipped;
+	if (attached == AttachSelectorResult::Failed) {
+		_menu = nullptr;
+		return true;
+	} else if (attached == AttachSelectorResult::Attached) {
+		_menu->popupPrepared();
+	} else {
 		_menu->popup(QCursor::pos());
 	}
 	activateControls();