Add reaction menu to story context menu.

This commit is contained in:
John Preston 2023-08-08 17:59:13 +02:00
parent 2dfaf27884
commit 1207e84dcb
10 changed files with 182 additions and 71 deletions

View File

@ -1036,21 +1036,65 @@ AttachSelectorResult AttachSelectorToMenu(
Fn<void(ChosenReaction)> chosen, Fn<void(ChosenReaction)> chosen,
Fn<void(FullMsgId)> showPremiumPromo, Fn<void(FullMsgId)> showPremiumPromo,
IconFactory iconFactory) { 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) { if (reactions.recent.empty() && !reactions.morePremiumAvailable) {
return AttachSelectorResult::Skipped; return base::make_unexpected(AttachSelectorResult::Skipped);
} }
const auto withSearch = reactions.customAllowed; const auto withSearch = reactions.customAllowed;
const auto selector = Ui::CreateChild<Selector>( const auto selector = Ui::CreateChild<Selector>(
menu.get(), menu.get(),
st::reactPanelEmojiPan, st,
controller->uiShow(), std::move(show),
std::move(reactions), std::move(reactions),
std::move(iconFactory), std::move(iconFactory),
[=](bool fast) { menu->hideMenu(fast); }, [=](bool fast) { menu->hideMenu(fast); },
false); // child false); // child
if (!AdjustMenuGeometryForSelector(menu, desiredPosition, selector)) { if (!AdjustMenuGeometryForSelector(menu, desiredPosition, selector)) {
return AttachSelectorResult::Failed; return base::make_unexpected(AttachSelectorResult::Failed);
} }
if (withSearch) { if (withSearch) {
Ui::Platform::FixPopupMenuNativeEmojiPopup(menu); Ui::Platform::FixPopupMenuNativeEmojiPopup(menu);
@ -1067,19 +1111,6 @@ AttachSelectorResult AttachSelectorToMenu(
selector->initGeometry(selectorInnerTop); selector->initGeometry(selectorInnerTop);
selector->show(); 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(); const auto correctTop = selector->y();
menu->showStateValue( menu->showStateValue(
) | rpl::start_with_next([=](Ui::PopupMenu::ShowState state) { ) | rpl::start_with_next([=](Ui::PopupMenu::ShowState state) {
@ -1100,17 +1131,7 @@ AttachSelectorResult AttachSelectorToMenu(
state.toggling); state.toggling);
}, selector->lifetime()); }, selector->lifetime());
const auto weak = base::make_weak(controller); return selector;
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;
} }
} // namespace HistoryView::Reactions } // namespace HistoryView::Reactions

View File

@ -7,9 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
#include "history/view/reactions/history_view_reactions_strip.h" #include "base/expected.h"
#include "data/data_message_reactions.h"
#include "base/unique_qptr.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/animation_value.h"
#include "ui/effects/round_area_with_shadow.h" #include "ui/effects/round_area_with_shadow.h"
#include "ui/rp_widget.h" #include "ui/rp_widget.h"
@ -210,4 +211,13 @@ AttachSelectorResult AttachSelectorToMenu(
Fn<void(FullMsgId)> showPremiumPromo, Fn<void(FullMsgId)> showPremiumPromo,
IconFactory iconFactory); 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 } // namespace HistoryView::Reactions

View File

@ -1552,6 +1552,13 @@ void Controller::setupStealthMode() {
SetupStealthMode(uiShow()); SetupStealthMode(uiShow());
} }
auto Controller::attachReactionsToMenu(
not_null<Ui::PopupMenu*> menu,
QPoint desiredPosition)
-> AttachStripResult {
return _reactions->attachToMenu(menu, desiredPosition);
}
rpl::lifetime &Controller::lifetime() { rpl::lifetime &Controller::lifetime() {
return _lifetime; return _lifetime;
} }

View File

