diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp index c0bee7bb6..9779cde58 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp @@ -1015,7 +1015,7 @@ void Controller::fillManageSection() { !_peer->isBroadcast(), session->data().reactions().list( Data::Reactions::Type::Active), - *Data::PeerAllowedReactions(_peer), + *Data::PeerReactionsFilter(_peer).allowed, done)); }, { &st::infoRoundedIconReactions, Settings::kIconRed }); diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp index 6cb9f190d..66dd9f199 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp @@ -566,45 +566,50 @@ void TabbedSelector::resizeEvent(QResizeEvent *e) { _tabsSlider->width(), st::lineWidth); } + updateScrollGeometry(e->oldSize()); + updateRestrictedLabelGeometry(); + updateFooterGeometry(); + update(); +} +void TabbedSelector::updateScrollGeometry(QSize oldSize) { auto scrollWidth = width() - st::roundRadiusSmall; auto scrollHeight = height() - scrollTop() - scrollBottom(); auto inner = currentTab()->widget(); auto innerWidth = scrollWidth - st::emojiScroll.width; - auto updateScrollGeometry = [&] { + auto setScrollGeometry = [&] { _scroll->setGeometryToLeft( st::roundRadiusSmall, scrollTop(), scrollWidth, scrollHeight); }; - auto updateInnerGeometry = [&] { + auto setInnerGeometry = [&] { auto scrollTop = _scroll->scrollTop(); auto scrollBottom = scrollTop + scrollHeight; inner->setMinimalHeight(innerWidth, scrollHeight); inner->setVisibleTopBottom(scrollTop, scrollBottom); }; - if (e->oldSize().height() > height()) { - updateScrollGeometry(); - updateInnerGeometry(); + if (oldSize.height() > height()) { + setScrollGeometry(); + setInnerGeometry(); } else { - updateInnerGeometry(); - updateScrollGeometry(); + setInnerGeometry(); + setScrollGeometry(); } _bottomShadow->setGeometry( 0, _scroll->y() + _scroll->height() - st::lineWidth, width(), st::lineWidth); - updateRestrictedLabelGeometry(); +} +void TabbedSelector::updateFooterGeometry() { _footerTop = _dropDown ? 0 : (height() - st::emojiFooterHeight); for (auto &tab : _tabs) { tab.footer()->resizeToWidth(width()); tab.footer()->moveToLeft(0, _footerTop); } - - update(); } void TabbedSelector::updateRestrictedLabelGeometry() { @@ -1139,6 +1144,15 @@ void TabbedSelector::showMenuWithType(SendMenu::Type type) { } } +void TabbedSelector::setDropDown(bool dropDown) { + if (_dropDown == dropDown) { + return; + } + _dropDown = dropDown; + updateFooterGeometry(); + updateScrollGeometry(size()); +} + rpl::producer<> TabbedSelector::contextMenuRequested() const { return events( ) | rpl::filter([=](not_null e) { diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h index 2b1e10acc..cfcdecef1 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h @@ -130,9 +130,7 @@ public: } void showMenuWithType(SendMenu::Type type); - void setDropDown(bool dropDown) { - _dropDown = dropDown; - } + void setDropDown(bool dropDown); // Float player interface. bool floatPlayerHandleWheelEvent(QEvent *e); @@ -204,6 +202,8 @@ private: void checkRestrictedPeer(); bool isRestrictedView(); void updateRestrictedLabelGeometry(); + void updateScrollGeometry(QSize oldSize); + void updateFooterGeometry(); void handleScroll(); QImage grabForAnimation(); diff --git a/Telegram/SourceFiles/data/data_message_reaction_id.cpp b/Telegram/SourceFiles/data/data_message_reaction_id.cpp new file mode 100644 index 000000000..653a481d6 --- /dev/null +++ b/Telegram/SourceFiles/data/data_message_reaction_id.cpp @@ -0,0 +1,32 @@ +/* +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 "data/data_message_reaction_id.h" + +namespace Data { + +ReactionId ReactionFromMTP(const MTPReaction &reaction) { + return reaction.match([](MTPDreactionEmpty) { + return ReactionId{ QString() }; + }, [](const MTPDreactionEmoji &data) { + return ReactionId{ qs(data.vemoticon()) }; + }, [](const MTPDreactionCustomEmoji &data) { + return ReactionId{ DocumentId(data.vdocument_id().v) }; + }); +} + +MTPReaction ReactionToMTP(ReactionId id) { + if (const auto custom = id.custom()) { + return MTP_reactionCustomEmoji(MTP_long(custom)); + } + const auto emoji = id.emoji(); + return emoji.isEmpty() + ? MTP_reactionEmpty() + : MTP_reactionEmoji(MTP_string(emoji)); +} + +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_message_reaction_id.h b/Telegram/SourceFiles/data/data_message_reaction_id.h new file mode 100644 index 000000000..c51e2f2c4 --- /dev/null +++ b/Telegram/SourceFiles/data/data_message_reaction_id.h @@ -0,0 +1,49 @@ +/* +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 +*/ +#pragma once + +namespace Data { + +struct ReactionId { + std::variant data; + + [[nodiscard]] bool empty() const { + const auto emoji = std::get_if(&data); + return emoji && emoji->isEmpty(); + } + [[nodiscard]] QString emoji() const { + const auto emoji = std::get_if(&data); + return emoji ? *emoji : QString(); + } + [[nodiscard]] DocumentId custom() const { + const auto custom = std::get_if(&data); + return custom ? *custom : DocumentId(); + } +}; +Q_DECLARE_METATYPE(ReactionId); + +inline bool operator<(const ReactionId &a, const ReactionId &b) { + return a.data < b.data; +} +inline bool operator==(const ReactionId &a, const ReactionId &b) { + return a.data == b.data; +} + +[[nodiscard]] ReactionId ReactionFromMTP(const MTPReaction &reaction); +[[nodiscard]] MTPReaction ReactionToMTP(ReactionId id); + +struct ReactionsFilter { + std::optional> allowed; + bool customAllowed = false; + + friend inline auto operator<=>( + const ReactionsFilter &, + const ReactionsFilter &) = default; +}; + +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_message_reactions.cpp b/Telegram/SourceFiles/data/data_message_reactions.cpp index af549638f..001181edf 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.cpp +++ b/Telegram/SourceFiles/data/data_message_reactions.cpp @@ -42,26 +42,6 @@ constexpr auto kSizeForDownscale = 64; } // namespace -ReactionId ReactionFromMTP(const MTPReaction &reaction) { - return reaction.match([](MTPDreactionEmpty) { - return ReactionId{ QString() }; - }, [](const MTPDreactionEmoji &data) { - return ReactionId{ qs(data.vemoticon()) }; - }, [](const MTPDreactionCustomEmoji &data) { - return ReactionId{ DocumentId(data.vdocument_id().v) }; - }); -} - -MTPReaction ReactionToMTP(ReactionId id) { - if (const auto custom = id.custom()) { - return MTP_reactionCustomEmoji(MTP_long(custom)); - } - const auto emoji = id.emoji(); - return emoji.isEmpty() - ? MTP_reactionEmpty() - : MTP_reactionEmoji(MTP_string(emoji)); -} - Reactions::Reactions(not_null owner) : _owner(owner) , _repaintTimer([=] { repaintCollected(); }) { diff --git a/Telegram/SourceFiles/data/data_message_reactions.h b/Telegram/SourceFiles/data/data_message_reactions.h index 369b6a4ec..c018126de 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.h +++ b/Telegram/SourceFiles/data/data_message_reactions.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "base/timer.h" +#include "data/data_message_reaction_id.h" namespace Lottie { class Icon; @@ -18,34 +19,6 @@ namespace Data { class DocumentMedia; class Session; -struct ReactionId { - std::variant data; - - [[nodiscard]] bool empty() const { - const auto emoji = std::get_if(&data); - return emoji && emoji->isEmpty(); - } - [[nodiscard]] QString emoji() const { - const auto emoji = std::get_if(&data); - return emoji ? *emoji : QString(); - } - [[nodiscard]] DocumentId custom() const { - const auto custom = std::get_if(&data); - return custom ? *custom : DocumentId(); - } -}; -Q_DECLARE_METATYPE(ReactionId); - -inline bool operator<(const ReactionId &a, const ReactionId &b) { - return a.data < b.data; -} -inline bool operator==(const ReactionId &a, const ReactionId &b) { - return a.data == b.data; -} - -[[nodiscard]] ReactionId ReactionFromMTP(const MTPReaction &reaction); -[[nodiscard]] MTPReaction ReactionToMTP(ReactionId id); - struct Reaction { ReactionId id; QString title; diff --git a/Telegram/SourceFiles/data/data_peer_values.cpp b/Telegram/SourceFiles/data/data_peer_values.cpp index b9e15930c..407ddc8bc 100644 --- a/Telegram/SourceFiles/data/data_peer_values.cpp +++ b/Telegram/SourceFiles/data/data_peer_values.cpp @@ -513,25 +513,23 @@ rpl::producer PeerUserpicImageValue( }; } -std::optional> PeerAllowedReactions( - not_null peer) { +ReactionsFilter PeerReactionsFilter(not_null peer) { if (const auto chat = peer->asChat()) { - return chat->allowedReactions(); + return { .allowed = chat->allowedReactions() }; } else if (const auto channel = peer->asChannel()) { - return channel->allowedReactions(); + return { .allowed = channel->allowedReactions() }; } else { - return std::nullopt; + return { .customAllowed = true }; } } - auto PeerAllowedReactionsValue( - not_null peer) --> rpl::producer>> { + rpl::producer PeerReactionsFilterValue( + not_null peer) { return peer->session().changes().peerFlagsValue( peer, Data::PeerUpdate::Flag::Reactions ) | rpl::map([=]{ - return PeerAllowedReactions(peer); + return PeerReactionsFilter(peer); }); } diff --git a/Telegram/SourceFiles/data/data_peer_values.h b/Telegram/SourceFiles/data/data_peer_values.h index 6245b382e..1ec4afbc9 100644 --- a/Telegram/SourceFiles/data/data_peer_values.h +++ b/Telegram/SourceFiles/data/data_peer_values.h @@ -21,6 +21,7 @@ class Session; namespace Data { struct Reaction; +struct ReactionsFilter; template inline auto FlagsValueWithMask( @@ -133,10 +134,9 @@ inline auto PeerFullFlagValue( int size, ImageRoundRadius radius); -[[nodiscard]] std::optional> PeerAllowedReactions( +[[nodiscard]] ReactionsFilter PeerReactionsFilter(not_null peer); +[[nodiscard]] rpl::producer PeerReactionsFilterValue( not_null peer); -[[nodiscard]] auto PeerAllowedReactionsValue(not_null peer) --> rpl::producer>>; [[nodiscard]] rpl::producer UniqueReactionsLimitValue( not_null session); diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 57af04041..fdd0641de 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_context_menu.h" #include "history/view/history_view_quick_action.h" #include "history/view/history_view_react_button.h" +#include "history/view/history_view_react_selector.h" #include "history/view/history_view_emoji_interactions.h" #include "history/history_item_components.h" #include "history/history_item_text.h" @@ -345,6 +346,7 @@ HistoryInner::HistoryInner( Data::UniqueReactionsLimitValue(&controller->session()), [=](QRect updated) { update(updated); }, controller->cachedReactionIconFactory().createMethod())) +, _reactionsSelector(std::make_unique()) , _touchSelectTimer([=] { onTouchSelect(); }) , _touchScrollTimer([=] { onTouchScrollTimer(); }) , _scrollDateCheck([this] { scrollDateCheck(); }) @@ -393,28 +395,25 @@ HistoryInner::HistoryInner( _controller->emojiInteractions().playStarted(_peer, std::move(emoji)); }, lifetime()); - using ChosenReaction = HistoryView::Reactions::Manager::Chosen; - _reactionsManager->chosen( + rpl::merge( + _reactionsManager->chosen(), + _reactionsSelector->chosen() ) | rpl::start_with_next([=](ChosenReaction reaction) { - const auto item = session().data().message(reaction.context); - if (!item - || Window::ShowReactPremiumError( + _reactionsManager->updateButton({}); + reactionChosen(reaction); + }, lifetime()); + + _reactionsManager->setExternalSelectorShown(_reactionsSelector->shown()); + _reactionsManager->expandSelectorRequests( + ) | rpl::start_with_next([=](ReactionExpandRequest request) { + if (request.expanded) { + _reactionsSelector->show( _controller, - item, - reaction.id)) { - return; - } - item->toggleReaction(reaction.id); - if (item->chosenReaction() != reaction.id) { - return; - } else if (const auto view = item->mainView()) { - if (const auto top = itemTop(view); top >= 0) { - view->animateReaction({ - .id = reaction.id, - .flyIcon = reaction.icon, - .flyFrom = reaction.geometry.translated(0, -top), - }); - } + this, + request.context, + request.button); + } else { + _reactionsSelector->hide(); } }, lifetime()); @@ -463,7 +462,7 @@ HistoryInner::HistoryInner( HistoryView::Reactions::SetupManagerList( _reactionsManager.get(), &session(), - Data::PeerAllowedReactionsValue(_peer)); + Data::PeerReactionsFilterValue(_peer)); controller->adaptive().chatWideValue( ) | rpl::start_with_next([=](bool wide) { @@ -478,6 +477,31 @@ HistoryInner::HistoryInner( setupSharingDisallowed(); } +void HistoryInner::reactionChosen(const ChosenReaction &reaction) { + const auto guard = gsl::finally([&] { _reactionsSelector->hide(); }); + + const auto item = session().data().message(reaction.context); + if (!item + || Window::ShowReactPremiumError( + _controller, + item, + reaction.id)) { + return; + } + item->toggleReaction(reaction.id); + if (item->chosenReaction() != reaction.id) { + return; + } else if (const auto view = item->mainView()) { + if (const auto top = itemTop(view); top >= 0) { + view->animateReaction({ + .id = reaction.id, + .flyIcon = reaction.icon, + .flyFrom = reaction.geometry.translated(0, -top), + }); + } + } +} + Main::Session &HistoryInner::session() const { return _controller->session(); } @@ -1925,10 +1949,11 @@ void HistoryInner::mouseDoubleClickEvent(QMouseEvent *e) { void HistoryInner::toggleFavoriteReaction(not_null view) const { const auto favorite = session().data().reactions().favorite(); - const auto allowed = _reactionsManager->allowedSublist(); - if (allowed - && (favorite.emoji().isEmpty() - || !allowed->contains(favorite.emoji()))) { + const auto &filter = _reactionsManager->filter(); + if (favorite.emoji().isEmpty() && !filter.customAllowed) { + return; + } else if (filter.allowed + && !filter.allowed->contains(favorite.emoji())) { return; } const auto item = view->data(); diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index b813913b6..3d90999df 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -36,6 +36,9 @@ class Element; namespace HistoryView::Reactions { class Manager; +class Selector; +struct ChosenReaction; +struct ExpandRequest; struct ButtonParameters; } // namespace HistoryView::Reactions @@ -225,6 +228,8 @@ private: void onTouchScrollTimer(); class BotAbout; + using ChosenReaction = HistoryView::Reactions::ChosenReaction; + using ReactionExpandRequest = HistoryView::Reactions::ExpandRequest; using VideoUserpic = Dialogs::Ui::VideoUserpic; using SelectedItems = std::map>; enum class MouseAction { @@ -396,6 +401,7 @@ private: const HistoryView::TextState &reactionState) const -> HistoryView::Reactions::ButtonParameters; void toggleFavoriteReaction(not_null view) const; + void reactionChosen(const ChosenReaction &reaction); void setupSharingDisallowed(); [[nodiscard]] bool hasCopyRestriction(HistoryItem *item = nullptr) const; @@ -458,6 +464,7 @@ private: std::unique_ptr> _videoUserpics; std::unique_ptr _reactionsManager; + std::unique_ptr _reactionsSelector; MouseAction _mouseAction = MouseAction::None; TextSelectType _mouseSelectType = TextSelectType::Letters; diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index a62ef5428..c447b0e37 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -353,9 +353,10 @@ ListWidget::ListWidget( } }, lifetime()); - using ChosenReaction = Reactions::Manager::Chosen; _reactionsManager->chosen( ) | rpl::start_with_next([=](ChosenReaction reaction) { + _reactionsManager->updateButton({}); + const auto item = session().data().message(reaction.context); if (!item || Window::ShowReactPremiumError( @@ -2115,10 +2116,11 @@ void ListWidget::mouseDoubleClickEvent(QMouseEvent *e) { void ListWidget::toggleFavoriteReaction(not_null view) const { const auto favorite = session().data().reactions().favorite(); - const auto allowed = _reactionsManager->allowedSublist(); - if (allowed - && (favorite.emoji().isEmpty() - || !allowed->contains(favorite.emoji()))) { + const auto &filter = _reactionsManager->filter(); + if (favorite.emoji().isEmpty() && !filter.customAllowed) { + return; + } else if (filter.allowed + && !filter.allowed->contains(favorite.emoji())) { return; } const auto item = view->data(); diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h index f83d3c004..f9b433c21 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h @@ -36,10 +36,12 @@ namespace Data { struct Group; class CloudImageView; struct Reaction; +struct ReactionsFilter; } // namespace Data namespace HistoryView::Reactions { class Manager; +struct ChosenReaction; struct ButtonParameters; } // namespace HistoryView::Reactions @@ -118,7 +120,7 @@ public: } virtual CopyRestrictionType listSelectRestrictionType() = 0; virtual auto listAllowedReactionsValue() - -> rpl::producer>> = 0; + -> rpl::producer = 0; virtual void listShowPremiumToast(not_null document) = 0; }; @@ -379,6 +381,7 @@ private: using ScrollTopState = ListMemento::ScrollTopState; using PointState = HistoryView::PointState; using CursorState = HistoryView::CursorState; + using ChosenReaction = HistoryView::Reactions::ChosenReaction; void refreshViewer(); void updateAroundPositionFromNearest(int nearestIndex); diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp index 5ca94f632..ecee0c3f2 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp @@ -684,8 +684,8 @@ CopyRestrictionType PinnedWidget::listSelectRestrictionType() { } auto PinnedWidget::listAllowedReactionsValue() --> rpl::producer>> { - return Data::PeerAllowedReactionsValue(_history->peer); +-> rpl::producer { + return Data::PeerReactionsFilterValue(_history->peer); } void PinnedWidget::listShowPremiumToast(not_null document) { diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_section.h b/Telegram/SourceFiles/history/view/history_view_pinned_section.h index 518ceadc2..9cdf89653 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_section.h +++ b/Telegram/SourceFiles/history/view/history_view_pinned_section.h @@ -106,7 +106,7 @@ public: CopyRestrictionType listCopyRestrictionType(HistoryItem *item) override; CopyRestrictionType listSelectRestrictionType() override; auto listAllowedReactionsValue() - -> rpl::producer>> override; + -> rpl::producer override; void listShowPremiumToast(not_null document) override; protected: diff --git a/Telegram/SourceFiles/history/view/history_view_react_button.cpp b/Telegram/SourceFiles/history/view/history_view_react_button.cpp index 45104206a..734936d5a 100644 --- a/Telegram/SourceFiles/history/view/history_view_react_button.cpp +++ b/Telegram/SourceFiles/history/view/history_view_react_button.cpp @@ -95,18 +95,24 @@ constexpr auto kMaxReactionsScrollAtOnce = 2; Button::Button( Fn update, ButtonParameters parameters, - Fn hideMe) + Fn toggleExpanded, + Fn hide) : _update(std::move(update)) +, _toggleExpanded(std::move(toggleExpanded)) , _finalScale(ScaleForState(_state)) , _collapsed(QPoint(), CountOuterSize()) , _finalHeight(_collapsed.height()) -, _expandTimer([=] { applyState(State::Inside, _update); }) -, _hideTimer(hideMe) { +, _expandTimer([=] { _toggleExpanded(true); }) +, _hideTimer(hide) { applyParameters(parameters, nullptr); } Button::~Button() = default; +void Button::expandWithoutCustom() { + applyState(State::Inside, _update); +} + bool Button::isHidden() const { return (_state == State::Hidden) && !_opacityAnimation.animating(); } @@ -316,6 +322,7 @@ void Button::applyState(State state, Fn update) { _finalScale = finalScale; } _state = state; + _toggleExpanded(false); } float64 Button::ScaleForState(State state) { @@ -410,15 +417,14 @@ Manager::Manager( _createChooseCallback = [=](ReactionId id) { return [=] { if (auto chosen = lookupChosen(id)) { - updateButton({}); _chosen.fire(std::move(chosen)); } }; }; } -Manager::Chosen Manager::lookupChosen(const ReactionId &id) const { - auto result = Chosen{ +ChosenReaction Manager::lookupChosen(const ReactionId &id) const { + auto result = ChosenReaction{ .context = _buttonContext, .id = id, }; @@ -461,21 +467,26 @@ Manager::Chosen Manager::lookupChosen(const ReactionId &id) const { return result; } -void Manager::applyListFilters() { +bool Manager::applyUniqueLimit() const { const auto limit = _uniqueLimit.current(); - const auto applyUniqueLimit = _buttonContext + return _buttonContext && (limit > 0) && (_buttonAlreadyNotMineCount >= limit); +} + +void Manager::applyListFilters() { + const auto limited = applyUniqueLimit(); auto icons = std::vector>(); icons.reserve(_list.size()); auto showPremiumLock = (ReactionIcons*)nullptr; auto favoriteIndex = -1; for (auto &icon : _list) { const auto &id = icon.id; - const auto add = applyUniqueLimit + const auto add = limited ? _buttonAlreadyList.contains(id) - : (!_filter - || (!id.emoji().isEmpty() && _filter->contains(id.emoji()))); + : id.emoji().isEmpty() + ? _filter.customAllowed + : (!_filter.allowed || _filter.allowed->contains(id.emoji())); if (add) { if (icon.premium && !_allowSendingPremium @@ -504,6 +515,9 @@ void Manager::applyListFilters() { const auto first = begin(icons); std::rotate(first, first + favoriteIndex, first + favoriteIndex + 1); } + if (!limited && _filter.customAllowed && icons.size() > 1) { + icons.erase(begin(icons) + 1, end(icons)); + } if (_icons == icons) { return; } @@ -528,8 +542,13 @@ void Manager::stealWheelEvents(not_null target) { Manager::~Manager() = default; void Manager::updateButton(ButtonParameters parameters) { - if (parameters.cursorLeft && _menu) { - return; + if (parameters.cursorLeft) { + if (_menu) { + return; + } else if (_externalSelectorShown) { + setSelectedIcon(-1); + return; + } } const auto contextChanged = (_buttonContext != parameters.context); if (contextChanged) { @@ -537,6 +556,7 @@ void Manager::updateButton(ButtonParameters parameters) { if (_button) { _button->applyState(ButtonState::Hidden); _buttonHiding.push_back(std::move(_button)); + _expandSelectorRequests.fire({ .expanded = false }); } _buttonShowTimer.cancel(); _scheduledParameters = std::nullopt; @@ -567,11 +587,32 @@ void Manager::updateButton(ButtonParameters parameters) { } } +void Manager::toggleExpanded(bool expanded) { + if (!_button || !_buttonContext) { + } else if (!expanded || (_filter.customAllowed && !applyUniqueLimit())) { + _expandSelectorRequests.fire({ + .context = _buttonContext, + .button = _button->geometry().marginsRemoved( + st::reactionCornerShadow), + .expanded = expanded, + }); + } else { + _button->expandWithoutCustom(); + } +} + +void Manager::setExternalSelectorShown(rpl::producer shown) { + std::move(shown) | rpl::start_with_next([=](bool shown) { + _externalSelectorShown = shown; + }, _lifetime); +} + void Manager::showButtonDelayed() { clearAppearAnimations(); _button = std::make_unique