/* 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/expected.h" #include "base/timer.h" #include "base/weak_ptr.h" class Image; class PhotoData; class DocumentData; namespace Main { class Session; } // namespace Main namespace Data { class Session; struct StoryIdDates { StoryId id = 0; TimeId date = 0; TimeId expires = 0; [[nodiscard]] bool valid() const { return id != 0; } explicit operator bool() const { return valid(); } friend inline auto operator<=>(StoryIdDates, StoryIdDates) = default; friend inline bool operator==(StoryIdDates, StoryIdDates) = default; }; struct StoriesIds { base::flat_set> list; friend inline bool operator==( const StoriesIds&, const StoriesIds&) = default; }; struct StoryMedia { std::variant, not_null> data; friend inline bool operator==(StoryMedia, StoryMedia) = default; }; struct StoryView { not_null peer; TimeId date = 0; friend inline bool operator==(StoryView, StoryView) = default; }; class Story final { public: Story( StoryId id, not_null peer, StoryMedia media, TimeId date, TimeId expires); [[nodiscard]] Session &owner() const; [[nodiscard]] Main::Session &session() const; [[nodiscard]] not_null peer() const; [[nodiscard]] StoryId id() const; [[nodiscard]] bool mine() const; [[nodiscard]] StoryIdDates idDates() const; [[nodiscard]] FullStoryId fullId() const; [[nodiscard]] TimeId date() const; [[nodiscard]] TimeId expires() const; [[nodiscard]] bool expired(TimeId now = 0) const; [[nodiscard]] const StoryMedia &media() const; [[nodiscard]] PhotoData *photo() const; [[nodiscard]] DocumentData *document() const; [[nodiscard]] bool hasReplyPreview() const; [[nodiscard]] Image *replyPreview() const; [[nodiscard]] TextWithEntities inReplyText() const; void setPinned(bool pinned); [[nodiscard]] bool pinned() const; void setCaption(TextWithEntities &&caption); [[nodiscard]] const TextWithEntities &caption() const; void setViewsData(std::vector> recent, int total); [[nodiscard]] auto recentViewers() const -> const std::vector> &; [[nodiscard]] const std::vector &viewsList() const; [[nodiscard]] int views() const; void applyViewsSlice( const std::optional &offset, const std::vector &slice, int total); bool applyChanges(StoryMedia media, const MTPDstoryItem &data); private: const StoryId _id = 0; const not_null _peer; StoryMedia _media; TextWithEntities _caption; std::vector> _recentViewers; std::vector _viewsList; int _views = 0; const TimeId _date = 0; const TimeId _expires = 0; bool _pinned = false; }; struct StoriesSourceInfo { PeerId id = 0; TimeId last = 0; bool unread = false; bool premium = false; bool hidden = false; 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]] bool unread() const; friend inline bool operator==(StoriesSource, StoriesSource) = default; }; enum class NoStory : uchar { Unknown, Deleted, }; enum class StorySourcesList : uchar { NotHidden, All, }; struct StoriesContextSingle { }; struct StoriesContextPeer { }; struct StoriesContextSaved { }; struct StoriesContextArchive { }; 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(); [[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(not_null peer, const MTPUserStories *data); 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); [[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); 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); private: struct Saved { StoriesIds ids; int total = -1; StoryId lastId = 0; bool loaded = false; mtpRequestId requestId = 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 applyDeleted(FullStoryId id); void applyExpired(FullStoryId id); void applyRemovedFromActive(FullStoryId id); void applyDeletedFromSources(PeerId id, StorySourcesList list); void removeDependencyStory(not_null story); void savedStateUpdated(not_null story); void sort(StorySourcesList list); [[nodiscard]] std::shared_ptr lookupItem( not_null story); void sendMarkAsReadRequests(); void sendMarkAsReadRequest(not_null peer, StoryId tillId); void requestUserStories(not_null user); void addToArchive(not_null story); void registerExpiring(TimeId expires, FullStoryId id); void scheduleExpireTimer(); void processExpired(); 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]; 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_set> _requestingUserStories; StoryId _viewsStoryId = 0; std::optional _viewsOffset; Fn)> _viewsDone; mtpRequestId _viewsRequestId = 0; }; } // namespace Data