/* 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; enum class ReportReason; } // namespace Ui namespace Data { class Folder; class Session; struct StoryView; struct StoryIdDates; class Story; class StoryPreload; struct StoriesIds { base::flat_set> list; friend inline bool operator==( const StoriesIds&, const StoriesIds&) = default; }; 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 user; 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 StoriesContextSaved { friend inline auto operator<=>( StoriesContextSaved, StoriesContextSaved) = default; friend inline bool operator==(StoriesContextSaved, StoriesContextSaved) = default; }; struct StoriesContextArchive { friend inline auto operator<=>( StoriesContextArchive, StoriesContextArchive) = default; friend inline bool operator==(StoriesContextArchive, StoriesContextArchive) = default; }; struct StoriesContext { std::variant< StoriesContextSingle, StoriesContextPeer, StoriesContextSaved, StoriesContextArchive, StorySourcesList> data; friend inline auto operator<=>( StoriesContext, StoriesContext) = default; friend inline bool operator==(StoriesContext, StoriesContext) = default; }; inline constexpr auto kStorySourcesListCount = 2; class Stories final : public base::has_weak_ptr { public: explicit Stories(not_null owner); ~Stories(); static constexpr auto kPinnedToastDuration = 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(not_null peer, const MTPUserStories *data); Story *applyFromWebpage(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( StoryId id, std::optional offset, Fn)> done); [[nodiscard]] const StoriesIds &archive() const; [[nodiscard]] rpl::producer<> archiveChanged() const; [[nodiscard]] int archiveCount() const; [[nodiscard]] bool archiveCountKnown() const; [[nodiscard]] bool archiveLoaded() const; void archiveLoadMore(); [[nodiscard]] const StoriesIds *saved(PeerId peerId) const; [[nodiscard]] rpl::producer savedChanged() const; [[nodiscard]] int savedCount(PeerId peerId) const; [[nodiscard]] bool savedCountKnown(PeerId peerId) const; [[nodiscard]] bool savedLoaded(PeerId peerId) const; void savedLoadMore(PeerId peerId); void deleteList(const std::vector &ids); void togglePinnedList(const std::vector &ids, bool pinned); void report( std::shared_ptr show, FullStoryId id, Ui::ReportReason reason, QString text); 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); bool registerPolling(FullStoryId id, Polling polling); void unregisterPolling(FullStoryId id, Polling polling); void requestUserStories( not_null user, Fn done = nullptr); void savedStateChanged(not_null story); [[nodiscard]] std::shared_ptr lookupItem( not_null story); private: struct Saved { StoriesIds ids; int total = -1; StoryId lastId = 0; bool loaded = false; mtpRequestId requestId = 0; }; struct PollingSettings { int chat = 0; int viewer = 0; }; void parseAndApply(const MTPUserStories &stories); [[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 updateUserStoriesState(not_null peer); void applyDeleted(FullStoryId 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(); [[nodiscard]] int pollingInterval( const PollingSettings &settings) const; void maybeSchedulePolling( not_null story, const PollingSettings &settings, TimeId now); void sendPollingRequests(); void sendPollingViewsRequests(); const not_null _owner; std::unordered_map< PeerId, base::flat_map>> _stories; std::unordered_map< PeerId, base::flat_map>> _items; base::flat_multi_map _expiring; 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; StoriesIds _archive; int _archiveTotal = -1; StoryId _archiveLastId = 0; bool _archiveLoaded = false; rpl::event_stream<> _archiveChanged; mtpRequestId _archiveRequestId = 0; std::unordered_map _saved; rpl::event_stream _savedChanged; base::flat_set _markReadPending; base::Timer _markReadTimer; base::flat_set _markReadRequests; base::flat_map< not_null, std::vector>> _requestingUserStories; base::flat_map> _incrementViewsPending; base::Timer _incrementViewsTimer; base::flat_set _incrementViewsRequests; StoryId _viewsStoryId = 0; std::optional _viewsOffset; Fn)> _viewsDone; mtpRequestId _viewsRequestId = 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> _pendingUserStateMaxId; mtpRequestId _readTillsRequestId = 0; bool _readTillReceived = false; base::flat_map, PollingSettings> _pollingSettings; base::flat_set> _pollingViews; base::Timer _pollingTimer; base::Timer _pollingViewsTimer; }; } // namespace Data