/* 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/qt/qt_compare.h" #include "base/expected.h" #include "base/timer.h" #include "base/weak_ptr.h" #include "data/data_story.h" namespace Main { class Session; } // namespace Main namespace Ui { class Show; } // namespace Ui namespace Data { inline constexpr auto kStoriesAlbumIdSaved = 0; inline constexpr auto kStoriesAlbumIdArchive = -1; class Folder; class Session; struct StoryView; struct StoryIdDates; class Story; struct StoryAlbum; class StoryPreload; struct StoriesIds { std::vector list; // flat_set for saved/archive. std::vector pinnedToTop; friend inline bool operator==( const StoriesIds&, const StoriesIds&) = default; }; [[nodiscard]] std::vector RespectingPinned(const StoriesIds &ids); struct StoriesSourceInfo { PeerId id = 0; TimeId last = 0; uint32 count : 15 = 0; uint32 unreadCount : 15 = 0; uint32 premium : 1 = 0; friend inline bool operator==( StoriesSourceInfo, StoriesSourceInfo) = default; }; struct StoriesSource { not_null peer; base::flat_set ids; StoryId readTill = 0; bool hidden = false; [[nodiscard]] StoriesSourceInfo info() const; [[nodiscard]] int unreadCount() const; [[nodiscard]] StoryIdDates toOpen() const; friend inline bool operator==(StoriesSource, StoriesSource) = default; }; enum class NoStory : uchar { Unknown, Deleted, }; enum class StorySourcesList : uchar { NotHidden, Hidden, }; struct StoriesContextSingle { friend inline auto operator<=>( StoriesContextSingle, StoriesContextSingle) = default; friend inline bool operator==(StoriesContextSingle, StoriesContextSingle) = default; }; struct StoriesContextPeer { friend inline auto operator<=>( StoriesContextPeer, StoriesContextPeer) = default; friend inline bool operator==(StoriesContextPeer, StoriesContextPeer) = default; }; struct StoriesContextAlbum { int id = 0; friend inline auto operator<=>( StoriesContextAlbum, StoriesContextAlbum) = default; friend inline bool operator==(StoriesContextAlbum, StoriesContextAlbum) = default; }; struct StoriesContext { std::variant< StoriesContextSingle, StoriesContextPeer, StoriesContextAlbum, StorySourcesList> data; friend inline auto operator<=>( StoriesContext, StoriesContext) = default; friend inline bool operator==(StoriesContext, StoriesContext) = default; }; struct StealthMode { TimeId enabledTill = 0; TimeId cooldownTill = 0; friend inline auto operator<=>(StealthMode, StealthMode) = default; friend inline bool operator==(StealthMode, StealthMode) = default; }; struct StoryAlbumUpdate { not_null peer; int albumId = 0; std::vector added; std::vector removed; }; inline constexpr auto kStorySourcesListCount = 2; struct StoryAlbumIdsKey { PeerId peerId; int albumId = 0; friend inline auto operator<=>( StoryAlbumIdsKey, StoryAlbumIdsKey) = default; friend inline bool operator==( StoryAlbumIdsKey, StoryAlbumIdsKey) = default; }; class Stories final : public base::has_weak_ptr { public: explicit Stories(not_null owner); ~Stories(); static constexpr auto kInProfileToastDuration = 4 * crl::time(1000); [[nodiscard]] Session &owner() const; [[nodiscard]] Main::Session &session() const; void updateDependentMessages(not_null story); void registerDependentMessage( not_null dependent, not_null dependency); void unregisterDependentMessage( not_null dependent, not_null dependency); void loadMore(StorySourcesList list); void apply(const MTPDupdateStory &data); void apply(const MTPDupdateReadStories &data); void apply(const MTPStoriesStealthMode &stealthMode); void apply(not_null peer, const MTPPeerStories *data); Story *applySingle(PeerId peerId, const MTPstoryItem &story); void loadAround(FullStoryId id, StoriesContext context); const StoriesSource *source(PeerId id) const; [[nodiscard]] const std::vector &sources( StorySourcesList list) const; [[nodiscard]] bool sourcesLoaded(StorySourcesList list) const; [[nodiscard]] rpl::producer<> sourcesChanged( StorySourcesList list) const; [[nodiscard]] rpl::producer sourceChanged() const; [[nodiscard]] rpl::producer itemsChanged() const; [[nodiscard]] base::expected, NoStory> lookup( FullStoryId id) const; void resolve(FullStoryId id, Fn done, bool force = false); [[nodiscard]] std::shared_ptr resolveItem(FullStoryId id); [[nodiscard]] std::shared_ptr resolveItem( not_null story); [[nodiscard]] bool isQuitPrevent(); void markAsRead(FullStoryId id, bool viewed); void toggleHidden( PeerId peerId, bool hidden, std::shared_ptr show); static constexpr auto kViewsPerPage = 50; void loadViewsSlice( not_null peer, StoryId id, QString offset, Fn done); void loadReactionsSlice( not_null peer, StoryId id, QString offset, Fn done); [[nodiscard]] bool hasArchive(not_null peer) const; [[nodiscard]] const StoriesIds &albumIds( PeerId peerId, int albumId) const; [[nodiscard]] rpl::producer albumIdsChanged() const; [[nodiscard]] int albumIdsCount(PeerId peerId, int albumId) const; [[nodiscard]] bool albumIdsCountKnown(PeerId peerId, int albumId) const; [[nodiscard]] bool albumIdsLoaded(PeerId peerId, int albumId) const; void albumIdsLoadMore(PeerId peerId, int albumId); [[nodiscard]] const base::flat_set &albumKnownInArchive( PeerId peerId, int albumId) const; [[nodiscard]] auto albumsListValue(PeerId peerId) -> rpl::producer>; void albumCreate( not_null peer, const QString &title, StoryId addId, Fn done, Fn fail); void albumRename( not_null peer, int id, const QString &title, Fn done, Fn fail); void albumDelete(not_null peer, int id); void notifyAlbumUpdate(StoryAlbumUpdate &&update); [[nodiscard]] rpl::producer albumUpdates() const; void deleteList(const std::vector &ids); void toggleInProfileList( const std::vector &ids, bool inProfile); [[nodiscard]] bool canTogglePinnedList( const std::vector &ids, bool pin) const; [[nodiscard]] int maxPinnedCount() const; void togglePinnedList(const std::vector &ids, bool pin); void incrementPreloadingMainSources(); void decrementPreloadingMainSources(); void incrementPreloadingHiddenSources(); void decrementPreloadingHiddenSources(); void setPreloadingInViewer(std::vector ids); struct PeerSourceState { StoryId maxId = 0; StoryId readTill = 0; }; [[nodiscard]] std::optional peerSourceState( not_null peer, StoryId storyMaxId); [[nodiscard]] bool isUnread(not_null story); enum class Polling { Chat, Viewer, }; void registerPolling(not_null story, Polling polling); void unregisterPolling(not_null story, Polling polling); [[nodiscard]] bool registerPolling(FullStoryId id, Polling polling); void unregisterPolling(FullStoryId id, Polling polling); void requestPeerStories( not_null peer, Fn done = nullptr); void savedStateChanged(not_null story); [[nodiscard]] std::shared_ptr lookupItem( not_null story); [[nodiscard]] StealthMode stealthMode() const; [[nodiscard]] rpl::producer stealthModeValue() const; void activateStealthMode(Fn done = nullptr); void sendReaction(FullStoryId id, Data::ReactionId reaction); private: struct Set { StoriesIds ids; base::flat_set albumKnownInArchive; int total = -1; StoryId lastId = 0; bool loaded = false; mtpRequestId requestId = 0; }; struct Albums { rpl::variable> list; base::flat_map sets; uint64 hash = 0; mtpRequestId requestId = 0; }; struct PollingSettings { int chat = 0; int viewer = 0; }; enum class ParseSource : uchar { MyStrip, DirectRequest, }; void albumIdsLoadMore(PeerId peerId, int albumId, bool reload); void parseAndApply(const MTPPeerStories &stories, ParseSource source); [[nodiscard]] Story *parseAndApply( not_null peer, const MTPDstoryItem &data, TimeId now); StoryIdDates parseAndApply( not_null peer, const MTPstoryItem &story, TimeId now); void processResolvedStories( not_null peer, const QVector &list); void sendResolveRequests(); void finalizeResolve(FullStoryId id); void updatePeerStoriesState(not_null peer); [[nodiscard]] Set *lookupArchive(not_null peer); void clearArchive(not_null peer); const Set *albumIdsSet(PeerId peerId, int albumId) const; Set *albumIdsSet(PeerId peerId, int albumId, bool lazy = false); void applyDeleted(not_null peer, StoryId id); void applyExpired(FullStoryId id); void applyRemovedFromActive(FullStoryId id); void applyDeletedFromSources(PeerId id, StorySourcesList list); void removeDependencyStory(not_null story); void sort(StorySourcesList list); bool bumpReadTill(PeerId peerId, StoryId maxReadTill); void requestReadTills(); void sendMarkAsReadRequests(); void sendMarkAsReadRequest(not_null peer, StoryId tillId); void sendIncrementViewsRequests(); void checkQuitPreventFinished(); void registerExpiring(TimeId expires, FullStoryId id); void scheduleExpireTimer(); void processExpired(); void preloadSourcesChanged(StorySourcesList list); bool rebuildPreloadSources(StorySourcesList list); void continuePreloading(); [[nodiscard]] bool shouldContinuePreload(FullStoryId id) const; [[nodiscard]] FullStoryId nextPreloadId() const; void startPreloading(not_null story); void preloadFinished(FullStoryId id, bool markAsPreloaded = false); void preloadListsMore(); void notifySourcesChanged(StorySourcesList list); void pushHiddenCountsToFolder(); void setPinnedToTop( PeerId peerId, std::vector &&pinnedToTop); [[nodiscard]] int pollingInterval( const PollingSettings &settings) const; void maybeSchedulePolling( not_null story, const PollingSettings &settings, TimeId now); void sendPollingRequests(); void sendPollingViewsRequests(); void sendViewsSliceRequest(); void sendViewsCountsRequest(); void loadAlbums(not_null peer, Albums &albums); const not_null _owner; std::unordered_map< PeerId, base::flat_map>> _stories; base::flat_map> _deletingStories; std::unordered_map< PeerId, base::flat_map>> _items; base::flat_multi_map _expiring; base::flat_set _peersWithDeletedStories; base::flat_set _deleted; base::Timer _expireTimer; bool _expireSchedulePosted = false; base::flat_map< PeerId, base::flat_map>>> _resolvePending; base::flat_map< PeerId, base::flat_map>>> _resolveSent; std::unordered_map< not_null, base::flat_set>> _dependentMessages; std::unordered_map _all; std::vector _sources[kStorySourcesListCount]; rpl::event_stream<> _sourcesChanged[kStorySourcesListCount]; bool _sourcesLoaded[kStorySourcesListCount] = { false }; QString _sourcesStates[kStorySourcesListCount]; Folder *_folderForHidden = nullptr; mtpRequestId _loadMoreRequestId[kStorySourcesListCount] = { 0 }; rpl::event_stream _sourceChanged; rpl::event_stream _itemsChanged; std::unordered_map _archive; std::unordered_map _saved; std::unordered_map _albums; rpl::event_stream _albumUpdates; rpl::event_stream _albumIdsChanged; base::flat_set _markReadPending; base::Timer _markReadTimer; base::flat_set _markReadRequests; base::flat_map< not_null, std::vector>> _requestingPeerStories; base::flat_map> _incrementViewsPending; base::Timer _incrementViewsTimer; base::flat_set _incrementViewsRequests; PeerData *_viewsStoryPeer = nullptr; StoryId _viewsStoryId = 0; QString _viewsOffset; Fn _viewsDone; mtpRequestId _viewsRequestId = 0; PeerData *_reactionsStoryPeer = nullptr; StoryId _reactionsStoryId = 0; QString _reactionsOffset; Fn _reactionsDone; mtpRequestId _reactionsRequestId = 0; base::flat_set _preloaded; std::vector _toPreloadSources[kStorySourcesListCount]; std::vector _toPreloadViewer; std::unique_ptr _preloading; int _preloadingHiddenSourcesCounter = 0; int _preloadingMainSourcesCounter = 0; base::flat_map _readTill; base::flat_set _pendingReadTillItems; base::flat_map, StoryId> _pendingPeerStateMaxId; mtpRequestId _readTillsRequestId = 0; bool _readTillReceived = false; base::flat_map, PollingSettings> _pollingSettings; base::flat_set> _pollingViews; base::Timer _pollingTimer; base::Timer _pollingViewsTimer; rpl::variable _stealthMode; rpl::lifetime _lifetime; }; } // namespace Data