mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-15 21:57:10 +02:00
Mark stories as read.
This commit is contained in:
parent
f323370752
commit
f814e401b9
7 changed files with 158 additions and 27 deletions
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<StoryMedia> 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<Session*> owner) : _owner(owner) {
|
||||
Stories::Stories(not_null<Session*> 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<PeerData*> 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
|
|
@ -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<UserData*> user;
|
||||
std::vector<StoryId> ids;
|
||||
base::flat_set<StoryId> ids;
|
||||
StoryId readTill = 0;
|
||||
int total = 0;
|
||||
|
||||
|
@ -113,6 +114,9 @@ public:
|
|||
FullStoryId id) const;
|
||||
void resolve(FullStoryId id, Fn<void()> 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*> story);
|
||||
|
||||
void sendMarkAsReadRequests();
|
||||
void sendMarkAsReadRequest(not_null<PeerData*> peer, StoryId tillId);
|
||||
|
||||
const not_null<Session*> _owner;
|
||||
base::flat_map<
|
||||
PeerId,
|
||||
|
@ -154,6 +161,10 @@ private:
|
|||
|
||||
mtpRequestId _loadMoreRequestId = 0;
|
||||
|
||||
base::flat_set<PeerId> _markReadPending;
|
||||
base::Timer _markReadTimer;
|
||||
base::flat_set<PeerId> _markReadRequests;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Data
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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<Data::StoriesList> &lists,
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue