/* 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 #include "base/timer.h" #include "data/data_message_reaction_id.h" #include "data/stickers/data_custom_emoji.h" namespace Ui { class AnimatedIcon; } // namespace Ui namespace Ui::Text { class CustomEmoji; } // namespace Ui::Text namespace Data { class SavedSublist; class DocumentMedia; class Session; struct Reaction { ReactionId id; QString title; //not_null staticIcon; not_null appearAnimation; not_null selectAnimation; //not_null activateAnimation; //not_null activateEffects; DocumentData *centerIcon = nullptr; DocumentData *aroundAnimation = nullptr; int count = 0; bool active = false; bool effect = false; bool premium = false; }; struct PossibleItemReactionsRef { std::vector> recent; std::vector> stickers; bool customAllowed = false; bool tags = false; }; struct PossibleItemReactions { PossibleItemReactions() = default; explicit PossibleItemReactions(const PossibleItemReactionsRef &other); std::vector recent; std::vector stickers; bool customAllowed = false; bool tags = false; }; [[nodiscard]] PossibleItemReactionsRef LookupPossibleReactions( not_null item); struct MyTagInfo { ReactionId id; QString title; int count = 0; }; class Reactions final : private CustomEmojiManager::Listener { public: explicit Reactions(not_null owner); ~Reactions(); [[nodiscard]] Session &owner() const { return *_owner; } [[nodiscard]] Main::Session &session() const; void refreshTop(); void refreshRecent(); void refreshRecentDelayed(); void refreshDefault(); void refreshMyTags(SavedSublist *sublist = nullptr); void refreshMyTagsDelayed(); void refreshTags(); void refreshEffects(); enum class Type { Active, Recent, Top, All, MyTags, Tags, Effects, }; [[nodiscard]] const std::vector &list(Type type) const; [[nodiscard]] const std::vector &myTagsInfo() const; [[nodiscard]] const QString &myTagTitle(const ReactionId &id) const; [[nodiscard]] ReactionId favoriteId() const; [[nodiscard]] const Reaction *favorite() const; void setFavorite(const ReactionId &id); void incrementMyTag(const ReactionId &id, SavedSublist *sublist); void decrementMyTag(const ReactionId &id, SavedSublist *sublist); void renameTag(const ReactionId &id, const QString &name); [[nodiscard]] DocumentData *chooseGenericAnimation( not_null custom) const; [[nodiscard]] rpl::producer<> topUpdates() const; [[nodiscard]] rpl::producer<> recentUpdates() const; [[nodiscard]] rpl::producer<> defaultUpdates() const; [[nodiscard]] rpl::producer<> favoriteUpdates() const; [[nodiscard]] rpl::producer<> myTagsUpdates() const; [[nodiscard]] rpl::producer<> tagsUpdates() const; [[nodiscard]] rpl::producer myTagRenamed() const; [[nodiscard]] rpl::producer<> effectsUpdates() const; void preloadReactionImageFor(const ReactionId &emoji); [[nodiscard]] QImage resolveReactionImageFor(const ReactionId &emoji); // This is used to reserve space for the effect in BottomInfo but not // actually paint anything, used in case we want to paint icon ourselves. static constexpr auto kFakeEffectId = EffectId(1); void preloadEffectImageFor(EffectId id); [[nodiscard]] QImage resolveEffectImageFor(EffectId id); void preloadAnimationsFor(const ReactionId &emoji); void send(not_null item, bool addToRecent); [[nodiscard]] bool sending(not_null item) const; void poll(not_null item, crl::time now); void updateAllInHistory(not_null peer, bool enabled); void clearTemporary(); [[nodiscard]] Reaction *lookupTemporary(const ReactionId &id); [[nodiscard]] rpl::producer> myTagsValue( SavedSublist *sublist = nullptr); [[nodiscard]] static bool HasUnread(const MTPMessageReactions &data); static void CheckUnknownForUnread( not_null owner, const MTPMessage &message); private: struct ImageSet { QImage image; std::shared_ptr media; std::unique_ptr icon; bool fromSelectAnimation = false; bool effect = false; }; struct TagsBySublist { TagsBySublist() = default; TagsBySublist(TagsBySublist&&) = default; TagsBySublist(const TagsBySublist&) = delete; TagsBySublist &operator=(TagsBySublist&&) = default; TagsBySublist &operator=(const TagsBySublist&) = delete; std::vector tags; std::vector info; uint64 hash = 0; mtpRequestId requestId = 0; bool requestScheduled = false; bool updateScheduled = false; }; [[nodiscard]] not_null resolveListener(); void customEmojiResolveDone(not_null document) override; void requestTop(); void requestRecent(); void requestDefault(); void requestGeneric(); void requestMyTags(SavedSublist *sublist = nullptr); void requestTags(); void requestEffects(); void updateTop(const MTPDmessages_reactions &data); void updateRecent(const MTPDmessages_reactions &data); void updateDefault(const MTPDmessages_availableReactions &data); void updateGeneric(const MTPDmessages_stickerSet &data); void updateMyTags( SavedSublist *sublist, const MTPDmessages_savedReactionTags &data); void updateTags(const MTPDmessages_reactions &data); void updateEffects(const MTPDmessages_availableEffects &data); void recentUpdated(); void defaultUpdated(); void myTagsUpdated(); void tagsUpdated(); void effectsUpdated(); [[nodiscard]] std::optional resolveById(const ReactionId &id); [[nodiscard]] std::vector resolveByIds( const std::vector &ids, base::flat_set &unresolved); [[nodiscard]] std::optional resolveByInfo( const MyTagInfo &info, SavedSublist *sublist); [[nodiscard]] std::vector resolveByInfos( const std::vector &infos, base::flat_map< ReactionId, base::flat_set> &unresolved, SavedSublist *sublist); void resolve(const ReactionId &id); void applyFavorite(const ReactionId &id); void scheduleMyTagsUpdate(SavedSublist *sublist); [[nodiscard]] std::optional parse( const MTPAvailableReaction &entry); [[nodiscard]] std::optional parse( const MTPAvailableEffect &entry); void preloadEffect(const Reaction &effect); void preloadImageFor(const ReactionId &id); [[nodiscard]] QImage resolveImageFor(const ReactionId &id); void loadImage( ImageSet &set, not_null document, bool fromSelectAnimation); void generateImage(ImageSet &set, const QString &emoji); void setAnimatedIcon(ImageSet &set); void resolveReactionImages(); void resolveEffectImages(); void downloadTaskFinished(); void repaintCollected(); void pollCollected(); const not_null _owner; std::vector _active; std::vector _available; std::vector _recent; std::vector _recentIds; base::flat_set _unresolvedRecent; base::flat_map _myTags; base::flat_map< ReactionId, base::flat_set> _unresolvedMyTags; std::vector _tags; std::vector _tagsIds; base::flat_set _unresolvedTags; std::vector _top; std::vector _topIds; base::flat_set _unresolvedTop; std::vector> _genericAnimations; std::vector _effects; ReactionId _favoriteId; ReactionId _unresolvedFavoriteId; std::optional _favorite; base::flat_map< not_null, std::shared_ptr> _iconsCache; base::flat_map< not_null, std::shared_ptr> _genericCache; rpl::event_stream<> _topUpdated; rpl::event_stream<> _recentUpdated; rpl::event_stream<> _defaultUpdated; rpl::event_stream<> _favoriteUpdated; rpl::event_stream _myTagsUpdated; rpl::event_stream<> _tagsUpdated; rpl::event_stream _myTagRenamed; rpl::event_stream<> _effectsUpdated; // We need &i->second stay valid while inserting new items. // So we use std::map instead of base::flat_map here. // Otherwise we could use flat_map>. std::map _temporary; base::Timer _topRefreshTimer; mtpRequestId _topRequestId = 0; uint64 _topHash = 0; mtpRequestId _recentRequestId = 0; bool _recentRequestScheduled = false; uint64 _recentHash = 0; mtpRequestId _defaultRequestId = 0; int32 _defaultHash = 0; mtpRequestId _genericRequestId = 0; mtpRequestId _tagsRequestId = 0; uint64 _tagsHash = 0; mtpRequestId _effectsRequestId = 0; int32 _effectsHash = 0; base::flat_map _images; rpl::lifetime _imagesLoadLifetime; bool _waitingForReactions = false; bool _waitingForEffects = false; base::flat_map _sentRequests; base::flat_map, crl::time> _repaintItems; base::Timer _repaintTimer; base::flat_set> _pollItems; base::flat_set> _pollingItems; mtpRequestId _pollRequestId = 0; mtpRequestId _saveFaveRequestId = 0; rpl::lifetime _lifetime; }; struct RecentReaction { not_null peer; bool unread = false; bool big = false; bool my = false; friend inline auto operator<=>( const RecentReaction &a, const RecentReaction &b) = default; friend inline bool operator==( const RecentReaction &a, const RecentReaction &b) = default; }; class MessageReactions final { public: explicit MessageReactions(not_null item); void add(const ReactionId &id, bool addToRecent); void remove(const ReactionId &id); bool change( const QVector &list, const QVector &recent, bool ignoreChosen); [[nodiscard]] bool checkIfChanged( const QVector &list, const QVector &recent, bool ignoreChosen) const; [[nodiscard]] const std::vector &list() const; [[nodiscard]] auto recent() const -> const base::flat_map> &; [[nodiscard]] std::vector chosen() const; [[nodiscard]] bool empty() const; [[nodiscard]] bool hasUnread() const; void markRead(); private: const not_null _item; std::vector _list; base::flat_map> _recent; }; } // namespace Data