From f40391b4f0a5fc503d50647acfe17052213169fb Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 2 Jun 2023 18:26:39 +0400 Subject: [PATCH] Support two lists of stories sources. --- Telegram/SourceFiles/data/data_session.cpp | 2 +- Telegram/SourceFiles/data/data_stories.cpp | 342 ++++++++++++------ Telegram/SourceFiles/data/data_stories.h | 84 ++++- .../dialogs/dialogs_inner_widget.cpp | 9 +- .../dialogs/ui/dialogs_stories_content.cpp | 59 +-- .../dialogs/ui/dialogs_stories_content.h | 7 +- .../history/history_item_helpers.cpp | 3 +- .../stories/media_stories_controller.cpp | 138 +++---- .../media/stories/media_stories_controller.h | 14 +- .../media/stories/media_stories_sibling.cpp | 12 +- .../media/stories/media_stories_sibling.h | 4 +- .../media/stories/media_stories_view.cpp | 7 +- .../media/stories/media_stories_view.h | 8 +- .../media/view/media_view_open_common.h | 7 + .../media/view/media_view_overlay_widget.cpp | 14 +- .../media/view/media_view_overlay_widget.h | 2 + .../window/window_session_controller.cpp | 23 +- .../window/window_session_controller.h | 11 +- 18 files changed, 462 insertions(+), 284 deletions(-) diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 8df77697a8..952f1e0fec 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -314,7 +314,7 @@ Session::Session(not_null session) } }, _lifetime); - _stories->loadMore(); + _stories->loadMore(Data::StorySourcesList::NotHidden); }); } diff --git a/Telegram/SourceFiles/data/data_stories.cpp b/Telegram/SourceFiles/data/data_stories.cpp index b9fe9d67ea..a3f17a0da6 100644 --- a/Telegram/SourceFiles/data/data_stories.cpp +++ b/Telegram/SourceFiles/data/data_stories.cpp @@ -35,7 +35,7 @@ constexpr auto kMarkAsReadDelay = 3 * crl::time(1000); using UpdateFlag = StoryUpdate::Flag; -std::optional ParseMedia( +[[nodiscard]] std::optional ParseMedia( not_null owner, const MTPMessageMedia &media) { return media.match([&](const MTPDmessageMediaPhoto &data) @@ -62,8 +62,18 @@ std::optional ParseMedia( } // namespace -bool StoriesList::unread() const { - return !ids.empty() && readTill < ids.back(); +StoriesSourceInfo StoriesSource::info() const { + return { + .id = user->id, + .last = ids.empty() ? 0 : ids.back().date, + .unread = unread(), + .premium = user->isPremium(), + .hidden = hidden, + }; +} + +bool StoriesSource::unread() const { + return !ids.empty() && readTill < ids.back().id; } Story::Story( @@ -93,6 +103,10 @@ StoryId Story::id() const { return _id; } +StoryIdDate Story::idDate() const { + return { _id, _date }; +} + FullStoryId Story::fullId() const { return { _peer->id, _id }; } @@ -278,51 +292,109 @@ Main::Session &Stories::session() const { void Stories::apply(const MTPDupdateStory &data) { const auto peerId = peerFromUser(data.vuser_id()); - const auto peer = _owner->peer(peerId); - const auto i = ranges::find(_all, peer, &StoriesList::user); - const auto id = parseAndApply(peer, data.vstory()); - if (i != end(_all)) { - auto added = false; - if (id && !i->ids.contains(id)) { - i->ids.emplace(id); - ++i->total; - added = true; - } - if (added) { - ranges::rotate(begin(_all), i, i + 1); - } - } else if (id) { - _all.insert(begin(_all), StoriesList{ - .user = peer->asUser(), - .ids = { id }, - .readTill = 0, - .total = 1, - }); + const auto user = not_null(_owner->peer(peerId)->asUser()); + const auto idDate = parseAndApply(user, data.vstory()); + if (!idDate) { + return; + } + const auto i = _all.find(peerId); + if (i == end(_all)) { + requestUserStories(user); + return; + } else if (i->second.ids.contains(idDate)) { + return; + } + const auto was = i->second.info(); + i->second.ids.emplace(idDate); + const auto now = i->second.info(); + if (was == now) { + return; + } + const auto refreshInList = [&](StorySourcesList list) { + auto &sources = _sources[static_cast(list)]; + const auto i = ranges::find( + sources, + peerId, + &StoriesSourceInfo::id); + if (i != end(sources)) { + *i = now; + sort(list); + } + }; + refreshInList(StorySourcesList::All); + if (!user->hasStoriesHidden()) { + refreshInList(StorySourcesList::NotHidden); } - _allChanged.fire({}); } -StoriesList Stories::parse(const MTPUserStories &stories) { +void Stories::requestUserStories(not_null user) { + if (!_requestingUserStories.emplace(user).second) { + return; + } + _owner->session().api().request(MTPstories_GetUserStories( + user->inputUser + )).done([=](const MTPstories_UserStories &result) { + _requestingUserStories.remove(user); + const auto &data = result.data(); + _owner->processUsers(data.vusers()); + parseAndApply(data.vstories()); + }).fail([=] { + _requestingUserStories.remove(user); + applyDeletedFromSources(user->id, StorySourcesList::All); + }).send(); + +} + +void Stories::parseAndApply(const MTPUserStories &stories) { const auto &data = stories.data(); - const auto userId = UserId(data.vuser_id()); + const auto peerId = peerFromUser(data.vuser_id()); const auto readTill = data.vmax_read_id().value_or_empty(); const auto count = int(data.vstories().v.size()); - auto result = StoriesList{ - .user = _owner->user(userId), + auto result = StoriesSource{ + .user = _owner->peer(peerId)->asUser(), .readTill = readTill, - .total = count, }; const auto &list = data.vstories().v; result.ids.reserve(list.size()); for (const auto &story : list) { if (const auto id = parseAndApply(result.user, story)) { result.ids.emplace(id); - } else { - --result.total; } } - result.total = std::max(result.total, int(result.ids.size())); - return result; + if (result.ids.empty()) { + applyDeletedFromSources(peerId, StorySourcesList::All); + return; + } + const auto info = result.info(); + const auto i = _all.find(peerId); + if (i != end(_all)) { + if (i->second != result) { + i->second = std::move(result); + } + } else { + _all.emplace(peerId, std::move(result)).first; + } + const auto add = [&](StorySourcesList list) { + auto &sources = _sources[static_cast(list)]; + const auto i = ranges::find( + sources, + peerId, + &StoriesSourceInfo::id); + if (i == end(sources)) { + sources.push_back(info); + } else if (*i == info) { + return; + } else { + *i = info; + } + sort(list); + }; + add(StorySourcesList::All); + if (result.user->hasStoriesHidden()) { + applyDeletedFromSources(peerId, StorySourcesList::NotHidden); + } else { + add(StorySourcesList::NotHidden); + } } Story *Stories::parseAndApply( @@ -352,20 +424,20 @@ Story *Stories::parseAndApply( return result; } -StoryId Stories::parseAndApply( +StoryIdDate Stories::parseAndApply( not_null peer, const MTPstoryItem &story) { return story.match([&](const MTPDstoryItem &data) { if (const auto story = parseAndApply(peer, data)) { - return story->id(); + return story->idDate(); } applyDeleted({ peer->id, data.vid().v }); - return StoryId(); + return StoryIdDate(); }, [&](const MTPDstoryItemSkipped &data) { - return StoryId(data.vid().v); + return StoryIdDate{ data.vid().v, data.vdate().v }; }, [&](const MTPDstoryItemDeleted &data) { applyDeleted({ peer->id, data.vid().v }); - return StoryId(); + return StoryIdDate(); }); } @@ -398,32 +470,34 @@ void Stories::unregisterDependentMessage( } } -void Stories::loadMore() { - if (_loadMoreRequestId || _allLoaded) { +void Stories::loadMore(StorySourcesList list) { + const auto index = static_cast(list); + if (_loadMoreRequestId[index] || _sourcesLoaded[index]) { return; } + const auto all = (list == StorySourcesList::All); const auto api = &_owner->session().api(); using Flag = MTPstories_GetAllStories::Flag; - _loadMoreRequestId = api->request(MTPstories_GetAllStories( - MTP_flags(_state.isEmpty() - ? Flag(0) - : (Flag::f_next | Flag::f_state)), - MTP_string(_state) + _loadMoreRequestId[index] = api->request(MTPstories_GetAllStories( + MTP_flags((all ? Flag::f_include_hidden : Flag()) + | (_sourcesStates[index].isEmpty() + ? Flag(0) + : (Flag::f_next | Flag::f_state))), + MTP_string(_sourcesStates[index]) )).done([=](const MTPstories_AllStories &result) { - _loadMoreRequestId = 0; + _loadMoreRequestId[index] = 0; result.match([&](const MTPDstories_allStories &data) { _owner->processUsers(data.vusers()); - _state = qs(data.vstate()); - _allLoaded = !data.is_has_more(); + _sourcesStates[index] = qs(data.vstate()); + _sourcesLoaded[index] = !data.is_has_more(); for (const auto &single : data.vuser_stories().v) { - pushToBack(parse(single)); + parseAndApply(single); } - _allChanged.fire({}); }, [](const MTPDstories_allStoriesNotModified &) { }); }).fail([=] { - _loadMoreRequestId = 0; + _loadMoreRequestId[index] = 0; }).send(); } @@ -519,37 +593,64 @@ void Stories::finalizeResolve(FullStoryId id) { } void Stories::applyDeleted(FullStoryId id) { - const auto i = _stories.find(id.peer); - if (i != end(_stories)) { - const auto j = i->second.find(id.story); - if (j != end(i->second)) { - auto story = std::move(j->second); - i->second.erase(j); + const auto removeFromList = [&](StorySourcesList list) { + const auto index = static_cast(list); + auto &sources = _sources[index]; + const auto i = ranges::find( + sources, + id.peer, + &StoriesSourceInfo::id); + if (i != end(sources)) { + sources.erase(i); + _sourcesChanged[index].fire({}); + } + }; + const auto i = _all.find(id.peer); + if (i != end(_all)) { + const auto j = i->second.ids.lower_bound(StoryIdDate{ id.story }); + if (j != end(i->second.ids) && j->id == id.story) { + i->second.ids.erase(j); + if (i->second.ids.empty()) { + _all.erase(i); + removeFromList(StorySourcesList::NotHidden); + removeFromList(StorySourcesList::All); + } + } + } + _deleted.emplace(id); + const auto j = _stories.find(id.peer); + if (j != end(_stories)) { + const auto k = j->second.find(id.story); + if (k != end(j->second)) { + auto story = std::move(k->second); + j->second.erase(k); session().changes().storyUpdated( story.get(), UpdateFlag::Destroyed); removeDependencyStory(story.get()); - if (i->second.empty()) { - _stories.erase(i); + if (j->second.empty()) { + _stories.erase(j); } } } - const auto j = ranges::find(_all, id.peer, [](const StoriesList &list) { - return list.user->id; - }); - if (j != end(_all)) { - const auto removed = j->ids.remove(id.story); - if (removed) { - if (j->ids.empty()) { - _all.erase(j); - } else { - Assert(j->total > 0); - --j->total; - } - _allChanged.fire({}); +} + +void Stories::applyDeletedFromSources(PeerId id, StorySourcesList list) { + const auto removeFromList = [&](StorySourcesList from) { + auto &sources = _sources[static_cast(from)]; + const auto i = ranges::find( + sources, + id, + &StoriesSourceInfo::id); + if (i != end(sources)) { + sources.erase(i); } + _sourcesChanged[static_cast(from)].fire({}); + }; + removeFromList(StorySourcesList::NotHidden); + if (list == StorySourcesList::All) { + removeFromList(StorySourcesList::All); } - _deleted.emplace(id); } void Stories::removeDependencyStory(not_null story) { @@ -564,16 +665,36 @@ void Stories::removeDependencyStory(not_null story) { } } -const std::vector &Stories::all() { +void Stories::sort(StorySourcesList list) { + const auto index = static_cast(list); + auto &sources = _sources[index]; + const auto self = _owner->session().user()->id; + const auto proj = [&](const StoriesSourceInfo &info) { + const auto key = int64(info.last) + + (info.premium ? (int64(1) << 48) : 0) + + (info.unread ? (int64(1) << 49) : 0) + + ((info.id == self) ? (int64(1) << 50) : 0); + return std::make_pair(key, info.id); + }; + ranges::sort(sources, ranges::greater(), proj); + _sourcesChanged[index].fire({}); +} + +const base::flat_map &Stories::all() const { return _all; } -bool Stories::allLoaded() const { - return _allLoaded; +const std::vector &Stories::sources( + StorySourcesList list) const { + return _sources[static_cast(list)]; } -rpl::producer<> Stories::allChanged() const { - return _allChanged.events(); +bool Stories::sourcesLoaded(StorySourcesList list) const { + return _sourcesLoaded[static_cast(list)]; +} + +rpl::producer<> Stories::sourcesChanged(StorySourcesList list) const { + return _sourcesChanged[static_cast(list)].events(); } rpl::producer Stories::itemsChanged() const { @@ -621,35 +742,21 @@ void Stories::resolve(FullStoryId id, Fn done) { } } -void Stories::pushToBack(StoriesList &&list) { - const auto i = ranges::find(_all, list.user, &StoriesList::user); - if (i != end(_all)) { - if (*i == list) { - return; - } - *i = std::move(list); - } else { - _all.push_back(std::move(list)); - } -} - void Stories::loadAround(FullStoryId id) { - const auto i = ranges::find(_all, id.peer, [](const StoriesList &list) { - return list.user->id; - }); + const auto i = _all.find(id.peer); if (i == end(_all)) { return; } - const auto j = ranges::find(i->ids, id.story); - if (j == end(i->ids)) { + const auto j = i->second.ids.lower_bound(StoryIdDate{ id.story }); + if (j == end(i->second.ids) || j->id != id.story) { return; } const auto ignore = [&] { const auto side = kIgnorePreloadAroundIfLoaded; - const auto left = ranges::min(int(j - begin(i->ids)), side); - const auto right = ranges::min(int(end(i->ids) - j), side); + const auto left = ranges::min(int(j - begin(i->second.ids)), side); + const auto right = ranges::min(int(end(i->second.ids) - j), side); for (auto k = j - left; k != j + right; ++k) { - const auto maybeStory = lookup({ id.peer, *k }); + const auto maybeStory = lookup({ id.peer, k->id }); if (!maybeStory && maybeStory.error() == NoStory::Unknown) { return false; } @@ -660,29 +767,43 @@ void Stories::loadAround(FullStoryId id) { return; } const auto side = kPreloadAroundCount; - const auto left = ranges::min(int(j - begin(i->ids)), side); - const auto right = ranges::min(int(end(i->ids) - j), side); + const auto left = ranges::min(int(j - begin(i->second.ids)), side); + const auto right = ranges::min(int(end(i->second.ids) - j), side); const auto from = j - left; const auto till = j + right; for (auto k = from; k != till; ++k) { - resolve({ id.peer, *k }, nullptr); + resolve({ id.peer, k->id }, nullptr); } } void Stories::markAsRead(FullStoryId id, bool viewed) { - const auto i = ranges::find(_all, id.peer, [](const StoriesList &list) { - return list.user->id; - }); + const auto i = _all.find(id.peer); Assert(i != end(_all)); - if (i->readTill >= id.story) { + if (i->second.readTill >= id.story) { return; } else if (!_markReadPending.contains(id.peer)) { sendMarkAsReadRequests(); } _markReadPending.emplace(id.peer); - i->readTill = id.story; + const auto wasUnread = i->second.unread(); + i->second.readTill = id.story; + const auto nowUnread = i->second.unread(); + if (wasUnread != nowUnread) { + const auto refreshInList = [&](StorySourcesList list) { + auto &sources = _sources[static_cast(list)]; + const auto i = ranges::find( + sources, + id.peer, + &StoriesSourceInfo::id); + if (i != end(sources)) { + i->unread = nowUnread; + sort(list); + } + }; + refreshInList(StorySourcesList::All); + refreshInList(StorySourcesList::NotHidden); + } _markReadTimer.callOnce(kMarkAsReadDelay); - _allChanged.fire({}); } void Stories::sendMarkAsReadRequest( @@ -721,12 +842,9 @@ void Stories::sendMarkAsReadRequests() { ++i; continue; } - const auto j = ranges::find(_all, peerId, []( - const StoriesList &list) { - return list.user->id; - }); + const auto j = _all.find(peerId); if (j != end(_all)) { - sendMarkAsReadRequest(j->user, j->readTill); + sendMarkAsReadRequest(j->second.user, j->second.readTill); } i = _markReadPending.erase(i); } diff --git a/Telegram/SourceFiles/data/data_stories.h b/Telegram/SourceFiles/data/data_stories.h index e41312f5e4..dc7b99141c 100644 --- a/Telegram/SourceFiles/data/data_stories.h +++ b/Telegram/SourceFiles/data/data_stories.h @@ -22,6 +22,21 @@ namespace Data { class Session; +struct StoryIdDate { + StoryId id = 0; + TimeId date = 0; + + [[nodiscard]] bool valid() const { + return id != 0; + } + explicit operator bool() const { + return valid(); + } + + friend inline auto operator<=>(StoryIdDate, StoryIdDate) = default; + friend inline bool operator==(StoryIdDate, StoryIdDate) = default; +}; + struct StoryMedia { std::variant, not_null> data; @@ -35,7 +50,7 @@ struct StoryView { friend inline bool operator==(StoryView, StoryView) = default; }; -class Story { +class Story final { public: Story( StoryId id, @@ -48,6 +63,7 @@ public: [[nodiscard]] not_null peer() const; [[nodiscard]] StoryId id() const; + [[nodiscard]] StoryIdDate idDate() const; [[nodiscard]] FullStoryId fullId() const; [[nodiscard]] TimeId date() const; [[nodiscard]] const StoryMedia &media() const; @@ -89,16 +105,28 @@ private: }; -struct StoriesList { - not_null user; - base::flat_set ids; - StoryId readTill = 0; - int total = 0; +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==(StoriesList, StoriesList) = default; + friend inline bool operator==(StoriesSource, StoriesSource) = default; }; enum class NoStory : uchar { @@ -106,6 +134,13 @@ enum class NoStory : uchar { Deleted, }; +enum class StorySourcesList : uchar { + NotHidden, + All, +}; + +inline constexpr auto kStorySourcesListCount = 2; + class Stories final { public: explicit Stories(not_null owner); @@ -122,13 +157,16 @@ public: not_null dependent, not_null dependency); - void loadMore(); + void loadMore(StorySourcesList list); void apply(const MTPDupdateStory &data); void loadAround(FullStoryId id); - [[nodiscard]] const std::vector &all(); - [[nodiscard]] bool allLoaded() const; - [[nodiscard]] rpl::producer<> allChanged() const; + [[nodiscard]] const base::flat_map &all() 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 itemsChanged() const; [[nodiscard]] base::expected, NoStory> lookup( @@ -145,11 +183,11 @@ public: Fn)> done); private: - [[nodiscard]] StoriesList parse(const MTPUserStories &stories); + void parseAndApply(const MTPUserStories &stories); [[nodiscard]] Story *parseAndApply( not_null peer, const MTPDstoryItem &data); - StoryId parseAndApply( + StoryIdDate parseAndApply( not_null peer, const MTPstoryItem &story); void processResolvedStories( @@ -158,13 +196,16 @@ private: void sendResolveRequests(); void finalizeResolve(FullStoryId id); - void pushToBack(StoriesList &&list); void applyDeleted(FullStoryId id); + void applyDeletedFromSources(PeerId id, StorySourcesList list); void removeDependencyStory(not_null story); + void sort(StorySourcesList list); void sendMarkAsReadRequests(); void sendMarkAsReadRequest(not_null peer, StoryId tillId); + void requestUserStories(not_null user); + const not_null _owner; base::flat_map< PeerId, @@ -182,17 +223,20 @@ private: not_null, base::flat_set>> _dependentMessages; - std::vector _all; - rpl::event_stream<> _allChanged; - rpl::event_stream _itemsChanged; - QString _state; - bool _allLoaded = false; + base::flat_map _all; + std::vector _sources[kStorySourcesListCount]; + rpl::event_stream<> _sourcesChanged[kStorySourcesListCount]; + bool _sourcesLoaded[kStorySourcesListCount] = { false }; + QString _sourcesStates[kStorySourcesListCount]; - mtpRequestId _loadMoreRequestId = 0; + mtpRequestId _loadMoreRequestId[kStorySourcesListCount] = { 0 }; + + rpl::event_stream _itemsChanged; base::flat_set _markReadPending; base::Timer _markReadTimer; base::flat_set _markReadRequests; + base::flat_set> _requestingUserStories; StoryId _viewsStoryId = 0; std::optional _viewsOffset; diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index bbc9a2c49f..fb15560a31 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -142,7 +142,9 @@ InnerWidget::InnerWidget( , _controller(controller) , _stories(std::make_unique( this, - Stories::ContentForSession(&controller->session()), + Stories::ContentForSession( + &controller->session(), + Data::StorySourcesList::NotHidden), [=] { return _stories->height() - _visibleTop; })) , _shownList(controller->session().data().chatsList()->indexed()) , _st(&st::defaultDialogRow) @@ -339,7 +341,7 @@ InnerWidget::InnerWidget( _stories->clicks( ) | rpl::start_with_next([=](uint64 id) { - _controller->openPeerStories(PeerId(int64(id))); + _controller->openPeerStories(PeerId(int64(id)), {}); }, lifetime()); _stories->showProfileRequests( @@ -365,7 +367,8 @@ InnerWidget::InnerWidget( _stories->loadMoreRequests( ) | rpl::start_with_next([=] { - session().data().stories().loadMore(); + session().data().stories().loadMore( + Data::StorySourcesList::NotHidden); }, lifetime()); handleChatListEntryRefreshes(); diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.cpp index 7b127cd3cf..37016cfa5d 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.cpp @@ -15,8 +15,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "ui/painter.h" -#include "history/history.h" // #TODO stories testing - namespace Dialogs::Stories { namespace { @@ -51,12 +49,13 @@ private: class State final { public: - explicit State(not_null data); + State(not_null data, Data::StorySourcesList list); [[nodiscard]] Content next(); private: const not_null _data; + const Data::StorySourcesList _list; base::flat_map, std::shared_ptr> _userpics; }; @@ -122,27 +121,23 @@ void PeerUserpic::processNewPhoto() { }, _subscribed->downloadLifetime); } -State::State(not_null data) -: _data(data) { +State::State(not_null data, Data::StorySourcesList list) +: _data(data) +, _list(list) { } Content State::next() { auto result = Content(); -#if 1 // #TODO stories testing const auto &all = _data->all(); - result.users.reserve(all.size()); - for (const auto &list : all) { + const auto &sources = _data->sources(_list); + result.users.reserve(sources.size()); + for (const auto &info : sources) { + const auto i = all.find(info.id); + Assert(i != end(all)); + const auto &source = i->second; + auto userpic = std::shared_ptr(); - const auto user = list.user; -#else - const auto list = _data->owner().chatsList(); - const auto &all = list->indexed()->all(); - result.users.reserve(all.size()); - for (const auto &entry : all) { - if (const auto history = entry->history()) { - if (const auto user = history->peer->asUser(); user && !user->isBot()) { - auto userpic = std::shared_ptr(); -#endif + const auto user = source.user; if (const auto i = _userpics.find(user); i != end(_userpics)) { userpic = i->second; } else { @@ -153,41 +148,25 @@ Content State::next() { .id = uint64(user->id.value), .name = user->shortName(), .userpic = std::move(userpic), -#if 1 // #TODO stories testing - .unread = list.unread(), -#else - .unread = history->chatListBadgesState().unread + .unread = info.unread, }); } -#endif - }); - } - ranges::stable_partition(result.users, [](const User &user) { - return user.unread; - }); return result; } } // namespace -rpl::producer ContentForSession(not_null session) { +rpl::producer ContentForSession( + not_null session, + Data::StorySourcesList list) { return [=](auto consumer) { auto result = rpl::lifetime(); const auto stories = &session->data().stories(); - const auto state = result.make_state(stories); + const auto state = result.make_state(stories, list); rpl::single( rpl::empty ) | rpl::then( -#if 1 // #TODO stories testing - stories->allChanged() -#else - rpl::merge( - session->data().chatsListChanges( - ) | rpl::filter( - rpl::mappers::_1 == nullptr - ) | rpl::to_empty, - session->data().unreadBadgeChanges()) -#endif + stories->sourcesChanged(list) ) | rpl::start_with_next([=] { consumer.put_next(state->next()); }, result); diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.h b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.h index dc81f25287..1feb6a77ac 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.h @@ -7,6 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +namespace Data { +enum class StorySourcesList : uchar; +} // namespace Data + namespace Main { class Session; } // namespace Main @@ -16,6 +20,7 @@ namespace Dialogs::Stories { struct Content; [[nodiscard]] rpl::producer ContentForSession( - not_null session); + not_null session, + Data::StorySourcesList list); } // namespace Dialogs::Stories diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index 68eaa0d1e2..0a1b1a8a04 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.cpp +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -297,7 +297,8 @@ ClickHandlerPtr JumpToStoryClickHandler( ? separate->sessionController() : peer->session().tryResolveWindow(); if (controller) { - controller->openPeerStory(peer, storyId); + // #TODO stories decide context + controller->openPeerStory(peer, storyId, {}); } }); } diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp index 55062a7d31..716da7feee 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -386,25 +386,30 @@ auto Controller::stickerOrEmojiChosen() const } void Controller::show( - const std::vector &lists, - int index, - int subindex) { - Expects(index >= 0 && index < lists.size()); - Expects(subindex >= 0 && subindex < lists[index].ids.size()); - - showSiblings(lists, index); - - const auto &list = lists[index]; - const auto id = *(begin(list.ids) + subindex); - const auto storyId = FullStoryId{ - .peer = list.user->id, - .story = id, - }; - const auto maybeStory = list.user->owner().stories().lookup(storyId); - if (!maybeStory) { + not_null story, + Data::StorySourcesList list) { + auto &stories = story->owner().stories(); + const auto &all = stories.all(); + const auto &sources = stories.sources(list); + const auto storyId = story->fullId(); + const auto id = storyId.story; + const auto i = ranges::find( + sources, + storyId.peer, + &Data::StoriesSourceInfo::id); + if (i == end(sources)) { return; } - const auto story = *maybeStory; + const auto j = all.find(storyId.peer); + if (j == end(all)) { + return; + } + const auto &source = j->second; + const auto k = source.ids.lower_bound(Data::StoryIdDate{ id }); + if (k == end(source.ids) || k->id != id) { + return; + } + showSiblings(&story->session(), sources, (i - begin(sources))); const auto guard = gsl::finally([&] { _paused = false; _started = false; @@ -414,10 +419,10 @@ void Controller::show( _photoPlayback = nullptr; } }); - if (_list != list) { - _list = list; + if (_source != source) { + _source = source; } - _index = subindex; + _index = (k - begin(source.ids)); _waitingForId = {}; if (_shown == storyId) { @@ -431,16 +436,16 @@ void Controller::show( unfocusReply(); } - _header->show({ .user = list.user, .date = story->date() }); - _slider->show({ .index = _index, .total = list.total }); - _replyArea->show({ .user = list.user, .id = id }); + _header->show({ .user = source.user, .date = story->date() }); + _slider->show({ .index = _index, .total = int(source.ids.size()) }); + _replyArea->show({ .user = source.user, .id = id }); _recentViews->show({ .list = story->recentViewers(), .total = story->views(), - .valid = list.user->isSelf(), + .valid = source.user->isSelf(), }); - const auto session = &list.user->session(); + const auto session = &story->session(); if (_session != session) { _session = session; _sessionLifetime = session->changes().storyUpdates( @@ -458,14 +463,13 @@ void Controller::show( }, _sessionLifetime); } - auto &stories = session->data().stories(); - if (int(lists.size()) - index < kPreloadUsersCount) { - stories.loadMore(); + if (int(sources.end() - i) < kPreloadUsersCount) { + stories.loadMore(list); } stories.loadAround(storyId); updatePlayingAllowed(); - list.user->updateFull(); + source.user->updateFull(); } void Controller::updatePlayingAllowed() { @@ -492,21 +496,33 @@ void Controller::setPlayingAllowed(bool allowed) { } void Controller::showSiblings( - const std::vector &lists, + not_null session, + const std::vector &sources, int index) { - showSibling(_siblingLeft, (index > 0) ? &lists[index - 1] : nullptr); + showSibling( + _siblingLeft, + session, + (index > 0) ? sources[index - 1].id : PeerId()); showSibling( _siblingRight, - (index + 1 < lists.size()) ? &lists[index + 1] : nullptr); + session, + (index + 1 < sources.size()) ? sources[index + 1].id : PeerId()); } void Controller::showSibling( std::unique_ptr &sibling, - const Data::StoriesList *list) { - if (!list || list->ids.empty()) { + not_null session, + PeerId peerId) { + if (!peerId) { sibling = nullptr; - } else if (!sibling || !sibling->shows(*list)) { - sibling = std::make_unique(this, *list); + return; + } + const auto &all = session->data().stories().all(); + const auto i = all.find(peerId); + if (i == end(all)) { + sibling = nullptr; + } else if (!sibling || !sibling->shows(i->second)) { + sibling = std::make_unique(this, i->second); } } @@ -552,19 +568,19 @@ void Controller::maybeMarkAsRead(const Player::TrackState &state) { } void Controller::markAsRead() { - Expects(_list.has_value()); + Expects(_source.has_value()); - _list->user->owner().stories().markAsRead(_shown, _started); + _source->user->owner().stories().markAsRead(_shown, _started); } bool Controller::subjumpAvailable(int delta) const { const auto index = _index + delta; if (index < 0) { return _siblingLeft && _siblingLeft->shownId().valid(); - } else if (index >= _list->total) { + } else if (index >= int(_source->ids.size())) { return _siblingRight && _siblingRight->shownId().valid(); } - return index >= 0 && index < _list->total; + return index >= 0 && index < int(_source->ids.size()); } bool Controller::subjumpFor(int delta) { @@ -575,32 +591,32 @@ bool Controller::subjumpFor(int delta) { if (index < 0) { if (_siblingLeft && _siblingLeft->shownId().valid()) { return jumpFor(-1); - } else if (!_list || _list->ids.empty()) { + } else if (!_source || _source->ids.empty()) { return false; } subjumpTo(0); return true; - } else if (index >= _list->total) { + } else if (index >= int(_source->ids.size())) { return _siblingRight && _siblingRight->shownId().valid() && jumpFor(1); - } else if (index < _list->ids.size()) { + } else { subjumpTo(index); } return true; } void Controller::subjumpTo(int index) { - Expects(_list.has_value()); - Expects(index >= 0 && index < _list->ids.size()); + Expects(_source.has_value()); + Expects(index >= 0 && index < _source->ids.size()); const auto id = FullStoryId{ - .peer = _list->user->id, - .story = *(begin(_list->ids) + index) + .peer = _source->user->id, + .story = (begin(_source->ids) + index)->id, }; - auto &stories = _list->user->owner().stories(); + auto &stories = _source->user->owner().stories(); if (stories.lookup(id)) { - _delegate->storiesJumpTo(&_list->user->session(), id); + _delegate->storiesJumpTo(&_source->user->session(), id); } else if (_waitingForId != id) { _waitingForId = id; stories.loadAround(id); @@ -609,9 +625,9 @@ void Controller::subjumpTo(int index) { void Controller::checkWaitingFor() { Expects(_waitingForId.valid()); - Expects(_list.has_value()); + Expects(_source.has_value()); - auto &stories = _list->user->owner().stories(); + auto &stories = _source->user->owner().stories(); const auto maybe = stories.lookup(_waitingForId); if (!maybe) { if (maybe.error() == Data::NoStory::Deleted) { @@ -620,7 +636,7 @@ void Controller::checkWaitingFor() { return; } _delegate->storiesJumpTo( - &_list->user->session(), + &_source->user->session(), base::take(_waitingForId)); } @@ -633,7 +649,7 @@ bool Controller::jumpFor(int delta) { return true; } } else if (delta == 1) { - if (_list && _index + 1 >= _list->total) { + if (_source && _index + 1 >= int(_source->ids.size())) { markAsRead(); } if (const auto right = _siblingRight.get()) { @@ -665,7 +681,7 @@ void Controller::setMenuShown(bool shown) { } bool Controller::canDownload() const { - return _list && _list->user->isSelf(); + return _source && _source->user->isSelf(); } void Controller::repaintSibling(not_null sibling) { @@ -706,7 +722,7 @@ Fn)> Controller::viewsGotMoreCallback() { return crl::guard(&_viewsLoadGuard, [=]( const std::vector &result) { if (_viewsSlice.list.empty()) { - auto &stories = _list->user->owner().stories(); + auto &stories = _source->user->owner().stories(); if (const auto maybeStory = stories.lookup(_shown)) { _viewsSlice = { .list = result, @@ -728,11 +744,11 @@ Fn)> Controller::viewsGotMoreCallback() { } void Controller::refreshViewsFromData() { - Expects(_list.has_value()); + Expects(_source.has_value()); - auto &stories = _list->user->owner().stories(); + auto &stories = _source->user->owner().stories(); const auto maybeStory = stories.lookup(_shown); - if (!maybeStory || !_list->user->isSelf()) { + if (!maybeStory || !_source->user->isSelf()) { _viewsSlice = {}; return; } @@ -750,11 +766,11 @@ void Controller::refreshViewsFromData() { } bool Controller::sliceViewsTo(PeerId offset) { - Expects(_list.has_value()); + Expects(_source.has_value()); - auto &stories = _list->user->owner().stories(); + auto &stories = _source->user->owner().stories(); const auto maybeStory = stories.lookup(_shown); - if (!maybeStory || !_list->user->isSelf()) { + if (!maybeStory || !_source->user->isSelf()) { _viewsSlice = {}; return true; } diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.h b/Telegram/SourceFiles/media/stories/media_stories_controller.h index b8954e9e3b..d6081d84b6 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.h +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.h @@ -20,7 +20,6 @@ struct FileChosen; } // namespace ChatHelpers namespace Data { -struct StoriesList; struct FileOrigin; } // namespace Data @@ -100,10 +99,7 @@ public: [[nodiscard]] auto stickerOrEmojiChosen() const -> rpl::producer; - void show( - const std::vector &lists, - int index, - int subindex); + void show(not_null story, Data::StorySourcesList list); void ready(); void updateVideoPlayback(const Player::TrackState &state); @@ -142,11 +138,13 @@ private: void setPlayingAllowed(bool allowed); void showSiblings( - const std::vector &lists, + not_null session, + const std::vector &lists, int index); void showSibling( std::unique_ptr &sibling, - const Data::StoriesList *list); + not_null session, + PeerId peerId); void subjumpTo(int index); void checkWaitingFor(); @@ -180,7 +178,7 @@ private: FullStoryId _shown; TextWithEntities _captionText; - std::optional _list; + std::optional _source; FullStoryId _waitingForId; int _index = 0; bool _started = false; diff --git a/Telegram/SourceFiles/media/stories/media_stories_sibling.cpp b/Telegram/SourceFiles/media/stories/media_stories_sibling.cpp index 90a3939542..375bf1f23b 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_sibling.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_sibling.cpp @@ -228,10 +228,10 @@ bool Sibling::LoaderVideo::updateAfterGoodCheck() { Sibling::Sibling( not_null controller, - const Data::StoriesList &list) + const Data::StoriesSource &source) : _controller(controller) -, _id{ list.user->id, list.ids.front() } -, _peer(list.user) { +, _id{ source.user->id, source.ids.front().id } +, _peer(source.user) { checkStory(); _goodShown.stop(); } @@ -279,10 +279,10 @@ not_null Sibling::peer() const { return _peer; } -bool Sibling::shows(const Data::StoriesList &list) const { - Expects(!list.ids.empty()); +bool Sibling::shows(const Data::StoriesSource &source) const { + Expects(!source.ids.empty()); - return _id == FullStoryId{ list.user->id, list.ids.front() }; + return _id == FullStoryId{ source.user->id, source.ids.front().id }; } SiblingView Sibling::view(const SiblingLayout &layout, float64 over) { diff --git a/Telegram/SourceFiles/media/stories/media_stories_sibling.h b/Telegram/SourceFiles/media/stories/media_stories_sibling.h index f6eb4edc75..ed4b8789ca 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_sibling.h +++ b/Telegram/SourceFiles/media/stories/media_stories_sibling.h @@ -26,12 +26,12 @@ class Sibling final : public base::has_weak_ptr { public: Sibling( not_null controller, - const Data::StoriesList &list); + const Data::StoriesSource &source); ~Sibling(); [[nodiscard]] FullStoryId shownId() const; [[nodiscard]] not_null peer() const; - [[nodiscard]] bool shows(const Data::StoriesList &list) const; + [[nodiscard]] bool shows(const Data::StoriesSource &source) const; [[nodiscard]] SiblingView view( const SiblingLayout &layout, diff --git a/Telegram/SourceFiles/media/stories/media_stories_view.cpp b/Telegram/SourceFiles/media/stories/media_stories_view.cpp index 18a4bb4998..0fbc387218 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_view.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_view.cpp @@ -22,11 +22,8 @@ View::View(not_null delegate) View::~View() = default; -void View::show( - const std::vector &lists, - int index, - int subindex) { - _controller->show(lists, index, subindex); +void View::show(not_null story, Data::StorySourcesList list) { + _controller->show(story, list); } void View::ready() { diff --git a/Telegram/SourceFiles/media/stories/media_stories_view.h b/Telegram/SourceFiles/media/stories/media_stories_view.h index d01bd3d613..a671bdbde6 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_view.h +++ b/Telegram/SourceFiles/media/stories/media_stories_view.h @@ -8,7 +8,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once namespace Data { -struct StoriesList; +class Story; +enum class StorySourcesList : uchar; struct FileOrigin; } // namespace Data @@ -52,10 +53,7 @@ public: explicit View(not_null delegate); ~View(); - void show( - const std::vector &lists, - int index, - int subindex); + void show(not_null story, Data::StorySourcesList list); void ready(); [[nodiscard]] bool canDownload() const; diff --git a/Telegram/SourceFiles/media/view/media_view_open_common.h b/Telegram/SourceFiles/media/view/media_view_open_common.h index 79b835461f..aed2fde585 100644 --- a/Telegram/SourceFiles/media/view/media_view_open_common.h +++ b/Telegram/SourceFiles/media/view/media_view_open_common.h @@ -16,6 +16,7 @@ class HistoryItem; namespace Data { class Story; +enum class StorySourcesList : uchar; } // namespace Data namespace Window { @@ -74,10 +75,12 @@ public: OpenRequest( Window::SessionController *controller, not_null story, + Data::StorySourcesList list, bool continueStreaming = false, crl::time startTime = 0) : _controller(controller) , _story(story) + , _storiesList(list) , _continueStreaming(continueStreaming) , _startTime(startTime) { } @@ -105,6 +108,9 @@ public: [[nodiscard]] Data::Story *story() const { return _story; } + [[nodiscard]] Data::StorySourcesList storiesList() const { + return _storiesList; + } [[nodiscard]] std::optional cloudTheme() const { return _cloudTheme; @@ -127,6 +133,7 @@ private: DocumentData *_document = nullptr; PhotoData *_photo = nullptr; Data::Story *_story = nullptr; + Data::StorySourcesList _storiesList = {}; PeerData *_peer = nullptr; HistoryItem *_item = nullptr; MsgId _topicRootId = 0; diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index df290b5746..00bb0f645a 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -4973,15 +4973,13 @@ void OverlayWidget::setContext( _topicRootId = MsgId(); _history = nullptr; _peer = nullptr; - const auto &all = story->peer->owner().stories().all(); - const auto i = ranges::find( - all, - story->peer, - &Data::StoriesList::user); - Assert(i != end(all)); - const auto j = ranges::find(i->ids, story->id); setStoriesPeer(story->peer); - _stories->show(all, (i - begin(all)), j - begin(i->ids)); + auto &stories = story->peer->owner().stories(); + const auto maybeStory = stories.lookup( + { story->peer->id, story->id }); + if (maybeStory) { + _stories->show(*maybeStory, story->list); + } } else { _message = nullptr; _topicRootId = MsgId(); diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h index a32c0ef59b..8376786755 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h @@ -30,6 +30,7 @@ enum class activation : uchar; namespace Data { class PhotoMedia; class DocumentMedia; +enum class StorySourcesList : uchar; } // namespace Data namespace Ui { @@ -306,6 +307,7 @@ private: struct StoriesContext { not_null peer; StoryId id = 0; + Data::StorySourcesList list = {}; }; void setContext(std::variant< v::null_t, diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index b30e9dd133..793964d9ee 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -2466,28 +2466,33 @@ Ui::ChatThemeBackgroundData SessionController::backgroundData( void SessionController::openPeerStory( not_null peer, - StoryId storyId) { + StoryId storyId, + Data::StorySourcesList list) { using namespace Media::View; using namespace Data; auto &stories = session().data().stories(); if (const auto from = stories.lookup({ peer->id, storyId })) { - window().openInMediaView(OpenRequest(this, *from)); + window().openInMediaView(OpenRequest(this, *from, list)); } } -void SessionController::openPeerStories(PeerId peerId) { +void SessionController::openPeerStories( + PeerId peerId, + Data::StorySourcesList list) { using namespace Media::View; using namespace Data; auto &stories = session().data().stories(); const auto &all = stories.all(); - const auto i = ranges::find(all, peerId, [](const StoriesList &list) { - return list.user->id; - }); - if (i != end(all) && !i->ids.empty()) { - const auto j = i->ids.lower_bound(i->readTill + 1); - openPeerStory(i->user, j != i->ids.end() ? *j : i->ids.front()); + const auto i = all.find(peerId); + if (i != end(all)) { + const auto j = i->second.ids.lower_bound( + StoryIdDate{ i->second.readTill + 1 }); + openPeerStory( + i->second.user, + j != i->second.ids.end() ? j->id : i->second.ids.front().id, + list); } } diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index 07f763ca81..5891696553 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -29,6 +29,10 @@ namespace Adaptive { enum class WindowLayout; } // namespace Adaptive +namespace Data { +enum class StorySourcesList : uchar; +} // namespace Data + namespace ChatHelpers { class TabbedSelector; class EmojiInteractions; @@ -564,8 +568,11 @@ public: return _peerThemeOverride.value(); } - void openPeerStory(not_null peer, StoryId storyId); - void openPeerStories(PeerId peerId); + void openPeerStory( + not_null peer, + StoryId storyId, + Data::StorySourcesList list); + void openPeerStories(PeerId peerId, Data::StorySourcesList list); struct PaintContextArgs { not_null theme;