diff --git a/Telegram/SourceFiles/data/data_stories.cpp b/Telegram/SourceFiles/data/data_stories.cpp index 8aa84afd2..4107f7a79 100644 --- a/Telegram/SourceFiles/data/data_stories.cpp +++ b/Telegram/SourceFiles/data/data_stories.cpp @@ -862,7 +862,8 @@ void Stories::activateStealthMode(Fn done) { using Flag = MTPstories_ActivateStealthMode::Flag; api->request(MTPstories_ActivateStealthMode( MTP_flags(Flag::f_past | Flag::f_future) - )).done([=](const MTPBool &result) { + )).done([=](const MTPUpdates &result) { + api->applyUpdates(result); if (done) done(); }).fail([=] { if (done) done(); @@ -1233,11 +1234,11 @@ void Stories::sendIncrementViewsRequests() { void Stories::loadViewsSlice( StoryId id, - std::optional offset, - Fn)> done) { + QString offset, + Fn done) { if (_viewsStoryId == id && _viewsOffset == offset - && (offset || _viewsRequestId)) { + && (!offset.isEmpty() || _viewsRequestId)) { if (_viewsRequestId) { _viewsDone = std::move(done); } @@ -1251,21 +1252,27 @@ void Stories::loadViewsSlice( const auto perPage = _viewsDone ? kViewsPerPage : kPollingViewsPerPage; api->request(_viewsRequestId).cancel(); _viewsRequestId = api->request(MTPstories_GetStoryViewsList( + MTP_flags(0), + MTPstring(), // q MTP_int(id), - MTP_int(offset ? offset->date : 0), - MTP_long(offset ? peerToUser(offset->peer->id).bare : 0), + MTP_string(offset), MTP_int(perPage) )).done([=](const MTPstories_StoryViewsList &result) { _viewsRequestId = 0; - auto slice = std::vector(); - const auto &data = result.data(); + auto slice = StoryViews{ + .nextOffset = data.vnext_offset().value_or_empty(), + .total = data.vcount().v, + }; _owner->processUsers(data.vusers()); - slice.reserve(data.vviews().v.size()); + slice.list.reserve(data.vviews().v.size()); for (const auto &view : data.vviews().v) { - slice.push_back({ + slice.list.push_back({ .peer = _owner->peer(peerFromUser(view.data().vuser_id())), + .reaction = (view.data().vreaction() + ? ReactionFromMTP(*view.data().vreaction()) + : Data::ReactionId()), .date = view.data().vdate().v, }); } @@ -1274,7 +1281,7 @@ void Stories::loadViewsSlice( .story = _viewsStoryId, }; if (const auto story = lookup(fullId)) { - (*story)->applyViewsSlice(_viewsOffset, slice, data.vcount().v); + (*story)->applyViewsSlice(_viewsOffset, slice); } if (const auto done = base::take(_viewsDone)) { done(std::move(slice)); @@ -1713,7 +1720,7 @@ void Stories::sendPollingViewsRequests() { return; } else if (!_viewsRequestId) { Assert(_viewsDone == nullptr); - loadViewsSlice(_pollingViews.front()->id(), std::nullopt, nullptr); + loadViewsSlice(_pollingViews.front()->id(), QString(), nullptr); } _pollingViewsTimer.callOnce(kPollViewsInterval); } diff --git a/Telegram/SourceFiles/data/data_stories.h b/Telegram/SourceFiles/data/data_stories.h index 545acdfe0..ae91d1ffc 100644 --- a/Telegram/SourceFiles/data/data_stories.h +++ b/Telegram/SourceFiles/data/data_stories.h @@ -179,8 +179,8 @@ public: static constexpr auto kViewsPerPage = 50; void loadViewsSlice( StoryId id, - std::optional offset, - Fn)> done); + QString offset, + Fn done); [[nodiscard]] const StoriesIds &archive() const; [[nodiscard]] rpl::producer<> archiveChanged() const; @@ -366,8 +366,8 @@ private: base::flat_set _incrementViewsRequests; StoryId _viewsStoryId = 0; - std::optional _viewsOffset; - Fn)> _viewsDone; + QString _viewsOffset; + Fn _viewsDone; mtpRequestId _viewsRequestId = 0; base::flat_set _preloaded; diff --git a/Telegram/SourceFiles/data/data_story.cpp b/Telegram/SourceFiles/data/data_story.cpp index e8389ad2c..8f629bd34 100644 --- a/Telegram/SourceFiles/data/data_story.cpp +++ b/Telegram/SourceFiles/data/data_story.cpp @@ -339,51 +339,35 @@ const std::vector> &Story::recentViewers() const { return _recentViewers; } -const std::vector &Story::viewsList() const { - return _viewsList; -} - -int Story::views() const { +const StoryViews &Story::viewsList() const { return _views; } +int Story::views() const { + return _views.total; +} + void Story::applyViewsSlice( - const std::optional &offset, - const std::vector &slice, - int total) { - const auto changed = (_views != total); - _views = total; - if (!offset) { - const auto i = _viewsList.empty() - ? end(slice) - : ranges::find(slice, _viewsList.front()); - const auto merge = (i != end(slice)) - && !ranges::contains(slice, _viewsList.back()); - if (merge) { - _viewsList.insert(begin(_viewsList), begin(slice), i); - } else { - _viewsList = slice; - } - } else if (!slice.empty()) { - const auto i = ranges::find(_viewsList, *offset); - const auto merge = (i != end(_viewsList)) - && !ranges::contains(_viewsList, slice.back()); - if (merge) { - const auto after = i + 1; - if (after == end(_viewsList)) { - _viewsList.insert(after, begin(slice), end(slice)); - } else { - const auto j = ranges::find(slice, _viewsList.back()); - if (j != end(slice)) { - _viewsList.insert(end(_viewsList), j + 1, end(slice)); - } - } + const QString &offset, + const StoryViews &slice) { + const auto changed = (_views.total != slice.total); + _views.total = slice.total; + if (offset.isEmpty()) { + _views = slice; + } else if (_views.nextOffset == offset) { + _views.list.insert( + end(_views.list), + begin(slice.list), + end(slice.list)); + _views.nextOffset = slice.nextOffset; + if (_views.nextOffset.isEmpty()) { + _views.total = int(_views.list.size()); } } - const auto known = int(_viewsList.size()); + const auto known = int(_views.list.size()); if (known >= _recentViewers.size()) { const auto take = std::min(known, kRecentViewersMax); - auto viewers = _viewsList + auto viewers = _views.list | ranges::views::take(take) | ranges::views::transform(&StoryView::peer) | ranges::to_vector; @@ -436,7 +420,7 @@ void Story::applyFields( &owner().session(), data.ventities().value_or_empty()), }; - auto views = _views; + auto views = _views.total; auto viewers = std::vector>(); if (!data.is_min()) { if (const auto info = data.vviews()) { @@ -457,7 +441,7 @@ void Story::applyFields( const auto editedChanged = (_edited != edited); const auto mediaChanged = (_media != media); const auto captionChanged = (_caption != caption); - const auto viewsChanged = (_views != views) + const auto viewsChanged = (_views.total != views) || (_recentViewers != viewers); _privacyPublic = (privacy == StoryPrivacy::Public); @@ -468,8 +452,10 @@ void Story::applyFields( _edited = edited; _pinned = pinned; _noForwards = noForwards; + if (_views.total != views) { + _views = StoryViews{ .total = views }; + } if (viewsChanged) { - _views = views; _recentViewers = std::move(viewers); } if (mediaChanged) { diff --git a/Telegram/SourceFiles/data/data_story.h b/Telegram/SourceFiles/data/data_story.h index 70370f176..21ab368b6 100644 --- a/Telegram/SourceFiles/data/data_story.h +++ b/Telegram/SourceFiles/data/data_story.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "base/weak_ptr.h" +#include "data/data_message_reaction_id.h" class Image; class PhotoData; @@ -58,11 +59,18 @@ struct StoryMedia { struct StoryView { not_null peer; + Data::ReactionId reaction; TimeId date = 0; friend inline bool operator==(StoryView, StoryView) = default; }; +struct StoryViews { + std::vector list; + QString nextOffset; + int total = 0; +}; + class Story final { public: Story( @@ -115,12 +123,9 @@ public: [[nodiscard]] auto recentViewers() const -> const std::vector> &; - [[nodiscard]] const std::vector &viewsList() const; + [[nodiscard]] const StoryViews &viewsList() const; [[nodiscard]] int views() const; - void applyViewsSlice( - const std::optional &offset, - const std::vector &slice, - int total); + void applyViewsSlice(const QString &offset, const StoryViews &slice); void applyChanges( StoryMedia media, @@ -140,8 +145,7 @@ private: StoryMedia _media; TextWithEntities _caption; std::vector> _recentViewers; - std::vector _viewsList; - int _views = 0; + StoryViews _views; 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 9e74b82b8..bc2233f3d 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -1254,12 +1254,17 @@ SiblingView Controller::sibling(SiblingType type) const { return {}; } -ViewsSlice Controller::views(PeerId offset) { +const Data::StoryViews &Controller::views(int limit, bool initial) { invalidate_weak_ptrs(&_viewsLoadGuard); - if (!offset) { + if (initial) { refreshViewsFromData(); - } else if (!sliceViewsTo(offset)) { - return { .left = _viewsSlice.left }; + } + if (_viewsSlice.total > _viewsSlice.list.size() + && _viewsSlice.list.size() < limit) { + const auto done = viewsGotMoreCallback(); + const auto user = shownUser(); + auto &stories = user->owner().stories(); + stories.loadViewsSlice(_shown.story, _viewsSlice.nextOffset, done); } return _viewsSlice; } @@ -1268,27 +1273,25 @@ rpl::producer<> Controller::moreViewsLoaded() const { return _moreViewsLoaded.events(); } -Fn)> Controller::viewsGotMoreCallback() { - return crl::guard(&_viewsLoadGuard, [=]( - const std::vector &result) { +Fn Controller::viewsGotMoreCallback() { + return crl::guard(&_viewsLoadGuard, [=](Data::StoryViews result) { if (_viewsSlice.list.empty()) { const auto user = shownUser(); auto &stories = user->owner().stories(); if (const auto maybeStory = stories.lookup(_shown)) { - _viewsSlice = { - .list = result, - .left = (*maybeStory)->views() - int(result.size()), - }; + _viewsSlice = (*maybeStory)->viewsList(); } else { _viewsSlice = {}; } } else { _viewsSlice.list.insert( end(_viewsSlice.list), - begin(result), - end(result)); - _viewsSlice.left - = std::max(_viewsSlice.left - int(result.size()), 0); + begin(result.list), + end(result.list)); + _viewsSlice.total = result.nextOffset.isEmpty() + ? int(_viewsSlice.list.size()) + : std::max(result.total, int(_viewsSlice.list.size())); + _viewsSlice.nextOffset = result.nextOffset; } _moreViewsLoaded.fire({}); }); @@ -1439,46 +1442,9 @@ void Controller::refreshViewsFromData() { return; } const auto story = *maybeStory; - const auto &list = story->viewsList(); + const auto &views = story->viewsList(); const auto total = story->views(); - _viewsSlice.list = list - | ranges::views::take(Data::Stories::kViewsPerPage) - | ranges::to_vector; - _viewsSlice.left = total - int(_viewsSlice.list.size()); - if (_viewsSlice.list.empty() && _viewsSlice.left > 0) { - const auto done = viewsGotMoreCallback(); - stories.loadViewsSlice(_shown.story, std::nullopt, done); - } -} - -bool Controller::sliceViewsTo(PeerId offset) { - Expects(shown()); - - const auto user = shownUser(); - auto &stories = user->owner().stories(); - const auto maybeStory = stories.lookup(_shown); - if (!maybeStory || !user->isSelf()) { - _viewsSlice = {}; - return true; - } - const auto story = *maybeStory; - const auto &list = story->viewsList(); - const auto proj = [&](const Data::StoryView &single) { - return single.peer->id; - }; - const auto i = ranges::find(list, _viewsSlice.list.back()); - const auto add = (i != end(list)) ? int(end(list) - i - 1) : 0; - const auto j = ranges::find(_viewsSlice.list, offset, proj); - Assert(j != end(_viewsSlice.list)); - if (!add && (j + 1) == end(_viewsSlice.list)) { - const auto done = viewsGotMoreCallback(); - stories.loadViewsSlice(_shown.story, _viewsSlice.list.back(), done); - return false; - } - _viewsSlice.list.erase(begin(_viewsSlice.list), j + 1); - _viewsSlice.list.insert(end(_viewsSlice.list), i + 1, end(list)); - _viewsSlice.left -= add; - return true; + _viewsSlice = story->viewsList(); } void Controller::unfocusReply() { diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.h b/Telegram/SourceFiles/media/stories/media_stories_controller.h index 997590929..f21b97d5b 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.h +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.h @@ -104,11 +104,6 @@ struct Layout { friend inline bool operator==(Layout, Layout) = default; }; -struct ViewsSlice { - std::vector list; - int left = 0; -}; - class Controller final : public base::has_weak_ptr { public: explicit Controller(not_null delegate); @@ -158,7 +153,7 @@ public: void repaintSibling(not_null sibling); [[nodiscard]] SiblingView sibling(SiblingType type) const; - [[nodiscard]] ViewsSlice views(PeerId offset); + [[nodiscard]] const Data::StoryViews &views(int limit, bool initial); [[nodiscard]] rpl::producer<> moreViewsLoaded() const; void unfocusReply(); @@ -221,9 +216,8 @@ private: void moveFromShown(); void refreshViewsFromData(); - bool sliceViewsTo(PeerId offset); [[nodiscard]] auto viewsGotMoreCallback() - -> Fn)>; + -> Fn; [[nodiscard]] bool shown() const; [[nodiscard]] UserData *shownUser() const; @@ -285,7 +279,7 @@ private: int _cachedSourceIndex = -1; bool _showingUnreadSources = false; - ViewsSlice _viewsSlice; + Data::StoryViews _viewsSlice; rpl::event_stream<> _moreViewsLoaded; base::has_weak_ptr _viewsLoadGuard; diff --git a/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp b/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp index 806f21ebd..5a5567466 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp @@ -264,8 +264,8 @@ void RecentViews::showMenu() { return; } - const auto views = _controller->views(PeerId()); - if (views.list.empty() && !views.left) { + const auto views = _controller->views(kAddPerPage * 2, true); + if (views.list.empty() && !views.total) { return; } @@ -276,7 +276,7 @@ void RecentViews::showMenu() { st::storiesViewsMenu); auto count = 0; const auto added = std::min(int(views.list.size()), kAddPerPage); - const auto add = std::min(added + views.left, kAddPerPage); + const auto add = std::min(views.total, kAddPerPage); const auto now = QDateTime::currentDateTime(); for (const auto &entry : views.list) { addMenuRow(entry, now); @@ -393,23 +393,18 @@ void RecentViews::addMenuRowPlaceholder() { } void RecentViews::rebuildMenuTail() { - const auto offset = (_menuPlaceholderCount < _menuEntries.size()) - ? (end(_menuEntries) - _menuPlaceholderCount - 1)->peer->id - : PeerId(); - const auto views = _controller->views(offset); - if (views.list.empty()) { + const auto elements = _menuEntries.size() - _menuPlaceholderCount; + const auto views = _controller->views(elements + kAddPerPage, false); + if (views.list.size() <= elements) { return; } const auto now = QDateTime::currentDateTime(); const auto added = std::min( _menuPlaceholderCount + kAddPerPage, - int(views.list.size())); - auto add = added; - for (const auto &entry : views.list) { + int(views.list.size() - elements)); + for (auto i = elements, till = i + added; i != till; ++i) { + const auto &entry = views.list[i]; addMenuRow(entry, now); - if (!--add) { - break; - } } _menuEntriesCount = _menuEntriesCount.current() + added; } diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index c2e1a6481..a4f1092f9 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -386,6 +386,7 @@ updateStory#205a4133 user_id:long story:StoryItem = Update; updateReadStories#feb5345a user_id:long max_id:int = Update; updateStoryID#1bf335b9 id:int random_id:long = Update; updateStoriesStealthMode#2c084dc1 stealth_mode:StoriesStealthMode = Update; +updateSentStoryReaction#e3a73d20 user_id:long story_id:int reaction:Reaction = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -1527,11 +1528,11 @@ messagePeerVoteMultiple#4628f6e6 peer:Peer options:Vector date:int = Mess sponsoredWebPage#3db8ec63 flags:# url:string site_name:string photo:flags.0?Photo = SponsoredWebPage; -storyViews#d36760cf flags:# views_count:int recent_viewers:flags.0?Vector = StoryViews; +storyViews#c64c0b97 flags:# views_count:int reactions_count:int recent_viewers:flags.0?Vector = StoryViews; storyItemDeleted#51e6ee4f id:int = StoryItem; storyItemSkipped#ffadc913 flags:# close_friends:flags.8?true id:int date:int expire_date:int = StoryItem; -storyItem#945953ba flags:# pinned:flags.5?true public:flags.7?true close_friends:flags.8?true min:flags.9?true noforwards:flags.10?true edited:flags.11?true contacts:flags.12?true selected_contacts:flags.13?true id:int date:int expire_date:int caption:flags.0?string entities:flags.1?Vector media:MessageMedia media_areas:flags.14?Vector privacy:flags.2?Vector views:flags.3?StoryViews = StoryItem; +storyItem#44c457ce flags:# pinned:flags.5?true public:flags.7?true close_friends:flags.8?true min:flags.9?true noforwards:flags.10?true edited:flags.11?true contacts:flags.12?true selected_contacts:flags.13?true id:int date:int expire_date:int caption:flags.0?string entities:flags.1?Vector media:MessageMedia media_areas:flags.14?Vector privacy:flags.2?Vector views:flags.3?StoryViews sent_reaction:flags.15?Reaction = StoryItem; userStories#8611a200 flags:# user_id:long max_read_id:flags.0?int stories:Vector = UserStories; @@ -1542,9 +1543,9 @@ stories.stories#4fe57df1 count:int stories:Vector users:Vector stories.userStories#37a6ff5f stories:UserStories users:Vector = stories.UserStories; -storyView#d0b0a7de flags:# blocked:flags.0?true blocked_my_stories_from:flags.1?true user_id:long date:int = StoryView; +storyView#b0bdeac5 flags:# blocked:flags.0?true blocked_my_stories_from:flags.1?true user_id:long date:int reaction:flags.2?Reaction = StoryView; -stories.storyViewsList#fb3f77ac count:int views:Vector users:Vector = stories.StoryViewsList; +stories.storyViewsList#46e9b9ec flags:# count:int reactions_count:int views:Vector users:Vector next_offset:flags.0?string = stories.StoryViewsList; stories.storyViews#de9eed1d views:Vector users:Vector = stories.StoryViews; @@ -2111,8 +2112,9 @@ stories.toggleAllStoriesHidden#7c2557c4 hidden:Bool = Bool; stories.getAllReadUserStories#729c562c = Updates; stories.readStories#edc5105b user_id:InputUser max_id:int = Vector; stories.incrementStoryViews#22126127 user_id:InputUser id:Vector = Bool; -stories.getStoryViewsList#4b3b5e97 id:int offset_date:int offset_id:long limit:int = stories.StoryViewsList; +stories.getStoryViewsList#f95f61a4 flags:# just_contacts:flags.0?true q:flags.1?string id:int offset:string limit:int = stories.StoryViewsList; stories.getStoriesViews#9a75d6a6 id:Vector = stories.StoryViews; stories.exportStoryLink#16e443ce user_id:InputUser id:int = ExportedStoryLink; stories.report#c95be06a user_id:InputUser id:Vector reason:ReportReason message:string = Bool; -stories.activateStealthMode#11d7ddae flags:# past:flags.0?true future:flags.1?true = Bool; +stories.activateStealthMode#57bbd166 flags:# past:flags.0?true future:flags.1?true = Updates; +stories.sendReaction#49aaa9b3 flags:# add_to_recent:flags.0?true user_id:InputUser story_id:int reaction:Reaction = Updates;