From 010c666d2373453ebdd0e5bda96e0f77b3d7189c Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 27 Jun 2023 12:38:02 +0400 Subject: [PATCH] Preload next stories inside the media viewer. --- Telegram/SourceFiles/data/data_stories.cpp | 39 +- Telegram/SourceFiles/data/data_stories.h | 5 +- Telegram/SourceFiles/data/data_story.cpp | 515 ++++++++++++++++++ Telegram/SourceFiles/data/data_story.h | 158 ++++++ .../dialogs/dialogs_inner_widget.cpp | 3 + .../stories/media_stories_controller.cpp | 40 +- .../media/stories/media_stories_controller.h | 1 + 7 files changed, 749 insertions(+), 12 deletions(-) create mode 100644 Telegram/SourceFiles/data/data_story.cpp create mode 100644 Telegram/SourceFiles/data/data_story.h diff --git a/Telegram/SourceFiles/data/data_stories.cpp b/Telegram/SourceFiles/data/data_stories.cpp index 0c7586151b..86ee493e6e 100644 --- a/Telegram/SourceFiles/data/data_stories.cpp +++ b/Telegram/SourceFiles/data/data_stories.cpp @@ -637,7 +637,7 @@ void Stories::applyDeleted(FullStoryId id) { } } if (_preloading && _preloading->id() == id) { - preloadFinished(); + preloadFinished(id); } if (i->second.empty()) { _stories.erase(i); @@ -1294,6 +1294,24 @@ bool Stories::isQuitPrevent() { return true; } +void Stories::incrementPreloadingMainSources() { + Expects(_preloadingMainSourcesCounter >= 0); + + if (++_preloadingMainSourcesCounter == 1 + && rebuildPreloadSources(StorySourcesList::NotHidden)) { + continuePreloading(); + } +} + +void Stories::decrementPreloadingMainSources() { + Expects(_preloadingMainSourcesCounter > 0); + + if (!--_preloadingMainSourcesCounter + && rebuildPreloadSources(StorySourcesList::NotHidden)) { + continuePreloading(); + } +} + void Stories::incrementPreloadingHiddenSources() { Expects(_preloadingHiddenSourcesCounter >= 0); @@ -1330,8 +1348,10 @@ void Stories::preloadSourcesChanged(StorySourcesList list) { bool Stories::rebuildPreloadSources(StorySourcesList list) { const auto index = static_cast(list); - if (!_preloadingHiddenSourcesCounter - && list == StorySourcesList::Hidden) { + const auto &counter = (list == StorySourcesList::Hidden) + ? _preloadingHiddenSourcesCounter + : _preloadingMainSourcesCounter; + if (!counter) { return !base::take(_toPreloadSources[index]).empty(); } auto now = std::vector(); @@ -1401,15 +1421,16 @@ void Stories::startPreloading(not_null story) { Expects(!_preloaded.contains(story->fullId())); const auto id = story->fullId(); - _preloading = std::make_unique(story, [=] { - preloadFinished(true); + auto preloading = std::make_unique(story, [=] { + _preloading = nullptr; + preloadFinished(id, true); }); + if (!_preloaded.contains(id)) { + _preloading = std::move(preloading); + } } -void Stories::preloadFinished(bool markAsPreloaded) { - Expects(_preloading != nullptr); - - const auto id = base::take(_preloading)->id(); +void Stories::preloadFinished(FullStoryId id, bool markAsPreloaded) { for (auto &sources : _toPreloadSources) { sources.erase(ranges::remove(sources, id), end(sources)); } diff --git a/Telegram/SourceFiles/data/data_stories.h b/Telegram/SourceFiles/data/data_stories.h index 80d4a97327..6485351870 100644 --- a/Telegram/SourceFiles/data/data_stories.h +++ b/Telegram/SourceFiles/data/data_stories.h @@ -190,6 +190,8 @@ public: Ui::ReportReason reason, QString text); + void incrementPreloadingMainSources(); + void decrementPreloadingMainSources(); void incrementPreloadingHiddenSources(); void decrementPreloadingHiddenSources(); void setPreloadingInViewer(std::vector ids); @@ -245,7 +247,7 @@ private: [[nodiscard]] bool shouldContinuePreload(FullStoryId id) const; [[nodiscard]] FullStoryId nextPreloadId() const; void startPreloading(not_null story); - void preloadFinished(bool markAsPreloaded = false); + void preloadFinished(FullStoryId id, bool markAsPreloaded = false); const not_null _owner; std::unordered_map< @@ -310,6 +312,7 @@ private: std::vector _toPreloadViewer; std::unique_ptr _preloading; int _preloadingHiddenSourcesCounter = 0; + int _preloadingMainSourcesCounter = 0; }; diff --git a/Telegram/SourceFiles/data/data_story.cpp b/Telegram/SourceFiles/data/data_story.cpp new file mode 100644 index 0000000000..cf31749f42 --- /dev/null +++ b/Telegram/SourceFiles/data/data_story.cpp @@ -0,0 +1,515 @@ +/* +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 +*/ +#include "data/data_story.h" + +#include "base/unixtime.h" +#include "api/api_text_entities.h" +#include "data/data_document.h" +#include "data/data_file_origin.h" +#include "data/data_photo.h" +#include "data/data_photo_media.h" +#include "data/data_user.h" +#include "data/data_session.h" +#include "data/data_thread.h" +#include "lang/lang_keys.h" +#include "main/main_session.h" +#include "media/streaming/media_streaming_reader.h" +#include "storage/download_manager_mtproto.h" +#include "ui/text/text_utilities.h" + +namespace Data { + +class StoryPreload::LoadTask final : private Storage::DownloadMtprotoTask { +public: + LoadTask( + FullStoryId id, + not_null document, + Fn done); + ~LoadTask(); + +private: + bool readyToRequest() const override; + int64 takeNextRequestOffset() override; + bool feedPart(int64 offset, const QByteArray &bytes) override; + void cancelOnFail() override; + bool setWebFileSizeHook(int64 size) override; + + base::flat_map _parts; + Fn _done; + base::flat_set _requestedOffsets; + int64 _full = 0; + int _nextRequestOffset = 0; + bool _finished = false; + bool _failed = false; + +}; + +StoryPreload::LoadTask::LoadTask( + FullStoryId id, + not_null document, + Fn done) +: DownloadMtprotoTask( + &document->session().downloader(), + document->videoPreloadLocation(), + FileOriginStory(id.peer, id.story)) +, _done(std::move(done)) +, _full(document->size) { + const auto prefix = document->videoPreloadPrefix(); + Assert(prefix > 0 && prefix <= document->size); + const auto part = Storage::kDownloadPartSize; + const auto parts = (prefix + part - 1) / part; + for (auto i = 0; i != parts; ++i) { + _parts.emplace(i * part, QByteArray()); + } + addToQueue(); +} + +StoryPreload::LoadTask::~LoadTask() { + if (!_finished && !_failed) { + cancelAllRequests(); + } +} + +bool StoryPreload::LoadTask::readyToRequest() const { + const auto part = Storage::kDownloadPartSize; + return !_failed && (_nextRequestOffset < _parts.size() * part); +} + +int64 StoryPreload::LoadTask::takeNextRequestOffset() { + Expects(readyToRequest()); + + _requestedOffsets.emplace(_nextRequestOffset); + _nextRequestOffset += Storage::kDownloadPartSize; + return _requestedOffsets.back(); +} + +bool StoryPreload::LoadTask::feedPart( + int64 offset, + const QByteArray &bytes) { + Expects(offset < _parts.size() * Storage::kDownloadPartSize); + Expects(_requestedOffsets.contains(int(offset))); + Expects(bytes.size() <= Storage::kDownloadPartSize); + + const auto part = Storage::kDownloadPartSize; + const auto index = offset / part; + _requestedOffsets.remove(int(offset)); + _parts[offset] = bytes; + if ((_nextRequestOffset + part >= _parts.size() * part) + && _requestedOffsets.empty()) { + _finished = true; + removeFromQueue(); + auto result = Media::Streaming::SerializeComplexPartsMap(_parts); + if (result.size() == _full) { + // Make sure it is parsed as a complex map. + result.push_back(char(0)); + } + _done(result); + } + return true; +} + +void StoryPreload::LoadTask::cancelOnFail() { + _failed = true; + cancelAllRequests(); + _done({}); +} + +bool StoryPreload::LoadTask::setWebFileSizeHook(int64 size) { + _failed = true; + cancelAllRequests(); + _done({}); + return false; +} + +Story::Story( + StoryId id, + not_null peer, + StoryMedia media, + TimeId date, + TimeId expires) +: _id(id) +, _peer(peer) +, _media(std::move(media)) +, _date(date) +, _expires(expires) { +} + +Session &Story::owner() const { + return _peer->owner(); +} + +Main::Session &Story::session() const { + return _peer->session(); +} + +not_null Story::peer() const { + return _peer; +} + +StoryId Story::id() const { + return _id; +} + +bool Story::mine() const { + return _peer->isSelf(); +} + +StoryIdDates Story::idDates() const { + return { _id, _date, _expires }; +} + +FullStoryId Story::fullId() const { + return { _peer->id, _id }; +} + +TimeId Story::date() const { + return _date; +} + +TimeId Story::expires() const { + return _expires; +} + +bool Story::expired(TimeId now) const { + return _expires <= (now ? now : base::unixtime::now()); +} + +bool Story::unsupported() const { + return v::is_null(_media.data); +} + +const StoryMedia &Story::media() const { + return _media; +} + +PhotoData *Story::photo() const { + const auto result = std::get_if>(&_media.data); + return result ? result->get() : nullptr; +} + +DocumentData *Story::document() const { + const auto result = std::get_if>(&_media.data); + return result ? result->get() : nullptr; +} + +bool Story::hasReplyPreview() const { + return v::match(_media.data, [](not_null photo) { + return !photo->isNull(); + }, [](not_null document) { + return document->hasThumbnail(); + }, [](v::null_t) { + return false; + }); +} + +Image *Story::replyPreview() const { + return v::match(_media.data, [&](not_null photo) { + return photo->getReplyPreview( + Data::FileOriginStory(_peer->id, _id), + _peer, + false); + }, [&](not_null document) { + return document->getReplyPreview( + Data::FileOriginStory(_peer->id, _id), + _peer, + false); + }, [](v::null_t) { + return (Image*)nullptr; + }); +} + +TextWithEntities Story::inReplyText() const { + const auto type = tr::lng_in_dlg_story(tr::now); + return _caption.text.isEmpty() + ? Ui::Text::PlainLink(type) + : tr::lng_dialogs_text_media( + tr::now, + lt_media_part, + tr::lng_dialogs_text_media_wrapped( + tr::now, + lt_media, + Ui::Text::PlainLink(type), + Ui::Text::WithEntities), + lt_caption, + _caption, + Ui::Text::WithEntities); +} + +void Story::setPinned(bool pinned) { + _pinned = pinned; +} + +bool Story::pinned() const { + return _pinned; +} + +void Story::setIsPublic(bool isPublic) { + _isPublic = isPublic; +} + +bool Story::isPublic() const { + return _isPublic; +} + +void Story::setCloseFriends(bool closeFriends) { + _closeFriends = closeFriends; +} + +bool Story::closeFriends() const { + return _closeFriends; +} + +bool Story::canDownload() const { + return _peer->isSelf(); +} + +bool Story::canShare() const { + return isPublic() && (pinned() || !expired()); +} + +bool Story::canDelete() const { + return _peer->isSelf(); +} + +bool Story::canReport() const { + return !_peer->isSelf(); +} + +bool Story::hasDirectLink() const { + if (!_isPublic || (!_pinned && expired())) { + return false; + } + const auto user = _peer->asUser(); + return user && !user->username().isEmpty(); +} + +std::optional Story::errorTextForForward( + not_null to) const { + const auto peer = to->peer(); + const auto holdsPhoto = v::is>(_media.data); + const auto first = holdsPhoto + ? ChatRestriction::SendPhotos + : ChatRestriction::SendVideos; + const auto second = holdsPhoto + ? ChatRestriction::SendVideos + : ChatRestriction::SendPhotos; + if (const auto error = Data::RestrictionError(peer, first)) { + return *error; + } else if (const auto error = Data::RestrictionError(peer, second)) { + return *error; + } else if (!Data::CanSend(to, first, false) + || !Data::CanSend(to, second, false)) { + return tr::lng_forward_cant(tr::now); + } + return {}; +} + +void Story::setCaption(TextWithEntities &&caption) { + _caption = std::move(caption); +} + +const TextWithEntities &Story::caption() const { + static const auto empty = TextWithEntities(); + return unsupported() ? empty : _caption; +} + +void Story::setViewsData( + std::vector> recent, + int total) { + _recentViewers = std::move(recent); + _views = total; +} + +const std::vector> &Story::recentViewers() const { + return _recentViewers; +} + +const std::vector &Story::viewsList() const { + return _viewsList; +} + +int Story::views() const { + return _views; +} + +void Story::applyViewsSlice( + const std::optional &offset, + const std::vector &slice, + int 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)); + } + } + } + } +} + +bool Story::applyChanges(StoryMedia media, const MTPDstoryItem &data) { + const auto pinned = data.is_pinned(); + const auto isPublic = data.is_public(); + const auto closeFriends = data.is_close_friends(); + auto caption = TextWithEntities{ + data.vcaption().value_or_empty(), + Api::EntitiesFromMTP( + &owner().session(), + data.ventities().value_or_empty()), + }; + auto views = -1; + auto recent = std::vector>(); + if (!data.is_min()) { + if (const auto info = data.vviews()) { + views = info->data().vviews_count().v; + if (const auto list = info->data().vrecent_viewers()) { + recent.reserve(list->v.size()); + auto &owner = _peer->owner(); + for (const auto &id : list->v) { + recent.push_back(owner.peer(peerFromUser(id))); + } + } + } + } + + const auto changed = (_media != media) + || (_pinned != pinned) + || (_isPublic != isPublic) + || (_closeFriends != closeFriends) + || (_caption != caption) + || (views >= 0 && _views != views) + || (_recentViewers != recent); + if (!changed) { + return false; + } + _media = std::move(media); + _pinned = pinned; + _isPublic = isPublic; + _closeFriends = closeFriends; + _caption = std::move(caption); + if (views >= 0) { + _views = views; + } + _recentViewers = std::move(recent); + return true; +} + +StoryPreload::StoryPreload(not_null story, Fn done) +: _story(story) +, _done(std::move(done)) { + start(); +} + +StoryPreload::~StoryPreload() { + if (_photo) { + base::take(_photo)->owner()->cancel(); + } +} + +FullStoryId StoryPreload::id() const { + return _story->fullId(); +} + +not_null StoryPreload::story() const { + return _story; +} + +void StoryPreload::start() { + const auto origin = FileOriginStory( + _story->peer()->id, + _story->id()); + if (const auto photo = _story->photo()) { + _photo = photo->createMediaView(); + if (_photo->loaded()) { + callDone(); + } else { + _photo->automaticLoad(origin, _story->peer()); + photo->session().downloaderTaskFinished( + ) | rpl::filter([=] { + return _photo->loaded(); + }) | rpl::start_with_next([=] { callDone(); }, _lifetime); + } + } else if (const auto video = _story->document()) { + if (video->canBeStreamed(nullptr) && video->videoPreloadPrefix()) { + const auto key = video->bigFileBaseCacheKey(); + if (key) { + const auto weak = base::make_weak(this); + video->owner().cacheBigFile().get(key, [weak]( + const QByteArray &result) { + if (!result.isEmpty()) { + crl::on_main([weak] { + if (const auto strong = weak.get()) { + strong->callDone(); + } + }); + } else { + crl::on_main([weak] { + if (const auto strong = weak.get()) { + strong->load(); + } + }); + } + }); + } else { + callDone(); + } + } else { + callDone(); + } + } else { + callDone(); + } +} + +void StoryPreload::load() { + Expects(_story->document() != nullptr); + + const auto video = _story->document(); + const auto valid = video->videoPreloadLocation().valid(); + const auto prefix = video->videoPreloadPrefix(); + const auto key = video->bigFileBaseCacheKey(); + if (!valid || prefix <= 0 || prefix > video->size || !key) { + callDone(); + return; + } + _task = std::make_unique(id(), video, [=](QByteArray data) { + if (!data.isEmpty()) { + _story->owner().cacheBigFile().putIfEmpty( + key, + Storage::Cache::Database::TaggedValue(std::move(data), 0)); + } + callDone(); + }); +} + +void StoryPreload::callDone() { + if (const auto onstack = _done) { + onstack(); + } +} + +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_story.h b/Telegram/SourceFiles/data/data_story.h new file mode 100644 index 0000000000..1f4801efd5 --- /dev/null +++ b/Telegram/SourceFiles/data/data_story.h @@ -0,0 +1,158 @@ +/* +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/weak_ptr.h" + +class Image; +class PhotoData; +class DocumentData; + +namespace Main { +class Session; +} // namespace Main + +namespace Data { + +class Session; +class Thread; +class PhotoMedia; + +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 StoryMedia { + std::variant< + v::null_t, + not_null, + 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 unsupported() 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 setIsPublic(bool isPublic); + [[nodiscard]] bool isPublic() const; + void setCloseFriends(bool closeFriends); + [[nodiscard]] bool closeFriends() const; + + [[nodiscard]] bool canDownload() const; + [[nodiscard]] bool canShare() const; + [[nodiscard]] bool canDelete() const; + [[nodiscard]] bool canReport() const; + + [[nodiscard]] bool hasDirectLink() const; + [[nodiscard]] std::optional errorTextForForward( + not_null to) 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 : 1 = false; + bool _isPublic : 1 = false; + bool _closeFriends : 1 = false; + +}; + +class StoryPreload final : public base::has_weak_ptr { +public: + StoryPreload(not_null story, Fn done); + ~StoryPreload(); + + [[nodiscard]] FullStoryId id() const; + [[nodiscard]] not_null story() const; + +private: + class LoadTask; + + void start(); + void load(); + void callDone(); + + const not_null _story; + Fn _done; + + std::shared_ptr _photo; + std::unique_ptr _task; + rpl::lifetime _lifetime; + +}; + +} // namespace Data diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index c65f9a2a01..1ab020d36f 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -366,6 +366,8 @@ InnerWidget::InnerWidget( Data::StorySourcesList::NotHidden); }, lifetime()); + session().data().stories().incrementPreloadingMainSources(); + handleChatListEntryRefreshes(); refreshWithCollapsedRows(true); @@ -2426,6 +2428,7 @@ void InnerWidget::appendToFiltered(Key key) { } InnerWidget::~InnerWidget() { + session().data().stories().decrementPreloadingMainSources(); clearSearchResults(); } diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp index af4abbb4be..40cd9fc4a8 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -64,6 +64,8 @@ constexpr auto kSiblingUserpicSize = 0.3; constexpr auto kInnerHeightMultiplier = 1.6; constexpr auto kPreloadUsersCount = 3; constexpr auto kPreloadStoriesCount = 5; +constexpr auto kPreloadNextMediaCount = 3; +constexpr auto kPreloadPreviousMediaCount = 1; constexpr auto kMarkAsReadAfterSeconds = 1; constexpr auto kMarkAsReadAfterProgress = 0.2; @@ -637,9 +639,28 @@ void Controller::rebuildFromContext( } } } + preloadNext(); _slider->show({ .index = _index, .total = shownCount() }); } +void Controller::preloadNext() { + Expects(shown()); + + auto ids = std::vector(); + ids.reserve(kPreloadPreviousMediaCount + kPreloadNextMediaCount); + const auto user = shownUser(); + const auto count = shownCount(); + const auto till = std::min(_index + kPreloadNextMediaCount, count); + for (auto i = _index + 1; i != till; ++i) { + ids.push_back({ .peer = user->id, .story = shownId(i) }); + } + const auto from = std::max(_index - kPreloadPreviousMediaCount, 0); + for (auto i = _index; i != from;) { + ids.push_back({ .peer = user->id, .story = shownId(--i) }); + } + user->owner().stories().setPreloadingInViewer(std::move(ids)); +} + void Controller::checkMoveByDelta() { const auto index = _index + _waitingForDelta; if (_waitingForDelta && shown() && index >= 0 && index < shownCount()) { @@ -659,7 +680,17 @@ void Controller::show( rebuildFromContext(user, storyId); _contextLifetime.destroy(); - v::match(_context.data, [&](Data::StoriesContextSaved) { + const auto subscribeToSource = [&] { + stories.sourceChanged() | rpl::filter( + rpl::mappers::_1 == storyId.peer + ) | rpl::start_with_next([=] { + rebuildFromContext(user, storyId); + }, _contextLifetime); + }; + v::match(_context.data, [&](Data::StoriesContextSingle) { + }, [&](Data::StoriesContextPeer) { + subscribeToSource(); + }, [&](Data::StoriesContextSaved) { stories.savedChanged() | rpl::filter( rpl::mappers::_1 == storyId.peer ) | rpl::start_with_next([=] { @@ -672,7 +703,9 @@ void Controller::show( rebuildFromContext(user, storyId); checkMoveByDelta(); }, _contextLifetime); - }, [](const auto &) {}); + }, [&](Data::StorySourcesList) { + subscribeToSource(); + }); const auto guard = gsl::finally([&] { _paused = false; @@ -733,6 +766,9 @@ void Controller::show( checkWaitingFor(); } }, _sessionLifetime); + _sessionLifetime.add([=] { + session->data().stories().setPreloadingInViewer({}); + }); } stories.loadAround(storyId, context); diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.h b/Telegram/SourceFiles/media/stories/media_stories_controller.h index a680d7f684..89dda44d4f 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.h +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.h @@ -183,6 +183,7 @@ private: void rebuildFromContext(not_null user, FullStoryId storyId); void checkMoveByDelta(); void loadMoreToList(); + void preloadNext(); void rebuildCachedSourcesList( const std::vector &lists, int index);