@ -32,11 +32,13 @@ class DocumentMedia;
namespace HistoryView::Reactions { namespace HistoryView::Reactions {
class CachedIconFactory; class CachedIconFactory;
struct ChosenReaction; struct ChosenReaction;
enum class AttachSelectorResult;
} // namespace HistoryView::Reactions } // namespace HistoryView::Reactions
namespace Ui { namespace Ui {
class RpWidget; class RpWidget;
class BoxContent; class BoxContent;
class PopupMenu;
} // namespace Ui } // namespace Ui
namespace Ui::Toast { namespace Ui::Toast {
@ -167,6 +169,11 @@ public:
[[nodiscard]] bool allowStealthMode() const; [[nodiscard]] bool allowStealthMode() const;
void setupStealthMode(); void setupStealthMode();
using AttachStripResult = HistoryView::Reactions::AttachSelectorResult;
[[nodiscard]] AttachStripResult attachReactionsToMenu(
not_null<Ui::PopupMenu*> menu,
QPoint desiredPosition);
[[nodiscard]] rpl::lifetime &lifetime(); [[nodiscard]] rpl::lifetime &lifetime();
private: private:

View File

@ -14,12 +14,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_document.h" #include "data/data_document.h"
#include "data/data_document_media.h" #include "data/data_document_media.h"
#include "data/data_message_reactions.h" #include "data/data_message_reactions.h"
#include "data/data_peer.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "history/view/reactions/history_view_reactions_selector.h" #include "history/view/reactions/history_view_reactions_selector.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "media/stories/media_stories_controller.h" #include "media/stories/media_stories_controller.h"
#include "ui/effects/emoji_fly_animation.h" #include "ui/effects/emoji_fly_animation.h"
#include "ui/effects/reaction_fly_animation.h" #include "ui/effects/reaction_fly_animation.h"
#include "ui/widgets/popup_menu.h"
#include "ui/animated_icon.h" #include "ui/animated_icon.h"
#include "ui/painter.h" #include "ui/painter.h"
#include "styles/style_chat_helpers.h" #include "styles/style_chat_helpers.h"
@ -389,6 +391,38 @@ void Reactions::attachToReactionButton(not_null<Ui::RpWidget*> button) {
_panel->attachToReactionButton(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 { Data::ReactionId Reactions::liked() const {
return _liked.current(); return _liked.current();
} }

View File

@ -20,6 +20,7 @@ class Story;
namespace HistoryView::Reactions { namespace HistoryView::Reactions {
class Selector; class Selector;
struct ChosenReaction; struct ChosenReaction;
enum class AttachSelectorResult;
} // namespace HistoryView::Reactions } // namespace HistoryView::Reactions
namespace Ui { namespace Ui {
@ -27,6 +28,7 @@ class RpWidget;
struct ReactionFlyAnimationArgs; struct ReactionFlyAnimationArgs;
struct ReactionFlyCenter; struct ReactionFlyCenter;
class EmojiFlyAnimation; class EmojiFlyAnimation;
class PopupMenu;
} // namespace Ui } // namespace Ui
namespace Media::Stories { namespace Media::Stories {
@ -69,6 +71,11 @@ public:
rpl::producer<bool> hasSendText); rpl::producer<bool> hasSendText);
void attachToReactionButton(not_null<Ui::RpWidget*> button); void attachToReactionButton(not_null<Ui::RpWidget*> button);
using AttachStripResult = HistoryView::Reactions::AttachSelectorResult;
[[nodiscard]] AttachStripResult attachToMenu(
not_null<Ui::PopupMenu*> menu,
QPoint desiredPosition);
private: private:
class Panel; class Panel;

View File

@ -123,6 +123,13 @@ void View::setupStealthMode() {
_controller->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 { SiblingView View::sibling(SiblingType type) const {
return _controller->sibling(type); return _controller->sibling(type);
} }

View File

@ -17,6 +17,14 @@ namespace Media::Player {
struct TrackState; struct TrackState;
} // namespace Media::Player } // namespace Media::Player
namespace HistoryView::Reactions {
enum class AttachSelectorResult;
} // namespace HistoryView::Reactions
namespace Ui {
class PopupMenu;
} // namespace Ui
namespace Media::Stories { namespace Media::Stories {
class Delegate; class Delegate;
@ -95,6 +103,11 @@ public:
[[nodiscard]] bool allowStealthMode() const; [[nodiscard]] bool allowStealthMode() const;
void setupStealthMode(); void setupStealthMode();
using AttachStripResult = HistoryView::Reactions::AttachSelectorResult;
[[nodiscard]] AttachStripResult attachReactionsToMenu(
not_null<Ui::PopupMenu*> menu,
QPoint desiredPosition);
[[nodiscard]] rpl::lifetime &lifetime(); [[nodiscard]] rpl::lifetime &lifetime();
private: private:

View File

@ -145,29 +145,30 @@ mediaviewFileIconSize: 80px;
mediaviewFileLink: defaultLinkButton; mediaviewFileLink: defaultLinkButton;
mediaviewMenuSeparator: MenuSeparator(defaultMenuSeparator) { mediaviewMenuSeparator: MenuSeparator(defaultMenuSeparator) {
fg: mediaviewMenuFg; fg: groupCallMenuBgOver;
} }
mediaviewMenu: Menu(menuWithIcons) { mediaviewMenu: Menu(menuWithIcons) {
itemBg: mediaviewMenuBg; itemBg: groupCallMenuBg;
itemBgOver: mediaviewMenuBgOver; itemBgOver: groupCallMenuBgOver;
itemFg: mediaviewMenuFg; itemFg: groupCallMembersFg;
itemFgOver: mediaviewMenuFg; itemFgOver: groupCallMembersFg;
itemFgDisabled: mediaviewMenuFg; itemFgDisabled: groupCallMemberNotJoinedStatus;
itemFgShortcut: mediaviewMenuFg; itemFgShortcut: groupCallMemberNotJoinedStatus;
itemFgShortcutOver: mediaviewMenuFg; itemFgShortcutOver: groupCallMemberNotJoinedStatus;
itemFgShortcutDisabled: mediaviewMenuFg; itemFgShortcutDisabled: groupCallMemberNotJoinedStatus;
separator: mediaviewMenuSeparator; separator: mediaviewMenuSeparator;
arrow: icon {{ "menu/submenu_arrow", groupCallMemberNotJoinedStatus }};
ripple: RippleAnimation(defaultRippleAnimation) { ripple: RippleAnimation(defaultRippleAnimation) {
color: mediaviewMenuBgRipple; color: groupCallMenuBgRipple;
} }
} }
mediaviewMenuShadow: Shadow(defaultEmptyShadow) { mediaviewMenuShadow: Shadow(defaultEmptyShadow) {
fallback: mediaviewMenuBg; fallback: groupCallMenuBg;
} }
mediaviewPanelAnimation: PanelAnimation(defaultPanelAnimation) { mediaviewPanelAnimation: PanelAnimation(defaultPanelAnimation) {
fadeBg: mediaviewMenuBg; fadeBg: groupCallMenuBg;
shadow: mediaviewMenuShadow; shadow: mediaviewMenuShadow;
} }
mediaviewPopupMenu: PopupMenu(defaultPopupMenu) { mediaviewPopupMenu: PopupMenu(defaultPopupMenu) {
@ -178,7 +179,7 @@ mediaviewPopupMenu: PopupMenu(defaultPopupMenu) {
mediaviewDropdownMenu: DropdownMenu(defaultDropdownMenu) { mediaviewDropdownMenu: DropdownMenu(defaultDropdownMenu) {
menu: mediaviewMenu; menu: mediaviewMenu;
wrap: InnerDropdown(defaultInnerDropdown) { wrap: InnerDropdown(defaultInnerDropdown) {
bg: mediaviewMenuBg; bg: groupCallMenuBg;
animation: mediaviewPanelAnimation; animation: mediaviewPanelAnimation;
scrollPadding: margins(0px, 8px, 0px, 8px); scrollPadding: margins(0px, 8px, 0px, 8px);
shadow: mediaviewMenuShadow; shadow: mediaviewMenuShadow;
@ -312,7 +313,7 @@ mediaviewSpeedMenu: MediaSpeedMenu(mediaPlayerSpeedMenu) {
dropdown: DropdownMenu(mediaviewDropdownMenu) { dropdown: DropdownMenu(mediaviewDropdownMenu) {
menu: Menu(mediaviewMenu) { menu: Menu(mediaviewMenu) {
separator: MenuSeparator(mediaviewMenuSeparator) { separator: MenuSeparator(mediaviewMenuSeparator) {
fg: mediaviewMenuBgOver; fg: groupCallMenuBgOver;
padding: margins(0px, 4px, 0px, 4px); padding: margins(0px, 4px, 0px, 4px);
width: 6px; width: 6px;
} }
@ -478,9 +479,7 @@ storiesRemoveSet: IconButton(stickerPanRemoveSet) {
iconOver: icon {{ "simple_close", storiesComposeGrayIcon }}; iconOver: icon {{ "simple_close", storiesComposeGrayIcon }};
ripple: storiesComposeRippleLight; ripple: storiesComposeRippleLight;
} }
storiesMenuSeparator: MenuSeparator(defaultMenuSeparator) { storiesMenuSeparator: mediaviewMenuSeparator;
fg: groupCallMenuBgOver;
}
storiesMenu: Menu(defaultMenu) { storiesMenu: Menu(defaultMenu) {
itemBg: groupCallMenuBg; itemBg: groupCallMenuBg;
itemBgOver: groupCallMenuBgOver; itemBgOver: groupCallMenuBgOver;
@ -498,22 +497,14 @@ storiesMenu: Menu(defaultMenu) {
color: groupCallMenuBgRipple; color: groupCallMenuBgRipple;
} }
} }
storiesMenuShadow: Shadow(defaultEmptyShadow) { storiesMenuShadow: mediaviewMenuShadow;
fallback: groupCallMenuBg; storiesMenuAnimation: mediaviewPanelAnimation;
}
storiesMenuAnimation: PanelAnimation(defaultPanelAnimation) {
fadeBg: groupCallMenuBg;
shadow: storiesMenuShadow;
}
storiesPopupMenu: PopupMenu(defaultPopupMenu) { storiesPopupMenu: PopupMenu(defaultPopupMenu) {
shadow: storiesMenuShadow; shadow: storiesMenuShadow;
menu: storiesMenu; menu: storiesMenu;
animation: storiesMenuAnimation; animation: storiesMenuAnimation;
} }
storiesMenuWithIcons: Menu(storiesMenu) { storiesMenuWithIcons: mediaviewMenu;
itemIconPosition: point(15px, 5px);
itemPadding: margins(54px, 8px, 17px, 8px);
}
storiesPopupMenuWithIcons: PopupMenu(storiesPopupMenu) { storiesPopupMenuWithIcons: PopupMenu(storiesPopupMenu) {
scrollPadding: margins(0px, 5px, 0px, 5px); scrollPadding: margins(0px, 5px, 0px, 5px);
menu: storiesMenuWithIcons; menu: storiesMenuWithIcons;
@ -779,8 +770,8 @@ storiesViewsMenu: PopupMenu(storiesPopupMenuWithIcons) {
maxHeight: 320px; maxHeight: 320px;
menu: Menu(storiesMenuWithIcons) { menu: Menu(storiesMenuWithIcons) {
itemPadding: margins(54px, 8px, 17px, 8px); itemPadding: margins(54px, 8px, 17px, 8px);
widthMin: 215px; widthMin: 240px;
widthMax: 250px; widthMax: 240px;
} }
radius: 7px; radius: 7px;
} }

View File

@ -56,6 +56,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_item_helpers.h" #include "history/history_item_helpers.h"
#include "history/view/media/history_view_media.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_strip.h"
#include "history/view/reactions/history_view_reactions_selector.h"
#include "data/data_media_types.h" #include "data/data_media_types.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_stories.h" #include "data/data_stories.h"
@ -5849,20 +5850,33 @@ bool OverlayWidget::handleContextMenu(std::optional<QPoint> position) {
const style::icon *icon) { const style::icon *icon) {
_menu->addAction(text, std::move(handler), icon); _menu->addAction(text, std::move(handler), icon);
}); });
if (_menu->empty()) { if (_menu->empty()) {
_menu = nullptr; _menu = nullptr;
} else { return true;
}
if (_stories) {
_stories->menuShown(true);
}
_menu->setDestroyedCallback(crl::guard(_widget, [=] {
if (_stories) { if (_stories) {
_stories->menuShown(true); _stories->menuShown(false);
} }
_menu->setDestroyedCallback(crl::guard(_widget, [=] { activateControls();
if (_stories) { _receiveMouse = false;
_stories->menuShown(false); InvokeQueued(_widget, [=] { receiveMouse(); });
} }));
activateControls();
_receiveMouse = false; using HistoryView::Reactions::AttachSelectorResult;
InvokeQueued(_widget, [=] { receiveMouse(); }); 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()); _menu->popup(QCursor::pos());
} }
activateControls(); activateControls();