From f814e401b9a7c5d3311bed76cd8d091f0e0ec24e Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 29 May 2023 19:09:36 +0400 Subject: [PATCH] Mark stories as read. --- Telegram/SourceFiles/core/application.cpp | 4 + Telegram/SourceFiles/data/data_stories.cpp | 100 ++++++++++++++++-- Telegram/SourceFiles/data/data_stories.h | 13 ++- .../dialogs/ui/dialogs_stories_list.cpp | 30 +++--- .../stories/media_stories_controller.cpp | 33 +++++- .../media/stories/media_stories_controller.h | 2 + .../window/window_session_controller.cpp | 3 +- 7 files changed, 158 insertions(+), 27 deletions(-) diff --git a/Telegram/SourceFiles/core/application.cpp b/Telegram/SourceFiles/core/application.cpp index 4fb01d12b..6ac6a20b8 100644 --- a/Telegram/SourceFiles/core/application.cpp +++ b/Telegram/SourceFiles/core/application.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_photo.h" #include "data/data_document.h" #include "data/data_session.h" +#include "data/data_stories.h" #include "data/data_user.h" #include "data/data_channel.h" #include "data/data_download_manager.h" @@ -1685,6 +1686,9 @@ bool Application::readyToQuit() { if (session->api().isQuitPrevent()) { prevented = true; } + if (session->data().stories().isQuitPrevent()) { + prevented = true; + } } } } diff --git a/Telegram/SourceFiles/data/data_stories.cpp b/Telegram/SourceFiles/data/data_stories.cpp index 6ff76e0f9..82b6c91b3 100644 --- a/Telegram/SourceFiles/data/data_stories.cpp +++ b/Telegram/SourceFiles/data/data_stories.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_text_entities.h" #include "apiwrap.h" +#include "core/application.h" #include "data/data_changes.h" #include "data/data_document.h" #include "data/data_file_origin.h" @@ -30,6 +31,7 @@ namespace { constexpr auto kMaxResolveTogether = 100; constexpr auto kIgnorePreloadAroundIfLoaded = 15; constexpr auto kPreloadAroundCount = 30; +constexpr auto kMarkAsReadDelay = 3 * crl::time(1000); using UpdateFlag = StoryUpdate::Flag; @@ -61,7 +63,7 @@ std::optional ParseMedia( } // namespace bool StoriesList::unread() const { - return !ids.empty() && readTill < ids.front(); + return !ids.empty() && readTill < ids.back(); } Story::Story( @@ -188,7 +190,9 @@ bool Story::applyChanges(StoryMedia media, const MTPDstoryItem &data) { return true; } -Stories::Stories(not_null owner) : _owner(owner) { +Stories::Stories(not_null owner) +: _owner(owner) +, _markReadTimer([=] { sendMarkAsReadRequests(); }) { } Stories::~Stories() { @@ -222,13 +226,13 @@ StoriesList Stories::parse(const MTPUserStories &stories) { for (const auto &story : list) { story.match([&](const MTPDstoryItem &data) { if (const auto story = parseAndApply(result.user, data)) { - result.ids.push_back(story->id()); + result.ids.emplace(story->id()); } else { applyDeleted({ peerFromUser(userId), data.vid().v }); --result.total; } }, [&](const MTPDstoryItemSkipped &data) { - result.ids.push_back(data.vid().v); + result.ids.emplace(data.vid().v); }, [&](const MTPDstoryItemDeleted &data) { applyDeleted({ peerFromUser(userId), data.vid().v }); --result.total; @@ -434,13 +438,13 @@ void Stories::applyDeleted(FullStoryId id) { return list.user->id; }); if (j != end(_all)) { - const auto till = ranges::remove(j->ids, id.story); - const auto removed = int(std::distance(till, end(j->ids))); - if (till != end(j->ids)) { - j->ids.erase(till, end(j->ids)); - j->total = std::max(j->total - removed, 0); + 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({}); } @@ -534,8 +538,8 @@ void Stories::applyChanges(StoriesList &&list) { if (i != end(_all)) { auto added = false; for (const auto id : list.ids) { - if (!ranges::contains(i->ids, id)) { - i->ids.push_back(id); + if (!i->ids.contains(id)) { + i->ids.emplace(id); ++i->total; added = true; } @@ -584,4 +588,78 @@ void Stories::loadAround(FullStoryId id) { } } +void Stories::markAsRead(FullStoryId id) { + const auto i = ranges::find(_all, id.peer, [](const StoriesList &list) { + return list.user->id; + }); + Assert(i != end(_all)); + if (i->readTill >= id.story) { + return; + } else if (!_markReadPending.contains(id.peer)) { + sendMarkAsReadRequests(); + } + _markReadPending.emplace(id.peer); + i->readTill = id.story; + _markReadTimer.callOnce(kMarkAsReadDelay); + _allChanged.fire({}); +} + +void Stories::sendMarkAsReadRequest( + not_null peer, + StoryId tillId) { + Expects(peer->isUser()); + + const auto peerId = peer->id; + _markReadRequests.emplace(peerId); + const auto finish = [=] { + _markReadRequests.remove(peerId); + if (!_markReadTimer.isActive() + && _markReadPending.contains(peerId)) { + sendMarkAsReadRequests(); + } + if (_markReadRequests.empty()) { + if (Core::Quitting()) { + LOG(("Stories doesn't prevent quit any more.")); + } + Core::App().quitPreventFinished(); + } + }; + + const auto api = &_owner->session().api(); + api->request(MTPstories_ReadStories( + peer->asUser()->inputUser, + MTP_int(tillId) + )).done(finish).fail(finish).send(); +} + +void Stories::sendMarkAsReadRequests() { + _markReadTimer.cancel(); + for (auto i = begin(_markReadPending); i != end(_markReadPending);) { + const auto peerId = *i; + if (_markReadRequests.contains(peerId)) { + ++i; + continue; + } + const auto j = ranges::find(_all, peerId, []( + const StoriesList &list) { + return list.user->id; + }); + if (j != end(_all)) { + sendMarkAsReadRequest(j->user, j->readTill); + } + i = _markReadPending.erase(i); + } +} + +bool Stories::isQuitPrevent() { + if (!_markReadPending.empty()) { + sendMarkAsReadRequests(); + } + if (_markReadRequests.empty()) { + return false; + } + LOG(("Stories prevents quit, marking as read...")); + return true; +} + } // namespace Data \ No newline at end of file diff --git a/Telegram/SourceFiles/data/data_stories.h b/Telegram/SourceFiles/data/data_stories.h index 92db99acd..44da5e7e3 100644 --- a/Telegram/SourceFiles/data/data_stories.h +++ b/Telegram/SourceFiles/data/data_stories.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "base/expected.h" +#include "base/timer.h" class Image; class PhotoData; @@ -70,7 +71,7 @@ private: struct StoriesList { not_null user; - std::vector ids; + base::flat_set ids; StoryId readTill = 0; int total = 0; @@ -113,6 +114,9 @@ public: FullStoryId id) const; void resolve(FullStoryId id, Fn done); + [[nodiscard]] bool isQuitPrevent(); + void markAsRead(FullStoryId id); + private: [[nodiscard]] StoriesList parse(const MTPUserStories &stories); [[nodiscard]] Story *parseAndApply( @@ -129,6 +133,9 @@ private: void applyDeleted(FullStoryId id); void removeDependencyStory(not_null story); + void sendMarkAsReadRequests(); + void sendMarkAsReadRequest(not_null peer, StoryId tillId); + const not_null _owner; base::flat_map< PeerId, @@ -154,6 +161,10 @@ private: mtpRequestId _loadMoreRequestId = 0; + base::flat_set _markReadPending; + base::Timer _markReadTimer; + base::flat_set _markReadRequests; + }; } // namespace Data diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp index 9e7370466..58625ddef 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp @@ -37,11 +37,15 @@ struct List::Layout { float64 userpicLeft = 0.; float64 photoLeft = 0.; float64 left = 0.; + float64 single = 0.; + int leftFull = 0; + int leftSmall = 0; + int singleFull = 0; + int singleSmall = 0; int startIndexSmall = 0; int endIndexSmall = 0; int startIndexFull = 0; int endIndexFull = 0; - int singleFull = 0; }; List::List( @@ -277,11 +281,15 @@ List::Layout List::computeLayout() const { .userpicLeft = userpicLeft, .photoLeft = photoLeft, .left = userpicLeft - photoLeft, + .single = lerp(st.shift, singleFull), + .leftFull = leftFull, + .leftSmall = leftSmall, + .singleFull = singleFull, + .singleSmall = st.shift, .startIndexSmall = startIndexSmall, .endIndexSmall = endIndexSmall, .startIndexFull = startIndexFull, .endIndexFull = endIndexFull, - .singleFull = singleFull, }; } @@ -296,8 +304,6 @@ void List::paintEvent(QPaintEvent *e) { auto &rendering = _data.empty() ? _hidingData : _data; const auto line = lerp(st.lineTwice, full.lineTwice) / 2.; const auto lineRead = lerp(st.lineReadTwice, full.lineReadTwice) / 2.; - const auto singleSmall = st.shift; - const auto single = lerp(singleSmall, layout.singleFull); const auto photoTopSmall = (st.height - st.photo) / 2.; const auto photoTop = lerp(photoTopSmall, full.photoTop); const auto photo = lerp(st.photo, full.photo); @@ -346,7 +352,7 @@ void List::paintEvent(QPaintEvent *e) { const auto full = (drawFull && indexFull < layout.endIndexFull) ? &rendering.items[indexFull] : nullptr; - const auto x = layout.left + single * index; + const auto x = layout.left + layout.single * index; return Single{ x, indexSmall, small, indexFull, full }; }; const auto hasUnread = [&](const Single &single) { @@ -697,19 +703,17 @@ void List::updateSelected() { const auto &full = st::dialogsStoriesFull; const auto p = mapFromGlobal(_lastMousePosition); const auto layout = computeLayout(); - const auto firstRightFull = full.left + layout.singleFull; - const auto firstRightSmall = st.left + const auto firstRightFull = layout.leftFull + + (layout.startIndexFull + 1) * layout.singleFull; + const auto firstRightSmall = layout.leftSmall + st.photoLeft + st.photo; - const auto stepFull = layout.singleFull; - const auto stepSmall = st.shift; const auto lastRightAddFull = 0; const auto lastRightAddSmall = st.photoLeft; const auto lerp = [&](float64 a, float64 b) { return a + (b - a) * layout.ratio; }; const auto firstRight = lerp(firstRightSmall, firstRightFull); - const auto step = lerp(stepSmall, stepFull); const auto lastRightAdd = lerp(lastRightAddSmall, lastRightAddFull); const auto activateFull = (layout.ratio >= 0.5); const auto startIndex = activateFull @@ -721,14 +725,16 @@ void List::updateSelected() { const auto x = p.x(); const auto infiniteIndex = (x < firstRight) ? 0 - : int(std::floor(((x - firstRight) / step) + 1)); + : int(std::floor(((x - firstRight) / layout.single) + 1)); const auto index = (endIndex == startIndex) ? -1 : (infiniteIndex == endIndex - startIndex && x < firstRight - + (endIndex - startIndex - 1) * step + + (endIndex - startIndex - 1) * layout.single + lastRightAdd) ? (infiniteIndex - 1) // Last small part should still be clickable. + : (startIndex + infiniteIndex >= endIndex) + ? -1 : infiniteIndex; const auto selected = (index < 0 || startIndex + index >= layout.itemsCount) diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp index 83a62b624..650ef7af2 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -41,6 +41,8 @@ constexpr auto kSiblingOutsidePart = 0.24; constexpr auto kSiblingUserpicSize = 0.3; constexpr auto kInnerHeightMultiplier = 1.6; constexpr auto kPreloadUsersCount = 3; +constexpr auto kMarkAsReadAfterSeconds = 1; +constexpr auto kMarkAsReadAfterProgress = 0.2; } // namespace @@ -360,7 +362,7 @@ void Controller::show( showSiblings(lists, index); const auto &list = lists[index]; - const auto id = list.ids[subindex]; + const auto id = *(begin(list.ids) + subindex); const auto storyId = FullStoryId{ .peer = list.user->id, .story = id, @@ -464,6 +466,7 @@ void Controller::updatePhotoPlayback(const Player::TrackState &state) { void Controller::updatePlayback(const Player::TrackState &state) { _slider->updatePlayback(state); updatePowerSaveBlocker(state); + maybeMarkAsRead(state); if (Player::IsStoppedAtEnd(state.state)) { if (!subjumpFor(1)) { _delegate->storiesClose(); @@ -471,6 +474,26 @@ void Controller::updatePlayback(const Player::TrackState &state) { } } +void Controller::maybeMarkAsRead(const Player::TrackState &state) { + const auto length = state.length; + const auto position = Player::IsStoppedAtEnd(state.state) + ? state.length + : Player::IsStoppedOrStopping(state.state) + ? 0 + : state.position; + if (position > state.frequency * kMarkAsReadAfterSeconds) { + if (position > kMarkAsReadAfterProgress * length) { + markAsRead(); + } + } +} + +void Controller::markAsRead() { + Expects(_list.has_value()); + + _list->user->owner().stories().markAsRead(_shown); +} + bool Controller::subjumpAvailable(int delta) const { const auto index = _index + delta; if (index < 0) { @@ -482,6 +505,9 @@ bool Controller::subjumpAvailable(int delta) const { } bool Controller::subjumpFor(int delta) { + if (delta > 0) { + markAsRead(); + } const auto index = _index + delta; if (index < 0) { if (_siblingLeft && _siblingLeft->shownId().valid()) { @@ -507,7 +533,7 @@ void Controller::subjumpTo(int index) { const auto id = FullStoryId{ .peer = _list->user->id, - .story = _list->ids[index] + .story = *(begin(_list->ids) + index) }; auto &stories = _list->user->owner().stories(); if (stories.lookup(id)) { @@ -554,6 +580,9 @@ bool Controller::jumpFor(int delta) { return true; } } else if (delta == 1) { + if (_list && _index + 1 >= _list->total) { + markAsRead(); + } if (const auto right = _siblingRight.get()) { _delegate->storiesJumpTo( &right->peer()->session(), diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.h b/Telegram/SourceFiles/media/stories/media_stories_controller.h index 79bca280e..f381e51c1 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.h +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.h @@ -123,6 +123,8 @@ private: void updatePhotoPlayback(const Player::TrackState &state); void updatePlayback(const Player::TrackState &state); void updatePowerSaveBlocker(const Player::TrackState &state); + void maybeMarkAsRead(const Player::TrackState &state); + void markAsRead(); void showSiblings( const std::vector &lists, diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 81cb18923..b30e9dd13 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -2486,7 +2486,8 @@ void SessionController::openPeerStories(PeerId peerId) { return list.user->id; }); if (i != end(all) && !i->ids.empty()) { - openPeerStory(i->user, i->ids.front()); + const auto j = i->ids.lower_bound(i->readTill + 1); + openPeerStory(i->user, j != i->ids.end() ? *j : i->ids.front()); } }