diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp index 6324e99a1..ad8a1f0be 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp @@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_changes.h" #include "data/data_message_reactions.h" +#include "data/data_peer_values.h" #include "history/admin_log/history_admin_log_section.h" #include "info/profile/info_profile_values.h" #include "lang/lang_keys.h" @@ -1060,7 +1061,7 @@ void Controller::fillManageSection() { !_peer->isBroadcast(), session->data().reactions().list( Data::Reactions::Type::Active), - session->data().reactions().list(_peer), + *Data::PeerAllowedReactions(_peer), done)); }, st::infoIconReactions); diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp index 3ca933fd7..65ebbc14e 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp @@ -93,7 +93,7 @@ void EditAllowedReactionsBox( not_null box, bool isGroup, const std::vector &list, - const std::vector &selected, + const base::flat_set &selected, Fn &)> callback) { box->setTitle(tr::lng_manage_peer_reactions()); @@ -146,7 +146,7 @@ void EditAllowedReactionsBox( tr::lng_manage_peer_reactions_available()); const auto active = [&](const Data::Reaction &entry) { - return ranges::contains(selected, entry.emoji, &Reaction::emoji); + return selected.contains(entry.emoji); }; const auto add = [&](const Data::Reaction &entry) { const auto button = Settings::AddButton( @@ -198,9 +198,9 @@ void SaveAllowedReactions( )).done([=](const MTPUpdates &result) { peer->session().api().applyUpdates(result); if (const auto chat = peer->asChat()) { - chat->setAllowedReactions(allowed); + chat->setAllowedReactions({ begin(allowed), end(allowed) }); } else if (const auto channel = peer->asChannel()) { - channel->setAllowedReactions(allowed); + channel->setAllowedReactions({ begin(allowed), end(allowed) }); } else { Unexpected("Invalid peer type in SaveAllowedReactions."); } diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.h b/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.h index 917957b7f..3feed5cf0 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.h +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.h @@ -19,7 +19,7 @@ void EditAllowedReactionsBox( not_null box, bool isGroup, const std::vector &list, - const std::vector &selected, + const base::flat_set &selected, Fn &)> callback); void SaveAllowedReactions( diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index 66f754cb8..a35c1e879 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -761,7 +761,7 @@ PeerId ChannelData::groupCallDefaultJoinAs() const { return _callDefaultJoinAs; } -void ChannelData::setAllowedReactions(std::vector list) { +void ChannelData::setAllowedReactions(base::flat_set list) { if (_allowedReactions != list) { const auto toggled = (_allowedReactions.empty() != list.empty()); _allowedReactions = std::move(list); @@ -774,7 +774,7 @@ void ChannelData::setAllowedReactions(std::vector list) { } } -const std::vector &ChannelData::allowedReactions() const { +const base::flat_set &ChannelData::allowedReactions() const { return _allowedReactions; } diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h index 9590f6b0e..12d7a1389 100644 --- a/Telegram/SourceFiles/data/data_channel.h +++ b/Telegram/SourceFiles/data/data_channel.h @@ -410,8 +410,8 @@ public: void setGroupCallDefaultJoinAs(PeerId peerId); [[nodiscard]] PeerId groupCallDefaultJoinAs() const; - void setAllowedReactions(std::vector list); - [[nodiscard]] const std::vector &allowedReactions() const; + void setAllowedReactions(base::flat_set list); + [[nodiscard]] const base::flat_set &allowedReactions() const; // Still public data members. uint64 access = 0; @@ -460,7 +460,7 @@ private: QString _inviteLink; std::optional _linkedChat; - std::vector _allowedReactions; + base::flat_set _allowedReactions; std::unique_ptr _call; PeerId _callDefaultJoinAs = 0; diff --git a/Telegram/SourceFiles/data/data_chat.cpp b/Telegram/SourceFiles/data/data_chat.cpp index 16acd3694..29324b782 100644 --- a/Telegram/SourceFiles/data/data_chat.cpp +++ b/Telegram/SourceFiles/data/data_chat.cpp @@ -287,7 +287,7 @@ void ChatData::setPendingRequestsCount( } } -void ChatData::setAllowedReactions(std::vector list) { +void ChatData::setAllowedReactions(base::flat_set list) { if (_allowedReactions != list) { const auto toggled = (_allowedReactions.empty() != list.empty()); _allowedReactions = std::move(list); @@ -300,7 +300,7 @@ void ChatData::setAllowedReactions(std::vector list) { } } -const std::vector &ChatData::allowedReactions() const { +const base::flat_set &ChatData::allowedReactions() const { return _allowedReactions; } diff --git a/Telegram/SourceFiles/data/data_chat.h b/Telegram/SourceFiles/data/data_chat.h index 8893a5889..eda79f41c 100644 --- a/Telegram/SourceFiles/data/data_chat.h +++ b/Telegram/SourceFiles/data/data_chat.h @@ -175,8 +175,8 @@ public: int count, std::vector recentRequesters); - void setAllowedReactions(std::vector list); - [[nodiscard]] const std::vector &allowedReactions() const; + void setAllowedReactions(base::flat_set list); + [[nodiscard]] const base::flat_set &allowedReactions() const; // Still public data members. const MTPlong inputChat; @@ -202,7 +202,7 @@ private: int _pendingRequestsCount = 0; std::vector _recentRequesters; - std::vector _allowedReactions; + base::flat_set _allowedReactions; std::unique_ptr _call; PeerId _callDefaultJoinAs = 0; diff --git a/Telegram/SourceFiles/data/data_message_reactions.cpp b/Telegram/SourceFiles/data/data_message_reactions.cpp index d1c96a295..0f4fcb532 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.cpp +++ b/Telegram/SourceFiles/data/data_message_reactions.cpp @@ -11,11 +11,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_item.h" #include "main/main_session.h" #include "data/data_session.h" -#include "data/data_channel.h" -#include "data/data_chat.h" +#include "data/data_changes.h" #include "data/data_document.h" #include "data/data_document_media.h" -#include "data/data_changes.h" #include "lottie/lottie_icon.h" #include "base/timer_rpl.h" #include "apiwrap.h" @@ -65,16 +63,6 @@ const std::vector &Reactions::list(Type type) const { Unexpected("Type in Reactions::list."); } -std::vector Reactions::list(not_null peer) const { - if (const auto chat = peer->asChat()) { - return filtered(chat->allowedReactions()); - } else if (const auto channel = peer->asChannel()) { - return filtered(channel->allowedReactions()); - } else { - return list(Type::Active); - } -} - rpl::producer<> Reactions::updates() const { return _updated.events(); } @@ -214,33 +202,17 @@ void Reactions::downloadTaskFinished() { } } -std::vector Reactions::Filtered( - const std::vector &reactions, - const std::vector &emoji) { - auto result = std::vector(); - result.reserve(emoji.size()); - for (const auto &single : emoji) { - const auto i = ranges::find(reactions, single, &Reaction::emoji); - if (i != end(reactions)) { - result.push_back(*i); - } - } - return result; -} - -std::vector Reactions::filtered( - const std::vector &emoji) const { - return Filtered(list(Type::Active), emoji); -} - -std::vector Reactions::ParseAllowed( +base::flat_set Reactions::ParseAllowed( const MTPVector *list) { if (!list) { return {}; } - return list->v | ranges::view::transform([](const MTPstring &string) { + const auto parsed = ranges::views::all( + list->v + ) | ranges::views::transform([](const MTPstring &string) { return qs(string); }) | ranges::to_vector; + return { begin(parsed), end(parsed) }; } void Reactions::request() { diff --git a/Telegram/SourceFiles/data/data_message_reactions.h b/Telegram/SourceFiles/data/data_message_reactions.h index 7827f4b6e..36f4ed13a 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.h +++ b/Telegram/SourceFiles/data/data_message_reactions.h @@ -43,15 +43,8 @@ public: All, }; [[nodiscard]] const std::vector &list(Type type) const; - [[nodiscard]] std::vector list(not_null peer) const; - [[nodiscard]] static std::vector Filtered( - const std::vector &reactions, - const std::vector &emoji); - [[nodiscard]] std::vector filtered( - const std::vector &emoji) const; - - [[nodiscard]] static std::vector ParseAllowed( + [[nodiscard]] static base::flat_set ParseAllowed( const MTPVector *list); [[nodiscard]] rpl::producer<> updates() const; diff --git a/Telegram/SourceFiles/data/data_peer_values.cpp b/Telegram/SourceFiles/data/data_peer_values.cpp index d25d42ba6..24bd00378 100644 --- a/Telegram/SourceFiles/data/data_peer_values.cpp +++ b/Telegram/SourceFiles/data/data_peer_values.cpp @@ -15,6 +15,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_message_reactions.h" #include "main/main_session.h" +#include "main/main_account.h" +#include "main/main_app_config.h" #include "ui/image/image_prepare.h" #include "base/unixtime.h" @@ -497,18 +499,36 @@ rpl::producer PeerUserpicImageValue( }; } -rpl::producer> PeerAllowedReactionsValue( +std::optional> PeerAllowedReactions( not_null peer) { - return rpl::combine( - rpl::single( - rpl::empty_value() - ) | rpl::then(peer->owner().reactions().updates()), - peer->session().changes().peerFlagsValue( - peer, - Data::PeerUpdate::Flag::Reactions) - ) | rpl::map([=] { - return peer->owner().reactions().list(peer); + if (const auto chat = peer->asChat()) { + return chat->allowedReactions(); + } else if (const auto channel = peer->asChannel()) { + return channel->allowedReactions(); + } else { + return std::nullopt; + } +} + + auto PeerAllowedReactionsValue( + not_null peer) +-> rpl::producer>> { + return peer->session().changes().peerFlagsValue( + peer, + Data::PeerUpdate::Flag::Reactions + ) | rpl::map([=]{ + return PeerAllowedReactions(peer); }); } +rpl::producer UniqueReactionsLimitValue( + not_null session) { + const auto config = &session->account().appConfig(); + return config->value( + ) | rpl::map([=] { + return int(base::SafeRound( + config->get("reactions_uniq_max", 11))); + }) | rpl::distinct_until_changed(); +} + } // namespace Data diff --git a/Telegram/SourceFiles/data/data_peer_values.h b/Telegram/SourceFiles/data/data_peer_values.h index fa6a8042b..5e35e0aa8 100644 --- a/Telegram/SourceFiles/data/data_peer_values.h +++ b/Telegram/SourceFiles/data/data_peer_values.h @@ -14,6 +14,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL enum class ImageRoundRadius; +namespace Main { +class Session; +} // namespace Main + namespace Data { struct Reaction; @@ -122,7 +126,12 @@ inline auto PeerFullFlagValue( int size, ImageRoundRadius radius); +[[nodiscard]] std::optional> PeerAllowedReactions( + not_null peer); [[nodiscard]] auto PeerAllowedReactionsValue(not_null peer) --> rpl::producer>; +-> rpl::producer>>; + +[[nodiscard]] rpl::producer UniqueReactionsLimitValue( + not_null session); } // namespace Data diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index dbb613d65..951690655 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -322,6 +322,7 @@ HistoryInner::HistoryInner( , _reactionsManager( std::make_unique( this, + Data::UniqueReactionsLimitValue(&controller->session()), [=](QRect updated) { update(updated); })) , _touchSelectTimer([=] { onTouchSelect(); }) , _touchScrollTimer([=] { onTouchScrollTimer(); }) @@ -412,6 +413,7 @@ HistoryInner::HistoryInner( return item->mainView() != nullptr; }) | rpl::start_with_next([=](not_null item) { item->mainView()->itemDataChanged(); + _reactionsManager->updateUniqueLimit(item); }, lifetime()); session().changes().historyUpdates( @@ -421,11 +423,10 @@ HistoryInner::HistoryInner( update(); }, lifetime()); - Data::PeerAllowedReactionsValue( - _peer - ) | rpl::start_with_next([=](std::vector &&list) { - _reactionsManager->applyList(std::move(list)); - }, lifetime()); + HistoryView::Reactions::SetupManagerList( + _reactionsManager.get(), + &session(), + Data::PeerAllowedReactionsValue(_peer)); controller->adaptive().chatWideValue( ) | rpl::start_with_next([=](bool wide) { @@ -3169,7 +3170,8 @@ void HistoryInner::mouseActionUpdate() { : nullptr; const auto item = view ? view->data().get() : nullptr; if (view) { - if (App::mousedItem() != view) { + const auto changed = (App::mousedItem() != view); + if (changed) { repaintItem(App::mousedItem()); App::mousedItem(view); repaintItem(App::mousedItem()); @@ -3179,6 +3181,9 @@ void HistoryInner::mouseActionUpdate() { view, m, reactionState)); + if (changed) { + _reactionsManager->updateUniqueLimit(item); + } if (view->pointState(m) != PointState::Outside) { if (App::hoveredItem() != view) { repaintItem(App::hoveredItem()); diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 3cfb449ee..728eda911 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -49,6 +49,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_channel.h" #include "data/data_file_click_handler.h" #include "data/data_message_reactions.h" +#include "data/data_peer_values.h" #include "facades.h" #include "styles/style_chat.h" @@ -267,6 +268,7 @@ ListWidget::ListWidget( , _reactionsManager( std::make_unique( this, + Data::UniqueReactionsLimitValue(&controller->session()), [=](QRect updated) { update(updated); })) , _scrollDateCheck([this] { scrollDateCheck(); }) , _applyUpdatedScrollState([this] { applyUpdatedScrollState(); }) @@ -344,10 +346,10 @@ ListWidget::ListWidget( } }, lifetime()); - _delegate->listAllowedReactionsValue( - ) | rpl::start_with_next([=](std::vector &&list) { - _reactionsManager->applyList(std::move(list)); - }, lifetime()); + Reactions::SetupManagerList( + _reactionsManager.get(), + &session(), + _delegate->listAllowedReactionsValue()); controller->adaptive().chatWideValue( ) | rpl::start_with_next([=](bool wide) { @@ -2563,7 +2565,8 @@ void ListWidget::mouseActionUpdate() { view ? view->height() : 0, itemPoint, view ? view->pointState(itemPoint) : PointState::Outside); - if (_overElement != view) { + const auto viewChanged = (_overElement != view); + if (viewChanged) { repaintItem(_overElement); _overElement = view; repaintItem(_overElement); @@ -2574,6 +2577,9 @@ void ListWidget::mouseActionUpdate() { itemPoint, reactionState) : Reactions::ButtonParameters()); + if (viewChanged && view) { + _reactionsManager->updateUniqueLimit(item); + } TextState dragState; ClickHandlerHost *lnkhost = nullptr; diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h index 062d3828d..0f5f1a9c7 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h @@ -113,7 +113,7 @@ public: } virtual CopyRestrictionType listSelectRestrictionType() = 0; virtual auto listAllowedReactionsValue() - -> rpl::producer> = 0; + -> rpl::producer>> = 0; }; struct SelectionData { diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp index 02a33c746..f3cf35052 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp @@ -684,7 +684,7 @@ CopyRestrictionType PinnedWidget::listSelectRestrictionType() { } auto PinnedWidget::listAllowedReactionsValue() --> rpl::producer> { +-> rpl::producer>> { return Data::PeerAllowedReactionsValue(_history->peer); } diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_section.h b/Telegram/SourceFiles/history/view/history_view_pinned_section.h index 49725bf54..0383b190a 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; protected: void resizeEvent(QResizeEvent *e) override; diff --git a/Telegram/SourceFiles/history/view/history_view_react_button.cpp b/Telegram/SourceFiles/history/view/history_view_react_button.cpp index 1f43732bb..4a7f84239 100644 --- a/Telegram/SourceFiles/history/view/history_view_react_button.cpp +++ b/Telegram/SourceFiles/history/view/history_view_react_button.cpp @@ -8,9 +8,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_react_button.h" #include "history/view/history_view_cursor_state.h" +#include "history/history_item.h" #include "ui/chat/chat_style.h" #include "ui/chat/message_bubble.h" #include "data/data_message_reactions.h" +#include "data/data_session.h" #include "data/data_document.h" #include "data/data_document_media.h" #include "lottie/lottie_icon.h" @@ -70,7 +72,6 @@ constexpr auto kHoverScale = 1.24; [[nodiscard]] std::shared_ptr CreateIcon( not_null media, - int startFrame, int size) { Expects(media->loaded()); @@ -78,7 +79,6 @@ constexpr auto kHoverScale = 1.24; .path = media->owner()->filepath(true), .json = media->bytes(), .sizeOverride = QSize(size, size), - .frame = startFrame, }); } @@ -331,6 +331,7 @@ float64 Button::currentOpacity() const { Manager::Manager( QWidget *wheelEventsTarget, + rpl::producer uniqueLimitValue, Fn buttonUpdate) : _outer(CountOuterSize()) , _inner(QRect({}, st::reactionCornerSize)) @@ -338,6 +339,7 @@ Manager::Manager( QRect(0, 0, _inner.width(), _inner.width()).marginsAdded( st::reactionCornerShadow ).size()) +, _uniqueLimit(std::move(uniqueLimitValue)) , _buttonShowTimer([=] { showButtonDelayed(); }) , _buttonUpdate(std::move(buttonUpdate)) { static_assert(!(kFramesCount % kDivider)); @@ -384,6 +386,11 @@ Manager::Manager( stealWheelEvents(wheelEventsTarget); } + _uniqueLimit.changes( + ) | rpl::start_with_next([=] { + applyListFilters(); + }, _lifetime); + _createChooseCallback = [=](QString emoji) { return [=] { if (const auto context = _buttonContext) { @@ -397,6 +404,33 @@ Manager::Manager( }; } +void Manager::applyListFilters() { + const auto limit = _uniqueLimit.current(); + const auto applyUniqueLimit = _buttonContext + && (limit > 0) + && (_buttonAlreadyNotMineCount >= limit); + auto icons = std::vector>(); + icons.reserve(_list.size()); + for (auto &icon : _list) { + const auto add = applyUniqueLimit + ? _buttonAlreadyList.contains(icon.emoji) + : (!_filter || _filter->contains(icon.emoji)); + if (add) { + icons.push_back(&icon); + } else { + clearStateForHidden(icon); + } + } + if (_icons == icons) { + return; + } + const auto selected = _selectedIcon; + setSelectedIcon(-1); + _icons = std::move(icons); + setSelectedIcon(selected < _icons.size() ? selected : -1); + resolveMainReactionIcon(); +} + void Manager::stealWheelEvents(not_null target) { base::install_event_filter(target, [=](not_null e) { if (e->type() != QEvent::Wheel @@ -422,8 +456,8 @@ void Manager::updateButton(ButtonParameters parameters) { _scheduledParameters = std::nullopt; } _buttonContext = parameters.context; - parameters.reactionsCount = _list.size(); - if (!_buttonContext || _list.empty()) { + parameters.reactionsCount = _icons.size(); + if (!_buttonContext || _icons.empty()) { return; } else if (_button) { _button->applyParameters(parameters); @@ -455,7 +489,7 @@ void Manager::showButtonDelayed() { [=]{ updateButton({}); }); } -void Manager::applyList(std::vector list) { +void Manager::applyList(const std::vector &list) { constexpr auto predicate = []( const Data::Reaction &a, const Data::Reaction &b) { @@ -463,32 +497,87 @@ void Manager::applyList(std::vector list) { && (a.appearAnimation == b.appearAnimation) && (a.selectAnimation == b.selectAnimation); }; - if (ranges::equal(_list, list, predicate)) { + const auto proj = [](const auto &obj) { + return std::tie(obj.emoji, obj.appearAnimation, obj.selectAnimation); + }; + if (ranges::equal(_list, list, ranges::equal_to(), proj, proj)) { return; } - _list = std::move(list); - _links = std::vector(_list.size()); - if (_list.empty()) { + const auto selected = _selectedIcon; + setSelectedIcon(-1); + _icons.clear(); + _list.clear(); + for (const auto &reaction : list) { + _list.push_back({ + .emoji = reaction.emoji, + .appearAnimation = reaction.appearAnimation, + .selectAnimation = reaction.selectAnimation, + }); + } + applyListFilters(); + setSelectedIcon(selected < _icons.size() ? selected : -1); +} + +void Manager::updateAllowedSublist( + std::optional> filter) { + if (_filter == filter) { + return; + } + _filter = std::move(filter); + applyListFilters(); +} + +void Manager::updateUniqueLimit(not_null item) { + if (item->fullId() != _buttonContext) { + return; + } + const auto &all = item->reactions(); + const auto my = item->chosenReaction(); + auto list = base::flat_set(); + list.reserve(all.size()); + auto myIsUnique = false; + for (const auto &[emoji, count] : all) { + list.emplace(emoji); + if (count == 1 && emoji == my) { + myIsUnique = true; + } + } + const auto notMineCount = int(list.size()) - (myIsUnique ? 1 : 0); + + auto changed = false; + if (_buttonAlreadyList != list) { + _buttonAlreadyList = std::move(list); + changed = true; + } + if (_buttonAlreadyNotMineCount != notMineCount) { + _buttonAlreadyNotMineCount = notMineCount; + changed = true; + } + if (changed) { + applyListFilters(); + } +} + +void Manager::resolveMainReactionIcon() { + if (_icons.empty()) { _mainReactionMedia = nullptr; _mainReactionLifetime.destroy(); - setSelectedIcon(-1); - _icons.clear(); return; } - const auto main = _list.front().appearAnimation; - if (_mainReactionMedia - && _mainReactionMedia->owner() == main) { + const auto main = _icons.front()->selectAnimation; + _icons.front()->appearAnimated = true; + if (_mainReactionMedia && _mainReactionMedia->owner() == main) { if (!_mainReactionLifetime) { loadIcons(); } return; } - _mainReactionLifetime.destroy(); _mainReactionMedia = main->createMediaView(); _mainReactionMedia->checkStickerLarge(); if (_mainReactionMedia->loaded()) { + _mainReactionLifetime.destroy(); setMainReactionIcon(); - } else { + } else if (!_mainReactionLifetime) { main->session().downloaderTaskFinished( ) | rpl::filter([=] { return _mainReactionMedia->loaded(); @@ -506,16 +595,15 @@ void Manager::setMainReactionIcon() { const auto i = _loadCache.find(_mainReactionMedia->owner()); if (i != end(_loadCache) && i->second.icon) { const auto &icon = i->second.icon; - if (icon->frameIndex() == icon->framesCount() - 1 - && icon->width() == MainReactionSize()) { + if (!icon->frameIndex() && icon->width() == MainReactionSize()) { _mainReactionImage = i->second.icon->frame(); return; } } - _mainReactionImage = CreateIcon( + _mainReactionImage = QImage(); + _mainReactionIcon = CreateIcon( _mainReactionMedia.get(), - -1, - MainReactionSize())->frame(); + MainReactionSize()); } QMargins Manager::innerMargins() const { @@ -544,7 +632,7 @@ bool Manager::checkIconLoaded(ReactionDocument &entry) const { const auto size = (entry.media == _mainReactionMedia) ? MainReactionSize() : CornerImageSize(1.); - entry.icon = CreateIcon(entry.media.get(), entry.startFrame, size); + entry.icon = CreateIcon(entry.media.get(), size); entry.media = nullptr; return true; } @@ -556,14 +644,13 @@ void Manager::updateCurrentButton() const { } void Manager::loadIcons() { - const auto load = [&](not_null document, int frame) { + const auto load = [&](not_null document) { if (const auto i = _loadCache.find(document); i != end(_loadCache)) { return i->second.icon; } auto &entry = _loadCache.emplace(document).first->second; entry.media = document->createMediaView(); entry.media->checkStickerLarge(); - entry.startFrame = frame; if (!checkIconLoaded(entry) && !_loadCacheLifetime) { document->session().downloaderTaskFinished( ) | rpl::start_with_next([=] { @@ -572,19 +659,14 @@ void Manager::loadIcons() { } return entry.icon; }; - const auto selected = _selectedIcon; - setSelectedIcon(-1); - _icons.clear(); - auto main = true; - for (const auto &reaction : _list) { - _icons.push_back({ - .appear = load(reaction.appearAnimation, main ? -1 : 0), - .select = load(reaction.selectAnimation, 0), - .appearAnimated = main, - }); - main = false; + for (const auto &icon : _icons) { + if (!icon->appear) { + icon->appear = load(icon->appearAnimation); + } + if (!icon->select) { + icon->select = load(icon->selectAnimation); + } } - setSelectedIcon(selected < _icons.size() ? selected : -1); } void Manager::checkIcons() { @@ -617,7 +699,7 @@ void Manager::paintButtons(Painter &p, const PaintContext &context) { } ClickHandlerPtr Manager::computeButtonLink(QPoint position) const { - if (_list.empty()) { + if (_icons.empty()) { setSelectedIcon(-1); return nullptr; } @@ -631,10 +713,10 @@ ClickHandlerPtr Manager::computeButtonLink(QPoint position) const { const auto index = std::clamp( int(base::SafeRound(shifted + between / 2.)) / oneHeight, 0, - int(_list.size() - 1)); - auto &result = _links[index]; + int(_icons.size() - 1)); + auto &result = _icons[index]->link; if (!result) { - result = resolveButtonLink(_list[index]); + result = resolveButtonLink(*_icons[index]); } setSelectedIcon(index); return result; @@ -645,25 +727,25 @@ void Manager::setSelectedIcon(int index) const { if (index < 0 || index >= _icons.size()) { return; } - auto &icon = _icons[index]; - if (icon.selected == selected) { + const auto &icon = _icons[index]; + if (icon->selected == selected) { return; } - icon.selected = selected; - icon.selectedScale.start( + icon->selected = selected; + icon->selectedScale.start( [=] { updateCurrentButton(); }, selected ? 1. : kHoverScale, selected ? kHoverScale : 1., kHoverScaleDuration, anim::sineInOut); if (selected) { - const auto skipAnimation = icon.selectAnimated - || !icon.appearAnimated - || (icon.select && icon.select->animating()) - || (icon.appear && icon.appear->animating()); - const auto select = skipAnimation ? nullptr : icon.select.get(); - if (select && !icon.selectAnimated) { - icon.selectAnimated = true; + const auto skipAnimation = icon->selectAnimated + || !icon->appearAnimated + || (icon->select && icon->select->animating()) + || (icon->appear && icon->appear->animating()); + const auto select = skipAnimation ? nullptr : icon->select.get(); + if (select && !icon->selectAnimated) { + icon->selectAnimated = true; select->animate( [=] { updateCurrentButton(); }, 0, @@ -679,7 +761,7 @@ void Manager::setSelectedIcon(int index) const { } ClickHandlerPtr Manager::resolveButtonLink( - const Data::Reaction &reaction) const { + const ReactionIcons &reaction) const { const auto emoji = reaction.emoji; const auto i = _reactionsLinks.find(emoji); if (i != end(_reactionsLinks)) { @@ -1043,12 +1125,12 @@ bool Manager::onlyMainEmojiVisible() const { return true; } const auto &icon = _icons.front(); - if (icon.selected - || icon.selectedScale.animating() - || (icon.select && icon.select->animating())) { + if (icon->selected + || icon->selectedScale.animating() + || (icon->select && icon->select->animating())) { return false; } - icon.selectAnimated = false; + icon->selectAnimated = false; return true; } @@ -1060,22 +1142,20 @@ void Manager::clearAppearAnimations() { auto main = true; for (auto &icon : _icons) { if (!main) { - if (icon.selected) { + if (icon->selected) { setSelectedIcon(-1); } - icon.selectedScale.stop(); - if (const auto select = icon.select.get()) { + icon->selectedScale.stop(); + if (const auto select = icon->select.get()) { select->jumpTo(0, nullptr); } - icon.selectAnimated = false; + icon->selectAnimated = false; } - if (icon.appearAnimated != main) { - if (const auto appear = icon.appear.get()) { - appear->jumpTo( - main ? (appear->framesCount() - 1) : 0, - nullptr); + if (icon->appearAnimated != main) { + if (const auto appear = icon->appear.get()) { + appear->jumpTo(0, nullptr); } - icon.appearAnimated = main; + icon->appearAnimated = main; } main = false; } @@ -1160,8 +1240,8 @@ void Manager::paintAllEmoji( const auto update = [=] { updateCurrentButton(); }; - for (auto &icon : _icons) { - const auto target = countTarget(icon).translated(emojiPosition); + for (const auto &icon : _icons) { + const auto target = countTarget(*icon).translated(emojiPosition); emojiPosition += shift; const auto paintFrame = [&](not_null animation) { @@ -1172,21 +1252,25 @@ void Manager::paintAllEmoji( if (!target.intersects(clip)) { if (current) { - clearStateForHidden(icon); + clearStateForHidden(*icon); } - } else if (icon.select && icon.select->animating()) { - paintFrame(icon.select.get()); - } else if (const auto appear = icon.appear.get()) { + } else { + const auto appear = icon->appear.get(); if (current - && !icon.appearAnimated + && appear + && !icon->appearAnimated && target.intersects(animationRect)) { - icon.appearAnimated = true; + icon->appearAnimated = true; appear->animate(update, 0, appear->framesCount() - 1); } - paintFrame(appear); + if (appear && appear->animating()) { + paintFrame(appear); + } else if (const auto select = icon->select.get()) { + paintFrame(select); + } } if (current) { - clearStateForSelectFinished(icon); + clearStateForSelectFinished(*icon); } } } @@ -1288,6 +1372,10 @@ QRect Manager::validateEmoji(int frameIndex, float64 scale) { const auto position = result.topLeft() / ratio; p.setCompositionMode(QPainter::CompositionMode_Source); p.fillRect(QRect(position, result.size() / ratio), Qt::transparent); + if (_mainReactionImage.isNull() + && _mainReactionIcon) { + _mainReactionImage = base::take(_mainReactionIcon)->frame(); + } if (!_mainReactionImage.isNull()) { const auto size = CornerImageSize(scale); const auto inner = _inner.translated(position); @@ -1355,4 +1443,25 @@ QRect Manager::validateFrame( return result; } +void SetupManagerList( + not_null manager, + not_null session, + rpl::producer>> filter) { + rpl::single( + rpl::empty_value() + ) | rpl::then( + session->data().reactions().updates() + ) | rpl::start_with_next([=] { + manager->applyList( + session->data().reactions().list(Data::Reactions::Type::Active)); + }, manager->lifetime()); + + std::move( + filter + ) | rpl::start_with_next([=]( + std::optional> &&list) { + manager->updateAllowedSublist(std::move(list)); + }, manager->lifetime()); +} + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_react_button.h b/Telegram/SourceFiles/history/view/history_view_react_button.h index 350155ba6..25d35ea10 100644 --- a/Telegram/SourceFiles/history/view/history_view_react_button.h +++ b/Telegram/SourceFiles/history/view/history_view_react_button.h @@ -24,6 +24,10 @@ using PaintContext = Ui::ChatPaintContext; struct TextState; } // namespace HistoryView +namespace Main { +class Session; +} // namespace Main + namespace Lottie { class Icon; } // namespace Lottie @@ -127,10 +131,13 @@ class Manager final : public base::has_weak_ptr { public: Manager( QWidget *wheelEventsTarget, + rpl::producer uniqueLimitValue, Fn buttonUpdate); ~Manager(); - void applyList(std::vector list); + void applyList(const std::vector &list); + void updateAllowedSublist(std::optional> filter); + void updateUniqueLimit(not_null item); void updateButton(ButtonParameters parameters); void paintButtons(Painter &p, const PaintContext &context); @@ -147,15 +154,22 @@ public: return _chosen.events(); } + [[nodiscard]] rpl::lifetime &lifetime() { + return _lifetime; + } + private: struct ReactionDocument { std::shared_ptr media; std::shared_ptr icon; - int startFrame = 0; }; struct ReactionIcons { + QString emoji; + not_null appearAnimation; + not_null selectAnimation; std::shared_ptr appear; std::shared_ptr select; + mutable ClickHandlerPtr link; mutable Ui::Animations::Simple selectedScale; bool appearAnimated = false; mutable bool selected = false; @@ -167,6 +181,7 @@ private: }; static constexpr auto kFramesCount = 32; + void applyListFilters(); void showButtonDelayed(); void stealWheelEvents(not_null target); @@ -208,6 +223,7 @@ private: const QImage &image, QRect source); + void resolveMainReactionIcon(); void setMainReactionIcon(); void clearAppearAnimations(); [[nodiscard]] QRect cacheRect(int frameIndex, int columnIndex) const; @@ -249,7 +265,7 @@ private: [[nodiscard]] ClickHandlerPtr computeButtonLink(QPoint position) const; [[nodiscard]] ClickHandlerPtr resolveButtonLink( - const Data::Reaction &reaction) const; + const ReactionIcons &reaction) const; void updateCurrentButton() const; [[nodiscard]] bool onlyMainEmojiVisible() const; @@ -258,8 +274,8 @@ private: void checkIcons(); rpl::event_stream _chosen; - std::vector _list; - mutable std::vector _links; + std::vector _list; + std::optional> _filter; QSize _outer; QRect _inner; QSize _overlayFull; @@ -282,11 +298,13 @@ private: QColor _shadow; std::shared_ptr _mainReactionMedia; + std::shared_ptr _mainReactionIcon; QImage _mainReactionImage; rpl::lifetime _mainReactionLifetime; + rpl::variable _uniqueLimit = 0; base::flat_map, ReactionDocument> _loadCache; - std::vector _icons; + std::vector> _icons; rpl::lifetime _loadCacheLifetime; bool _showingAll = false; mutable int _selectedIcon = -1; @@ -297,9 +315,18 @@ private: std::unique_ptr