diff --git a/Telegram/SourceFiles/data/data_stories.cpp b/Telegram/SourceFiles/data/data_stories.cpp index 47bed21db..d3baee3bb 100644 --- a/Telegram/SourceFiles/data/data_stories.cpp +++ b/Telegram/SourceFiles/data/data_stories.cpp @@ -1353,6 +1353,96 @@ void Stories::loadViewsSlice( } } +void Stories::loadReactionsSlice( + not_null peer, + StoryId id, + QString offset, + Fn done) { + Expects(peer->isChannel()); + + if (_reactionsStoryPeer == peer + && _reactionsStoryId == id + && _reactionsOffset == offset) { + if (_reactionsRequestId) { + _reactionsDone = std::move(done); + } + return; + } + _reactionsStoryPeer = peer; + _reactionsStoryId = id; + _reactionsOffset = offset; + _reactionsDone = std::move(done); + + using Flag = MTPstories_GetStoryReactionsList::Flag; + const auto api = &_owner->session().api(); + _owner->session().api().request(_reactionsRequestId).cancel(); + _reactionsRequestId = api->request(MTPstories_GetStoryReactionsList( + MTP_flags(offset.isEmpty() ? Flag() : Flag::f_offset), + _reactionsStoryPeer->input, + MTP_int(_reactionsStoryId), + MTPReaction(), + MTP_string(_reactionsOffset), + MTP_int(kViewsPerPage) + )).done([=](const MTPstories_StoryReactionsList &result) { + _reactionsRequestId = 0; + + const auto &data = result.data(); + auto slice = StoryViews{ + .nextOffset = data.vnext_offset().value_or_empty(), + .reactions = data.vcount().v, + .total = data.vcount().v, + }; + _owner->processUsers(data.vusers()); + _owner->processChats(data.vchats()); + slice.list.reserve(data.vreactions().v.size()); + for (const auto &reaction : data.vreactions().v) { + reaction.match([&](const MTPDstoryReaction &data) { + slice.list.push_back({ + .peer = _owner->peer(peerFromMTP(data.vpeer_id())), + .reaction = ReactionFromMTP(data.vreaction()), + .date = data.vdate().v, + }); + }, [&](const MTPDstoryReactionPublicRepost &data) { + const auto story = applySingle( + peerFromMTP(data.vpeer_id()), + data.vstory()); + if (story) { + slice.list.push_back({ + .peer = story->peer(), + .repostId = story->id(), + }); + } + }, [&](const MTPDstoryReactionPublicForward &data) { + const auto item = _owner->addNewMessage( + data.vmessage(), + {}, + NewMessageType::Existing); + if (item) { + slice.list.push_back({ + .peer = item->history()->peer, + .forwardId = item->id, + }); + } + }); + } + const auto fullId = FullStoryId{ + .peer = _reactionsStoryPeer->id, + .story = _reactionsStoryId, + }; + if (const auto story = lookup(fullId)) { + (*story)->applyChannelReactionsSlice(_reactionsOffset, slice); + } + if (const auto done = base::take(_reactionsDone)) { + done(std::move(slice)); + } + }).fail([=] { + _reactionsRequestId = 0; + if (const auto done = base::take(_reactionsDone)) { + done({}); + } + }).send(); +} + void Stories::sendViewsSliceRequest() { Expects(_viewsStoryPeer != nullptr); Expects(_viewsStoryPeer->isSelf()); @@ -1379,6 +1469,7 @@ void Stories::sendViewsSliceRequest() { .total = data.vcount().v, }; _owner->processUsers(data.vusers()); + _owner->processChats(data.vchats()); slice.list.reserve(data.vviews().v.size()); for (const auto &view : data.vviews().v) { view.match([&](const MTPDstoryView &data) { diff --git a/Telegram/SourceFiles/data/data_stories.h b/Telegram/SourceFiles/data/data_stories.h index b12881ed2..1cc3bb30d 100644 --- a/Telegram/SourceFiles/data/data_stories.h +++ b/Telegram/SourceFiles/data/data_stories.h @@ -182,6 +182,11 @@ public: StoryId id, QString offset, Fn done); + void loadReactionsSlice( + not_null peer, + StoryId id, + QString offset, + Fn done); [[nodiscard]] bool hasArchive(not_null peer) const; @@ -379,6 +384,12 @@ private: 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; diff --git a/Telegram/SourceFiles/data/data_story.cpp b/Telegram/SourceFiles/data/data_story.cpp index ec584efaa..e1d0c87c9 100644 --- a/Telegram/SourceFiles/data/data_story.cpp +++ b/Telegram/SourceFiles/data/data_story.cpp @@ -513,6 +513,10 @@ const StoryViews &Story::viewsList() const { return _views; } +const StoryViews &Story::channelReactionsList() const { + return _channelReactions; +} + int Story::interactions() const { return _views.total; } @@ -543,6 +547,9 @@ void Story::applyViewsSlice( _views.known = true; if (offset.isEmpty()) { _views = slice; + if (!_channelReactions.total) { + _channelReactions.total = _views.reactions + _views.forwards; + } } else if (_views.nextOffset == offset) { _views.list.insert( end(_views.list), @@ -591,6 +598,33 @@ void Story::applyViewsSlice( } } +void Story::applyChannelReactionsSlice( + const QString &offset, + const StoryViews &slice) { + const auto changed = (_channelReactions.reactions != slice.reactions) + || (_channelReactions.total != slice.total); + _channelReactions.reactions = slice.reactions; + _channelReactions.total = slice.total; + _channelReactions.known = true; + if (offset.isEmpty()) { + _channelReactions = slice; + } else if (_channelReactions.nextOffset == offset) { + _channelReactions.list.insert( + end(_channelReactions.list), + begin(slice.list), + end(slice.list)); + _channelReactions.nextOffset = slice.nextOffset; + if (_channelReactions.nextOffset.isEmpty()) { + _channelReactions.total = int(_channelReactions.list.size()); + } + } + if (changed) { + _peer->session().changes().storyUpdated( + this, + UpdateFlag::ViewsChanged); + } +} + const std::vector &Story::locations() const { return _locations; } @@ -793,6 +827,9 @@ void Story::updateViewsCounts(ViewsCounts &&counts, bool known, bool initial) { .total = total, .known = known, }; + if (!_channelReactions.total) { + _channelReactions.total = _views.reactions + _views.forwards; + } } if (viewsChanged) { _recentViewers = std::move(counts.viewers); diff --git a/Telegram/SourceFiles/data/data_story.h b/Telegram/SourceFiles/data/data_story.h index 60b4ee9a4..e953212bc 100644 --- a/Telegram/SourceFiles/data/data_story.h +++ b/Telegram/SourceFiles/data/data_story.h @@ -179,11 +179,15 @@ public: [[nodiscard]] auto recentViewers() const -> const std::vector> &; [[nodiscard]] const StoryViews &viewsList() const; + [[nodiscard]] const StoryViews &channelReactionsList() const; [[nodiscard]] int interactions() const; [[nodiscard]] int views() const; [[nodiscard]] int forwards() const; [[nodiscard]] int reactions() const; void applyViewsSlice(const QString &offset, const StoryViews &slice); + void applyChannelReactionsSlice( + const QString &offset, + const StoryViews &slice); [[nodiscard]] const std::vector &locations() const; [[nodiscard]] auto suggestedReactions() const @@ -235,6 +239,7 @@ private: std::vector _locations; std::vector _suggestedReactions; StoryViews _views; + StoryViews _channelReactions; const TimeId _date = 0; const TimeId _expires = 0; TimeId _lastUpdateTime = 0; diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp index fdc83d1d9..07605c365 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -907,6 +907,7 @@ void Controller::show( .views = story->views(), .total = story->interactions(), .type = RecentViewsTypeFor(peer), + .canViewReactions = CanViewReactionsFor(peer), }, _reactions->likedValue()); if (const auto nowLikeButton = _recentViews->likeButton()) { if (wasLikeButton != nowLikeButton) { @@ -998,13 +999,15 @@ void Controller::subscribeToSession() { show(update.story, _context); _delegate->storiesRedisplay(update.story); } else { + const auto peer = update.story->peer(); _recentViews->show({ .list = update.story->recentViewers(), .reactions = update.story->reactions(), .forwards = update.story->forwards(), .views = update.story->views(), .total = update.story->interactions(), - .type = RecentViewsTypeFor(update.story->peer()), + .type = RecentViewsTypeFor(peer), + .canViewReactions = CanViewReactionsFor(peer), }); updateAreas(update.story); } @@ -1414,11 +1417,19 @@ const Data::StoryViews &Controller::views(int limit, bool initial) { const auto done = viewsGotMoreCallback(); const auto peer = shownPeer(); auto &stories = peer->owner().stories(); - stories.loadViewsSlice( - peer, - _shown.story, - _viewsSlice.nextOffset, - done); + if (peer->isChannel()) { + stories.loadReactionsSlice( + peer, + _shown.story, + _viewsSlice.nextOffset, + done); + } else { + stories.loadViewsSlice( + peer, + _shown.story, + _viewsSlice.nextOffset, + done); + } } return _viewsSlice; } @@ -1433,7 +1444,11 @@ Fn Controller::viewsGotMoreCallback() { const auto peer = shownPeer(); auto &stories = peer->owner().stories(); if (const auto maybeStory = stories.lookup(_shown)) { - _viewsSlice = (*maybeStory)->viewsList(); + if (peer->isChannel()) { + _viewsSlice = (*maybeStory)->channelReactionsList(); + } else { + _viewsSlice = (*maybeStory)->viewsList(); + } } else { _viewsSlice = {}; } @@ -1596,8 +1611,12 @@ void Controller::refreshViewsFromData() { const auto peer = shownPeer(); auto &stories = peer->owner().stories(); const auto maybeStory = stories.lookup(_shown); - if (!maybeStory || !peer->isSelf()) { + const auto check = peer->isSelf() + || CanViewReactionsFor(peer); + if (!maybeStory || !check) { _viewsSlice = {}; + } else if (peer->isChannel()) { + _viewsSlice = (*maybeStory)->channelReactionsList(); } else { _viewsSlice = (*maybeStory)->viewsList(); } diff --git a/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp b/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp index 8e2d4bbb5..2521de47f 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_who_reacted.h" // FormatReadDate. #include "chat_helpers/compose/compose_show.h" #include "data/stickers/data_custom_emoji.h" +#include "data/data_channel.h" #include "data/data_peer.h" #include "data/data_session.h" #include "data/data_stories.h" @@ -145,6 +146,13 @@ RecentViewsType RecentViewsTypeFor(not_null peer) { : RecentViewsType::Other; } +bool CanViewReactionsFor(not_null peer) { + if (const auto channel = peer->asChannel()) { + return channel->amCreator() || channel->hasAdminRights(); + } + return false; +} + RecentViews::RecentViews(not_null controller) : _controller(controller) { } @@ -178,8 +186,10 @@ void RecentViews::show( || (_data.forwards != data.forwards) || (_data.reactions != data.reactions); const auto usersChanged = !_userpics || (_data.list != data.list); + const auto canViewReactions = data.canViewReactions + && (data.reactions > 0 || data.forwards > 0); _data = data; - if (_data.type != RecentViewsType::Self) { + if (_data.type != RecentViewsType::Self && !canViewReactions) { _text = {}; _clickHandlerLifetime.destroy(); _userpicsLifetime.destroy(); @@ -229,7 +239,8 @@ Ui::RpWidget *RecentViews::likeIconWidget() const { } void RecentViews::refreshClickHandler() { - const auto nowEmpty = _data.list.empty(); + const auto nowEmpty = (_data.type != RecentViewsType::Channel) + && _data.list.empty(); const auto wasEmpty = !_clickHandlerLifetime; const auto raw = _widget.get(); if (wasEmpty == nowEmpty) { @@ -389,13 +400,16 @@ void RecentViews::updateViewsReactionsGeometry() { void RecentViews::updatePartsGeometry() { const auto skip = st::storiesRecentViewsSkip; const auto full = _userpicsWidth + skip + _text.maxWidth(); + const auto add = (_data.type == RecentViewsType::Channel) + ? st::storiesViewsTextPosition.y() + : 0; const auto use = std::min(full, _outer.width()); const auto ux = _outer.x() + (_outer.width() - use) / 2; const auto uheight = st::storiesWhoViewed.userpics.size; - const auto uy = _outer.y() + (_outer.height() - uheight) / 2; + const auto uy = _outer.y() + (_outer.height() - uheight) / 2 + add; const auto tx = ux + _userpicsWidth + skip; const auto theight = st::normalFont->height; - const auto ty = _outer.y() + (_outer.height() - theight) / 2; + const auto ty = _outer.y() + (_outer.height() - theight) / 2 + add; const auto my = std::min(uy, ty); const auto mheight = std::max(uheight, theight); const auto padding = skip; @@ -406,7 +420,9 @@ void RecentViews::updatePartsGeometry() { } void RecentViews::updateText() { - const auto text = _data.views + const auto text = (_data.type == RecentViewsType::Channel) + ? u"View reactions"_q + : _data.views ? (tr::lng_stories_views(tr::now, lt_count, _data.views) + (_data.reactions ? (u" "_q + QChar(10084) + QString::number(_data.reactions)) @@ -417,7 +433,8 @@ void RecentViews::updateText() { } void RecentViews::showMenu() { - if (_menu || _data.list.empty()) { + if (_menu + || (_data.type != RecentViewsType::Channel && _data.list.empty())) { return; } diff --git a/Telegram/SourceFiles/media/stories/media_stories_recent_views.h b/Telegram/SourceFiles/media/stories/media_stories_recent_views.h index f4acd05ef..ea8d69313 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_recent_views.h +++ b/Telegram/SourceFiles/media/stories/media_stories_recent_views.h @@ -48,6 +48,7 @@ struct RecentViewsData { int views = 0; int total = 0; RecentViewsType type = RecentViewsType::Other; + bool canViewReactions = false; friend inline auto operator<=>( const RecentViewsData &, @@ -58,6 +59,7 @@ struct RecentViewsData { }; [[nodiscard]] RecentViewsType RecentViewsTypeFor(not_null peer); +[[nodiscard]] bool CanViewReactionsFor(not_null peer); class RecentViews final { public: diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 9977b8e8b..ea586c3b6 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -396,6 +396,7 @@ updateSentStoryReaction#7d627683 peer:Peer story_id:int reaction:Reaction = Upda updateBotChatBoost#904dd49c peer:Peer boost:Boost qts:int = Update; updateChannelViewForumAsMessages#7b68920 channel_id:long enabled:Bool = Update; updatePeerWallpaper#ae3f101d flags:# wallpaper_overridden:flags.1?true peer:Peer wallpaper:flags.0?WallPaper = Update; +updateBotMessageReaction#ac21d3ce peer:Peer msg_id:int date:int actor:Peer old_reactions:Vector new_reactions:Vector qts:int = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -1558,7 +1559,7 @@ storyView#b0bdeac5 flags:# blocked:flags.0?true blocked_my_stories_from:flags.1? storyViewPublicForward#9083670b flags:# blocked:flags.0?true blocked_my_stories_from:flags.1?true message:Message = StoryView; storyViewPublicRepost#bd74cf49 flags:# blocked:flags.0?true blocked_my_stories_from:flags.1?true peer_id:Peer story:StoryItem = StoryView; -stories.storyViewsList#19a16886 flags:# count:int views_count:int forwards_count:int reactions_count:int views:Vector users:Vector next_offset:flags.0?string = stories.StoryViewsList; +stories.storyViewsList#59d78fc5 flags:# count:int views_count:int forwards_count:int reactions_count:int views:Vector chats:Vector users:Vector next_offset:flags.0?string = stories.StoryViewsList; stories.storyViews#de9eed1d views:Vector users:Vector = stories.StoryViews;