diff --git a/Telegram/SourceFiles/data/data_stories.cpp b/Telegram/SourceFiles/data/data_stories.cpp index e21a6dfa5..9fc7a1203 100644 --- a/Telegram/SourceFiles/data/data_stories.cpp +++ b/Telegram/SourceFiles/data/data_stories.cpp @@ -1318,10 +1318,14 @@ void Stories::sendIncrementViewsRequests() { } void Stories::loadViewsSlice( + not_null peer, StoryId id, QString offset, Fn done) { - if (_viewsStoryId == id + Expects(peer->isSelf() || !done); + + if (_viewsStoryPeer == peer + && _viewsStoryId == id && _viewsOffset == offset && (!offset.isEmpty() || _viewsRequestId)) { if (_viewsRequestId) { @@ -1329,21 +1333,32 @@ void Stories::loadViewsSlice( } return; } + _viewsStoryPeer = peer; _viewsStoryId = id; _viewsOffset = offset; _viewsDone = std::move(done); - const auto api = &_owner->session().api(); - const auto perPage = _viewsDone ? kViewsPerPage : kPollingViewsPerPage; - api->request(_viewsRequestId).cancel(); + if (peer->isSelf()) { + sendViewsSliceRequest(); + } else { + sendViewsCountsRequest(); + } +} + +void Stories::sendViewsSliceRequest() { + Expects(_viewsStoryPeer != nullptr); + Expects(_viewsStoryPeer->isSelf()); + using Flag = MTPstories_GetStoryViewsList::Flag; + const auto api = &_owner->session().api(); + _owner->session().api().request(_viewsRequestId).cancel(); _viewsRequestId = api->request(MTPstories_GetStoryViewsList( MTP_flags(Flag::f_reactions_first), - MTP_inputPeerSelf(), + _viewsStoryPeer->input, MTPstring(), // q - MTP_int(id), - MTP_string(offset), - MTP_int(perPage) + MTP_int(_viewsStoryId), + MTP_string(_viewsOffset), + MTP_int(_viewsDone ? kViewsPerPage : kPollingViewsPerPage) )).done([=](const MTPstories_StoryViewsList &result) { _viewsRequestId = 0; @@ -1382,6 +1397,34 @@ void Stories::loadViewsSlice( }).send(); } +void Stories::sendViewsCountsRequest() { + Expects(_viewsStoryPeer != nullptr); + Expects(!_viewsDone); + + const auto api = &_owner->session().api(); + _owner->session().api().request(_viewsRequestId).cancel(); + _viewsRequestId = api->request(MTPstories_GetStoriesViews( + _viewsStoryPeer->input, + MTP_vector(1, MTP_int(_viewsStoryId)) + )).done([=](const MTPstories_StoryViews &result) { + _viewsRequestId = 0; + + const auto &data = result.data(); + _owner->processUsers(data.vusers()); + if (data.vviews().v.size() == 1) { + const auto fullId = FullStoryId{ + _viewsStoryPeer->id, + _viewsStoryId, + }; + if (const auto story = lookup(fullId)) { + (*story)->applyViewsCounts(data.vviews().v.front().data()); + } + } + }).fail([=] { + _viewsRequestId = 0; + }).send(); +} + bool Stories::hasArchive(not_null peer) const { if (peer->isSelf()) { return true; @@ -1745,7 +1788,7 @@ void Stories::registerPolling(not_null story, Polling polling) { case Polling::Chat: ++settings.chat; break; case Polling::Viewer: ++settings.viewer; - if (story->peer()->isSelf() + if ((story->peer()->isSelf() || story->peer()->isChannel()) && _pollingViews.emplace(story).second) { sendPollingViewsRequests(); } @@ -1838,7 +1881,8 @@ void Stories::sendPollingViewsRequests() { return; } else if (!_viewsRequestId) { Assert(_viewsDone == nullptr); - loadViewsSlice(_pollingViews.front()->id(), QString(), nullptr); + const auto story = _pollingViews.front(); + loadViewsSlice(story->peer(), story->id(), QString(), nullptr); } _pollingViewsTimer.callOnce(kPollViewsInterval); } diff --git a/Telegram/SourceFiles/data/data_stories.h b/Telegram/SourceFiles/data/data_stories.h index 2791e8077..16cc27d38 100644 --- a/Telegram/SourceFiles/data/data_stories.h +++ b/Telegram/SourceFiles/data/data_stories.h @@ -178,6 +178,7 @@ public: static constexpr auto kViewsPerPage = 50; void loadViewsSlice( + not_null peer, StoryId id, QString offset, Fn done); @@ -315,6 +316,8 @@ private: TimeId now); void sendPollingRequests(); void sendPollingViewsRequests(); + void sendViewsSliceRequest(); + void sendViewsCountsRequest(); const not_null _owner; std::unordered_map< @@ -370,6 +373,7 @@ private: base::Timer _incrementViewsTimer; base::flat_set _incrementViewsRequests; + PeerData *_viewsStoryPeer = nullptr; StoryId _viewsStoryId = 0; QString _viewsOffset; Fn _viewsDone; diff --git a/Telegram/SourceFiles/data/data_story.cpp b/Telegram/SourceFiles/data/data_story.cpp index e3acfe06b..7500071c5 100644 --- a/Telegram/SourceFiles/data/data_story.cpp +++ b/Telegram/SourceFiles/data/data_story.cpp @@ -516,6 +516,46 @@ void Story::applyChanges( applyFields(std::move(media), data, now, false); } +Story::ViewsCounts Story::parseViewsCounts( + const MTPDstoryViews &data, + const Data::ReactionId &mine) { + auto result = ViewsCounts{ + .views = data.vviews_count().v, + .reactions = data.vreactions_count().value_or_empty(), + }; + if (const auto list = data.vrecent_viewers()) { + result.viewers.reserve(list->v.size()); + auto &owner = _peer->owner(); + auto &&cut = list->v + | ranges::views::take(kRecentViewersMax); + for (const auto &id : cut) { + result.viewers.push_back(owner.peer(peerFromUser(id))); + } + } + auto total = 0; + if (const auto list = data.vreactions() + ; list && _peer->isChannel()) { + result.reactionsCounts.reserve(list->v.size()); + for (const auto &reaction : list->v) { + const auto &data = reaction.data(); + const auto id = Data::ReactionFromMTP(data.vreaction()); + const auto count = data.vcount().v; + result.reactionsCounts[id] = count; + total += count; + } + } + if (!mine.empty()) { + if (auto &count = result.reactionsCounts[mine]; !count) { + count = 1; + ++total; + } + } + if (result.reactions < total) { + result.reactions = total; + } + return result; +} + void Story::applyFields( StoryMedia media, const MTPDstoryItem &data, @@ -547,51 +587,18 @@ void Story::applyFields( &owner().session(), data.ventities().value_or_empty()), }; - auto views = _views.total; - auto reactions = _views.reactions; - auto viewers = std::vector>(); + auto counts = ViewsCounts(); auto viewsKnown = _views.known; - auto reactionsCounts = base::flat_map(); if (const auto info = data.vviews()) { - const auto &data = info->data(); - views = data.vviews_count().v; - reactions = data.vreactions_count().value_or_empty(); + counts = parseViewsCounts(info->data(), reaction); viewsKnown = true; - if (const auto list = data.vrecent_viewers()) { - viewers.reserve(list->v.size()); - auto &owner = _peer->owner(); - auto &&cut = list->v - | ranges::views::take(kRecentViewersMax); - for (const auto &id : cut) { - viewers.push_back(owner.peer(peerFromUser(id))); - } - } - auto total = 0; - if (const auto list = data.vreactions() - ; list && _peer->isChannel()) { - reactionsCounts.reserve(list->v.size()); - for (const auto &reaction : list->v) { - const auto &data = reaction.data(); - const auto id = Data::ReactionFromMTP(data.vreaction()); - const auto count = data.vcount().v; - reactionsCounts[id] = count; - total += count; - } - } - if (!reaction.empty()) { - if (auto &mine = reactionsCounts[reaction]; !mine) { - mine = 1; - ++total; - } - } - if (reactions < total) { - reactions = total; - } } else { - viewers = _recentViewers; + counts.views = _views.total; + counts.reactions = _views.reactions; + counts.viewers = _recentViewers; for (const auto &suggested : _suggestedReactions) { if (const auto count = suggested.count) { - reactionsCounts[suggested.reaction] = count; + counts.reactionsCounts[suggested.reaction] = count; } } } @@ -604,8 +611,9 @@ void Story::applyFields( if (const auto location = ParseLocation(area)) { locations.push_back(*location); } else if (auto reaction = ParseSuggestedReaction(area)) { - const auto i = reactionsCounts.find(reaction->reaction); - if (i != end(reactionsCounts)) { + const auto i = counts.reactionsCounts.find( + reaction->reaction); + if (i != end(counts.reactionsCounts)) { reaction->count = i->second; } suggestedReactions.push_back(*reaction); @@ -617,9 +625,6 @@ void Story::applyFields( const auto editedChanged = (_edited != edited); const auto mediaChanged = (_media != media); const auto captionChanged = (_caption != caption); - const auto viewsChanged = (_views.total != views) - || (_views.reactions != reactions) - || (_recentViewers != viewers); const auto locationsChanged = (_locations != locations); const auto suggestedReactionsChanged = (_suggestedReactions != suggestedReactions); @@ -633,18 +638,6 @@ void Story::applyFields( _edited = edited; _pinned = pinned; _noForwards = noForwards; - if (_views.reactions != reactions - || _views.total != views - || _views.known != viewsKnown) { - _views = StoryViews{ - .reactions = reactions, - .total = views, - .known = viewsKnown, - }; - } - if (viewsChanged) { - _recentViewers = std::move(viewers); - } if (mediaChanged) { _media = std::move(media); } @@ -660,17 +653,18 @@ void Story::applyFields( if (reactionChanged) { _sentReactionId = reaction; } + updateViewsCounts(std::move(counts), viewsKnown, initial); const auto changed = editedChanged || captionChanged || mediaChanged - || locationsChanged + || locationsChanged; + const auto reactionsChanged = reactionChanged || suggestedReactionsChanged; - if (!initial && (changed || viewsChanged || reactionChanged)) { + if (!initial && (changed || reactionsChanged)) { _peer->session().changes().storyUpdated(this, UpdateFlag() | (changed ? UpdateFlag::Edited : UpdateFlag()) - | (viewsChanged ? UpdateFlag::ViewsChanged : UpdateFlag()) - | (reactionChanged ? UpdateFlag::Reaction : UpdateFlag())); + | (reactionsChanged ? UpdateFlag::Reaction : UpdateFlag())); } if (!initial && (captionChanged || mediaChanged)) { if (const auto item = _peer->owner().stories().lookupItem(this)) { @@ -683,6 +677,44 @@ void Story::applyFields( } } +void Story::updateViewsCounts(ViewsCounts &&counts, bool known, bool initial) { + const auto viewsChanged = (_views.total != counts.views) + || (_views.reactions != counts.reactions) + || (_recentViewers != counts.viewers); + if (_views.reactions != counts.reactions + || _views.total != counts.views + || _views.known != known) { + _views = StoryViews{ + .reactions = counts.reactions, + .total = counts.views, + .known = known, + }; + } + if (viewsChanged) { + _recentViewers = std::move(counts.viewers); + _peer->session().changes().storyUpdated( + this, + UpdateFlag::ViewsChanged); + } +} + +void Story::applyViewsCounts(const MTPDstoryViews &data) { + auto counts = parseViewsCounts(data, _sentReactionId); + auto suggestedCountsChanged = false; + for (auto &suggested : _suggestedReactions) { + const auto i = counts.reactionsCounts.find(suggested.reaction); + const auto v = (i != end(counts.reactionsCounts)) ? i->second : 0; + if (suggested.count != v) { + suggested.count = v; + suggestedCountsChanged = true; + } + } + updateViewsCounts(std::move(counts), true, false); + if (suggestedCountsChanged) { + _peer->session().changes().storyUpdated(this, UpdateFlag::Reaction); + } +} + TimeId Story::lastUpdateTime() const { return _lastUpdateTime; } diff --git a/Telegram/SourceFiles/data/data_story.h b/Telegram/SourceFiles/data/data_story.h index f5e0c93f9..7ebe6bbde 100644 --- a/Telegram/SourceFiles/data/data_story.h +++ b/Telegram/SourceFiles/data/data_story.h @@ -178,9 +178,17 @@ public: StoryMedia media, const MTPDstoryItem &data, TimeId now); + void applyViewsCounts(const MTPDstoryViews &data); [[nodiscard]] TimeId lastUpdateTime() const; private: + struct ViewsCounts { + int views = 0; + int reactions = 0; + base::flat_map reactionsCounts; + std::vector> viewers; + }; + void changeSuggestedReactionCount(Data::ReactionId id, int delta); void applyFields( StoryMedia media, @@ -188,6 +196,11 @@ private: TimeId now, bool initial); + void updateViewsCounts(ViewsCounts &&counts, bool known, bool initial); + [[nodiscard]] ViewsCounts parseViewsCounts( + const MTPDstoryViews &data, + const Data::ReactionId &mine); + const StoryId _id = 0; const not_null _peer; Data::ReactionId _sentReactionId; diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp index 4a0fbd0eb..3201fb844 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -1367,7 +1367,11 @@ const Data::StoryViews &Controller::views(int limit, bool initial) { const auto done = viewsGotMoreCallback(); const auto peer = shownPeer(); auto &stories = peer->owner().stories(); - stories.loadViewsSlice(_shown.story, _viewsSlice.nextOffset, done); + stories.loadViewsSlice( + peer, + _shown.story, + _viewsSlice.nextOffset, + done); } return _viewsSlice; }