Track and load ids of expired mine stories.
This commit is contained in:
parent
aba84a6010
commit
8eac04cb90
|
@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "data/data_stories.h"
|
#include "data/data_stories.h"
|
||||||
|
|
||||||
|
#include "base/unixtime.h"
|
||||||
#include "api/api_text_entities.h"
|
#include "api/api_text_entities.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
|
@ -32,6 +33,8 @@ constexpr auto kMaxResolveTogether = 100;
|
||||||
constexpr auto kIgnorePreloadAroundIfLoaded = 15;
|
constexpr auto kIgnorePreloadAroundIfLoaded = 15;
|
||||||
constexpr auto kPreloadAroundCount = 30;
|
constexpr auto kPreloadAroundCount = 30;
|
||||||
constexpr auto kMarkAsReadDelay = 3 * crl::time(1000);
|
constexpr auto kMarkAsReadDelay = 3 * crl::time(1000);
|
||||||
|
constexpr auto kExpiredMineFirstPerPage = 30;
|
||||||
|
constexpr auto kExpiredMinePerPage = 100;
|
||||||
|
|
||||||
using UpdateFlag = StoryUpdate::Flag;
|
using UpdateFlag = StoryUpdate::Flag;
|
||||||
|
|
||||||
|
@ -80,11 +83,13 @@ Story::Story(
|
||||||
StoryId id,
|
StoryId id,
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
StoryMedia media,
|
StoryMedia media,
|
||||||
TimeId date)
|
TimeId date,
|
||||||
|
TimeId expires)
|
||||||
: _id(id)
|
: _id(id)
|
||||||
, _peer(peer)
|
, _peer(peer)
|
||||||
, _media(std::move(media))
|
, _media(std::move(media))
|
||||||
, _date(date) {
|
, _date(date)
|
||||||
|
, _expires(expires) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Session &Story::owner() const {
|
Session &Story::owner() const {
|
||||||
|
@ -103,8 +108,12 @@ StoryId Story::id() const {
|
||||||
return _id;
|
return _id;
|
||||||
}
|
}
|
||||||
|
|
||||||
StoryIdDate Story::idDate() const {
|
bool Story::mine() const {
|
||||||
return { _id, _date };
|
return _peer->isSelf();
|
||||||
|
}
|
||||||
|
|
||||||
|
StoryIdDates Story::idDates() const {
|
||||||
|
return { _id, _date, _expires };
|
||||||
}
|
}
|
||||||
|
|
||||||
FullStoryId Story::fullId() const {
|
FullStoryId Story::fullId() const {
|
||||||
|
@ -115,6 +124,14 @@ TimeId Story::date() const {
|
||||||
return _date;
|
return _date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TimeId Story::expires() const {
|
||||||
|
return _expires;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Story::expired(TimeId now) const {
|
||||||
|
return _expires <= (now ? now : base::unixtime::now());
|
||||||
|
}
|
||||||
|
|
||||||
const StoryMedia &Story::media() const {
|
const StoryMedia &Story::media() const {
|
||||||
return _media;
|
return _media;
|
||||||
}
|
}
|
||||||
|
@ -276,6 +293,7 @@ bool Story::applyChanges(StoryMedia media, const MTPDstoryItem &data) {
|
||||||
|
|
||||||
Stories::Stories(not_null<Session*> owner)
|
Stories::Stories(not_null<Session*> owner)
|
||||||
: _owner(owner)
|
: _owner(owner)
|
||||||
|
, _expireTimer([=] { processExpired(); })
|
||||||
, _markReadTimer([=] { sendMarkAsReadRequests(); }) {
|
, _markReadTimer([=] { sendMarkAsReadRequests(); }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -293,21 +311,27 @@ Main::Session &Stories::session() const {
|
||||||
void Stories::apply(const MTPDupdateStory &data) {
|
void Stories::apply(const MTPDupdateStory &data) {
|
||||||
const auto peerId = peerFromUser(data.vuser_id());
|
const auto peerId = peerFromUser(data.vuser_id());
|
||||||
const auto user = not_null(_owner->peer(peerId)->asUser());
|
const auto user = not_null(_owner->peer(peerId)->asUser());
|
||||||
const auto idDate = parseAndApply(user, data.vstory());
|
const auto now = base::unixtime::now();
|
||||||
if (!idDate) {
|
const auto idDates = parseAndApply(user, data.vstory(), now);
|
||||||
|
if (!idDates) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto expired = (idDates.expires <= now);
|
||||||
|
if (expired) {
|
||||||
|
applyExpired({ peerId, idDates.id });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto i = _all.find(peerId);
|
const auto i = _all.find(peerId);
|
||||||
if (i == end(_all)) {
|
if (i == end(_all)) {
|
||||||
requestUserStories(user);
|
requestUserStories(user);
|
||||||
return;
|
return;
|
||||||
} else if (i->second.ids.contains(idDate)) {
|
} else if (i->second.ids.contains(idDates)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto was = i->second.info();
|
const auto wasInfo = i->second.info();
|
||||||
i->second.ids.emplace(idDate);
|
i->second.ids.emplace(idDates);
|
||||||
const auto now = i->second.info();
|
const auto nowInfo = i->second.info();
|
||||||
if (was == now) {
|
if (wasInfo == nowInfo) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto refreshInList = [&](StorySourcesList list) {
|
const auto refreshInList = [&](StorySourcesList list) {
|
||||||
|
@ -317,7 +341,7 @@ void Stories::apply(const MTPDupdateStory &data) {
|
||||||
peerId,
|
peerId,
|
||||||
&StoriesSourceInfo::id);
|
&StoriesSourceInfo::id);
|
||||||
if (i != end(sources)) {
|
if (i != end(sources)) {
|
||||||
*i = now;
|
*i = nowInfo;
|
||||||
sort(list);
|
sort(list);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -342,7 +366,61 @@ void Stories::requestUserStories(not_null<UserData*> user) {
|
||||||
_requestingUserStories.remove(user);
|
_requestingUserStories.remove(user);
|
||||||
applyDeletedFromSources(user->id, StorySourcesList::All);
|
applyDeletedFromSources(user->id, StorySourcesList::All);
|
||||||
}).send();
|
}).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stories::registerExpiring(TimeId expires, FullStoryId id) {
|
||||||
|
for (auto i = _expiring.findFirst(expires)
|
||||||
|
; (i != end(_expiring)) && (i->first == expires)
|
||||||
|
; ++i) {
|
||||||
|
if (i->second == id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const auto reschedule = _expiring.empty()
|
||||||
|
|| (_expiring.front().first > expires);
|
||||||
|
_expiring.emplace(expires, id);
|
||||||
|
if (reschedule) {
|
||||||
|
scheduleExpireTimer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stories::scheduleExpireTimer() {
|
||||||
|
if (_expireSchedulePosted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_expireSchedulePosted = true;
|
||||||
|
crl::on_main(this, [=] {
|
||||||
|
if (!_expireSchedulePosted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_expireSchedulePosted = false;
|
||||||
|
if (_expiring.empty()) {
|
||||||
|
_expireTimer.cancel();
|
||||||
|
} else {
|
||||||
|
const auto nearest = _expiring.front().first;
|
||||||
|
const auto now = base::unixtime::now();
|
||||||
|
const auto delay = (nearest > now)
|
||||||
|
? (nearest - now)
|
||||||
|
: 0;
|
||||||
|
_expireTimer.callOnce(delay * crl::time(1000));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stories::processExpired() {
|
||||||
|
const auto now = base::unixtime::now();
|
||||||
|
auto expired = base::flat_set<FullStoryId>();
|
||||||
|
auto i = begin(_expiring);
|
||||||
|
for (; i != end(_expiring) && i->first <= now; ++i) {
|
||||||
|
expired.emplace(i->second);
|
||||||
|
}
|
||||||
|
_expiring.erase(begin(_expiring), i);
|
||||||
|
for (const auto &id : expired) {
|
||||||
|
applyExpired(id);
|
||||||
|
}
|
||||||
|
if (!_expiring.empty()) {
|
||||||
|
scheduleExpireTimer();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Stories::parseAndApply(const MTPUserStories &stories) {
|
void Stories::parseAndApply(const MTPUserStories &stories) {
|
||||||
|
@ -357,9 +435,10 @@ void Stories::parseAndApply(const MTPUserStories &stories) {
|
||||||
.hidden = user->hasStoriesHidden(),
|
.hidden = user->hasStoriesHidden(),
|
||||||
};
|
};
|
||||||
const auto &list = data.vstories().v;
|
const auto &list = data.vstories().v;
|
||||||
|
const auto now = base::unixtime::now();
|
||||||
result.ids.reserve(list.size());
|
result.ids.reserve(list.size());
|
||||||
for (const auto &story : list) {
|
for (const auto &story : list) {
|
||||||
if (const auto id = parseAndApply(result.user, story)) {
|
if (const auto id = parseAndApply(result.user, story, now)) {
|
||||||
result.ids.emplace(id);
|
result.ids.emplace(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -391,21 +470,31 @@ void Stories::parseAndApply(const MTPUserStories &stories) {
|
||||||
}
|
}
|
||||||
sort(list);
|
sort(list);
|
||||||
};
|
};
|
||||||
add(StorySourcesList::All);
|
if (result.user->isContact()) {
|
||||||
if (result.user->hasStoriesHidden()) {
|
add(StorySourcesList::All);
|
||||||
applyDeletedFromSources(peerId, StorySourcesList::NotHidden);
|
if (result.user->hasStoriesHidden()) {
|
||||||
|
applyDeletedFromSources(peerId, StorySourcesList::NotHidden);
|
||||||
|
} else {
|
||||||
|
add(StorySourcesList::NotHidden);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
add(StorySourcesList::NotHidden);
|
applyDeletedFromSources(peerId, StorySourcesList::All);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Story *Stories::parseAndApply(
|
Story *Stories::parseAndApply(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
const MTPDstoryItem &data) {
|
const MTPDstoryItem &data,
|
||||||
|
TimeId now) {
|
||||||
const auto media = ParseMedia(_owner, data.vmedia());
|
const auto media = ParseMedia(_owner, data.vmedia());
|
||||||
if (!media) {
|
if (!media) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
const auto expires = data.vexpire_date().v;
|
||||||
|
const auto expired = (expires >= now);
|
||||||
|
if (expired && !data.is_pinned() && !peer->isSelf()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
const auto id = data.vid().v;
|
const auto id = data.vid().v;
|
||||||
auto &stories = _stories[peer->id];
|
auto &stories = _stories[peer->id];
|
||||||
const auto i = stories.find(id);
|
const auto i = stories.find(id);
|
||||||
|
@ -421,25 +510,51 @@ Story *Stories::parseAndApply(
|
||||||
id,
|
id,
|
||||||
peer,
|
peer,
|
||||||
StoryMedia{ *media },
|
StoryMedia{ *media },
|
||||||
data.vdate().v)).first->second.get();
|
data.vdate().v,
|
||||||
|
data.vexpire_date().v)).first->second.get();
|
||||||
result->applyChanges(*media, data);
|
result->applyChanges(*media, data);
|
||||||
|
|
||||||
|
if (expired) {
|
||||||
|
_expiring.remove(expires, result->fullId());
|
||||||
|
applyExpired(result->fullId());
|
||||||
|
} else {
|
||||||
|
registerExpiring(expires, result->fullId());
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
StoryIdDate Stories::parseAndApply(
|
StoryIdDates Stories::parseAndApply(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
const MTPstoryItem &story) {
|
const MTPstoryItem &story,
|
||||||
|
TimeId now) {
|
||||||
return story.match([&](const MTPDstoryItem &data) {
|
return story.match([&](const MTPDstoryItem &data) {
|
||||||
if (const auto story = parseAndApply(peer, data)) {
|
if (const auto story = parseAndApply(peer, data, now)) {
|
||||||
return story->idDate();
|
return story->idDates();
|
||||||
}
|
}
|
||||||
applyDeleted({ peer->id, data.vid().v });
|
applyDeleted({ peer->id, data.vid().v });
|
||||||
return StoryIdDate();
|
return StoryIdDates();
|
||||||
}, [&](const MTPDstoryItemSkipped &data) {
|
}, [&](const MTPDstoryItemSkipped &data) {
|
||||||
return StoryIdDate{ data.vid().v, data.vdate().v };
|
const auto expires = data.vexpire_date().v;
|
||||||
|
const auto expired = (expires >= now);
|
||||||
|
const auto fullId = FullStoryId{ peer->id, data.vid().v };
|
||||||
|
if (!expired) {
|
||||||
|
registerExpiring(expires, fullId);
|
||||||
|
} else if (!peer->isSelf()) {
|
||||||
|
applyDeleted(fullId);
|
||||||
|
return StoryIdDates();
|
||||||
|
} else {
|
||||||
|
_expiring.remove(expires, fullId);
|
||||||
|
applyExpired(fullId);
|
||||||
|
}
|
||||||
|
return StoryIdDates{
|
||||||
|
data.vid().v,
|
||||||
|
data.vdate().v,
|
||||||
|
data.vexpire_date().v,
|
||||||
|
};
|
||||||
}, [&](const MTPDstoryItemDeleted &data) {
|
}, [&](const MTPDstoryItemDeleted &data) {
|
||||||
applyDeleted({ peer->id, data.vid().v });
|
applyDeleted({ peer->id, data.vid().v });
|
||||||
return StoryIdDate();
|
return StoryIdDates();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -571,9 +686,10 @@ void Stories::sendResolveRequests() {
|
||||||
void Stories::processResolvedStories(
|
void Stories::processResolvedStories(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
const QVector<MTPStoryItem> &list) {
|
const QVector<MTPStoryItem> &list) {
|
||||||
|
const auto now = base::unixtime::now();
|
||||||
for (const auto &item : list) {
|
for (const auto &item : list) {
|
||||||
item.match([&](const MTPDstoryItem &data) {
|
item.match([&](const MTPDstoryItem &data) {
|
||||||
if (!parseAndApply(peer, data)) {
|
if (!parseAndApply(peer, data, now)) {
|
||||||
applyDeleted({ peer->id, data.vid().v });
|
applyDeleted({ peer->id, data.vid().v });
|
||||||
}
|
}
|
||||||
}, [&](const MTPDstoryItemSkipped &data) {
|
}, [&](const MTPDstoryItemSkipped &data) {
|
||||||
|
@ -595,6 +711,48 @@ void Stories::finalizeResolve(FullStoryId id) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Stories::applyDeleted(FullStoryId id) {
|
void Stories::applyDeleted(FullStoryId id) {
|
||||||
|
applyRemovedFromActive(id);
|
||||||
|
|
||||||
|
_deleted.emplace(id);
|
||||||
|
const auto j = _stories.find(id.peer);
|
||||||
|
if (j != end(_stories)) {
|
||||||
|
const auto k = j->second.find(id.story);
|
||||||
|
if (k != end(j->second)) {
|
||||||
|
auto story = std::move(k->second);
|
||||||
|
_expiring.remove(story->expires(), story->fullId());
|
||||||
|
j->second.erase(k);
|
||||||
|
session().changes().storyUpdated(
|
||||||
|
story.get(),
|
||||||
|
UpdateFlag::Destroyed);
|
||||||
|
removeDependencyStory(story.get());
|
||||||
|
if (j->second.empty()) {
|
||||||
|
_stories.erase(j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stories::applyExpired(FullStoryId id) {
|
||||||
|
if (const auto maybeStory = lookup(id)) {
|
||||||
|
const auto story = *maybeStory;
|
||||||
|
if (story->peer()->isSelf()) {
|
||||||
|
addToExpiredMine(story);
|
||||||
|
} else if (!story->pinned()) {
|
||||||
|
applyDeleted(id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
applyRemovedFromActive(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stories::addToExpiredMine(not_null<Story*> story) {
|
||||||
|
const auto added = _expiredMine.emplace(story->id()).second;
|
||||||
|
if (added && _expiredMineTotal >= 0) {
|
||||||
|
++_expiredMineTotal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stories::applyRemovedFromActive(FullStoryId id) {
|
||||||
const auto removeFromList = [&](StorySourcesList list) {
|
const auto removeFromList = [&](StorySourcesList list) {
|
||||||
const auto index = static_cast<int>(list);
|
const auto index = static_cast<int>(list);
|
||||||
auto &sources = _sources[index];
|
auto &sources = _sources[index];
|
||||||
|
@ -609,7 +767,7 @@ void Stories::applyDeleted(FullStoryId id) {
|
||||||
};
|
};
|
||||||
const auto i = _all.find(id.peer);
|
const auto i = _all.find(id.peer);
|
||||||
if (i != end(_all)) {
|
if (i != end(_all)) {
|
||||||
const auto j = i->second.ids.lower_bound(StoryIdDate{ id.story });
|
const auto j = i->second.ids.lower_bound(StoryIdDates{ id.story });
|
||||||
if (j != end(i->second.ids) && j->id == id.story) {
|
if (j != end(i->second.ids) && j->id == id.story) {
|
||||||
i->second.ids.erase(j);
|
i->second.ids.erase(j);
|
||||||
if (i->second.ids.empty()) {
|
if (i->second.ids.empty()) {
|
||||||
|
@ -619,22 +777,6 @@ void Stories::applyDeleted(FullStoryId id) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_deleted.emplace(id);
|
|
||||||
const auto j = _stories.find(id.peer);
|
|
||||||
if (j != end(_stories)) {
|
|
||||||
const auto k = j->second.find(id.story);
|
|
||||||
if (k != end(j->second)) {
|
|
||||||
auto story = std::move(k->second);
|
|
||||||
j->second.erase(k);
|
|
||||||
session().changes().storyUpdated(
|
|
||||||
story.get(),
|
|
||||||
UpdateFlag::Destroyed);
|
|
||||||
removeDependencyStory(story.get());
|
|
||||||
if (j->second.empty()) {
|
|
||||||
_stories.erase(j);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Stories::applyDeletedFromSources(PeerId id, StorySourcesList list) {
|
void Stories::applyDeletedFromSources(PeerId id, StorySourcesList list) {
|
||||||
|
@ -755,7 +897,7 @@ void Stories::loadAround(FullStoryId id, StoriesContext context) {
|
||||||
if (i == end(_all)) {
|
if (i == end(_all)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto j = i->second.ids.lower_bound(StoryIdDate{ id.story });
|
const auto j = i->second.ids.lower_bound(StoryIdDates{ id.story });
|
||||||
if (j == end(i->second.ids) || j->id != id.story) {
|
if (j == end(i->second.ids) || j->id != id.story) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -960,6 +1102,67 @@ void Stories::loadViewsSlice(
|
||||||
}).send();
|
}).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const base::flat_set<StoryId> &Stories::expiredMine() const {
|
||||||
|
return _expiredMine;
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<> Stories::expiredMineChanged() const {
|
||||||
|
return _expiredMineChanged.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
int Stories::expiredMineCount() const {
|
||||||
|
return std::max(_expiredMineTotal, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Stories::expiredMineCountKnown() const {
|
||||||
|
return _expiredMineTotal >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Stories::expiredMineLoaded() const {
|
||||||
|
return _expiredMineLoaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stories::expiredMineLoadMore() {
|
||||||
|
if (_expiredMineRequestId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto api = &_owner->session().api();
|
||||||
|
_expiredMineRequestId = api->request(MTPstories_GetExpiredStories(
|
||||||
|
MTP_int(_expiredMineLastId),
|
||||||
|
MTP_int(_expiredMineLastId
|
||||||
|
? kExpiredMinePerPage
|
||||||
|
: kExpiredMineFirstPerPage)
|
||||||
|
)).done([=](const MTPstories_Stories &result) {
|
||||||
|
_expiredMineRequestId = 0;
|
||||||
|
|
||||||
|
const auto &data = result.data();
|
||||||
|
_expiredMineTotal = std::max(
|
||||||
|
data.vcount().v,
|
||||||
|
int(_expiredMine.size()));
|
||||||
|
_expiredMineLoaded = data.vstories().v.empty();
|
||||||
|
const auto self = _owner->session().user();
|
||||||
|
const auto now = base::unixtime::now();
|
||||||
|
for (const auto &story : data.vstories().v) {
|
||||||
|
const auto id = story.match([&](const auto &id) {
|
||||||
|
return id.vid().v;
|
||||||
|
});
|
||||||
|
_expiredMine.emplace(id);
|
||||||
|
_expiredMineLastId = id;
|
||||||
|
if (!parseAndApply(self, story, now)) {
|
||||||
|
_expiredMine.remove(id);
|
||||||
|
if (_expiredMineTotal > 0) {
|
||||||
|
--_expiredMineTotal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_expiredMineChanged.fire({});
|
||||||
|
}).fail([=] {
|
||||||
|
_expiredMineRequestId = 0;
|
||||||
|
_expiredMineLoaded = true;
|
||||||
|
_expiredMineTotal = int(_expiredMine.size());
|
||||||
|
}).send();
|
||||||
|
}
|
||||||
|
|
||||||
bool Stories::isQuitPrevent() {
|
bool Stories::isQuitPrevent() {
|
||||||
if (!_markReadPending.empty()) {
|
if (!_markReadPending.empty()) {
|
||||||
sendMarkAsReadRequests();
|
sendMarkAsReadRequests();
|
||||||
|
|
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
#include "base/expected.h"
|
#include "base/expected.h"
|
||||||
#include "base/timer.h"
|
#include "base/timer.h"
|
||||||
|
#include "base/weak_ptr.h"
|
||||||
|
|
||||||
class Image;
|
class Image;
|
||||||
class PhotoData;
|
class PhotoData;
|
||||||
|
@ -22,9 +23,10 @@ namespace Data {
|
||||||
|
|
||||||
class Session;
|
class Session;
|
||||||
|
|
||||||
struct StoryIdDate {
|
struct StoryIdDates {
|
||||||
StoryId id = 0;
|
StoryId id = 0;
|
||||||
TimeId date = 0;
|
TimeId date = 0;
|
||||||
|
TimeId expires = 0;
|
||||||
|
|
||||||
[[nodiscard]] bool valid() const {
|
[[nodiscard]] bool valid() const {
|
||||||
return id != 0;
|
return id != 0;
|
||||||
|
@ -33,8 +35,8 @@ struct StoryIdDate {
|
||||||
return valid();
|
return valid();
|
||||||
}
|
}
|
||||||
|
|
||||||
friend inline auto operator<=>(StoryIdDate, StoryIdDate) = default;
|
friend inline auto operator<=>(StoryIdDates, StoryIdDates) = default;
|
||||||
friend inline bool operator==(StoryIdDate, StoryIdDate) = default;
|
friend inline bool operator==(StoryIdDates, StoryIdDates) = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct StoryMedia {
|
struct StoryMedia {
|
||||||
|
@ -56,16 +58,20 @@ public:
|
||||||
StoryId id,
|
StoryId id,
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
StoryMedia media,
|
StoryMedia media,
|
||||||
TimeId date);
|
TimeId date,
|
||||||
|
TimeId expires);
|
||||||
|
|
||||||
[[nodiscard]] Session &owner() const;
|
[[nodiscard]] Session &owner() const;
|
||||||
[[nodiscard]] Main::Session &session() const;
|
[[nodiscard]] Main::Session &session() const;
|
||||||
[[nodiscard]] not_null<PeerData*> peer() const;
|
[[nodiscard]] not_null<PeerData*> peer() const;
|
||||||
|
|
||||||
[[nodiscard]] StoryId id() const;
|
[[nodiscard]] StoryId id() const;
|
||||||
[[nodiscard]] StoryIdDate idDate() const;
|
[[nodiscard]] bool mine() const;
|
||||||
|
[[nodiscard]] StoryIdDates idDates() const;
|
||||||
[[nodiscard]] FullStoryId fullId() const;
|
[[nodiscard]] FullStoryId fullId() const;
|
||||||
[[nodiscard]] TimeId date() const;
|
[[nodiscard]] TimeId date() const;
|
||||||
|
[[nodiscard]] TimeId expires() const;
|
||||||
|
[[nodiscard]] bool expired(TimeId now = 0) const;
|
||||||
[[nodiscard]] const StoryMedia &media() const;
|
[[nodiscard]] const StoryMedia &media() const;
|
||||||
[[nodiscard]] PhotoData *photo() const;
|
[[nodiscard]] PhotoData *photo() const;
|
||||||
[[nodiscard]] DocumentData *document() const;
|
[[nodiscard]] DocumentData *document() const;
|
||||||
|
@ -101,6 +107,7 @@ private:
|
||||||
std::vector<StoryView> _viewsList;
|
std::vector<StoryView> _viewsList;
|
||||||
int _views = 0;
|
int _views = 0;
|
||||||
const TimeId _date = 0;
|
const TimeId _date = 0;
|
||||||
|
const TimeId _expires = 0;
|
||||||
bool _pinned = false;
|
bool _pinned = false;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -119,7 +126,7 @@ struct StoriesSourceInfo {
|
||||||
|
|
||||||
struct StoriesSource {
|
struct StoriesSource {
|
||||||
not_null<UserData*> user;
|
not_null<UserData*> user;
|
||||||
base::flat_set<StoryIdDate> ids;
|
base::flat_set<StoryIdDates> ids;
|
||||||
StoryId readTill = 0;
|
StoryId readTill = 0;
|
||||||
bool hidden = false;
|
bool hidden = false;
|
||||||
|
|
||||||
|
@ -167,7 +174,7 @@ struct StoriesContext {
|
||||||
|
|
||||||
inline constexpr auto kStorySourcesListCount = 2;
|
inline constexpr auto kStorySourcesListCount = 2;
|
||||||
|
|
||||||
class Stories final {
|
class Stories final : public base::has_weak_ptr {
|
||||||
public:
|
public:
|
||||||
explicit Stories(not_null<Session*> owner);
|
explicit Stories(not_null<Session*> owner);
|
||||||
~Stories();
|
~Stories();
|
||||||
|
@ -210,14 +217,23 @@ public:
|
||||||
std::optional<StoryView> offset,
|
std::optional<StoryView> offset,
|
||||||
Fn<void(std::vector<StoryView>)> done);
|
Fn<void(std::vector<StoryView>)> done);
|
||||||
|
|
||||||
|
[[nodiscard]] const base::flat_set<StoryId> &expiredMine() const;
|
||||||
|
[[nodiscard]] rpl::producer<> expiredMineChanged() const;
|
||||||
|
[[nodiscard]] int expiredMineCount() const;
|
||||||
|
[[nodiscard]] bool expiredMineCountKnown() const;
|
||||||
|
[[nodiscard]] bool expiredMineLoaded() const;
|
||||||
|
[[nodiscard]] void expiredMineLoadMore();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void parseAndApply(const MTPUserStories &stories);
|
void parseAndApply(const MTPUserStories &stories);
|
||||||
[[nodiscard]] Story *parseAndApply(
|
[[nodiscard]] Story *parseAndApply(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
const MTPDstoryItem &data);
|
const MTPDstoryItem &data,
|
||||||
StoryIdDate parseAndApply(
|
TimeId now);
|
||||||
|
StoryIdDates parseAndApply(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
const MTPstoryItem &story);
|
const MTPstoryItem &story,
|
||||||
|
TimeId now);
|
||||||
void processResolvedStories(
|
void processResolvedStories(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
const QVector<MTPStoryItem> &list);
|
const QVector<MTPStoryItem> &list);
|
||||||
|
@ -225,20 +241,30 @@ private:
|
||||||
void finalizeResolve(FullStoryId id);
|
void finalizeResolve(FullStoryId id);
|
||||||
|
|
||||||
void applyDeleted(FullStoryId id);
|
void applyDeleted(FullStoryId id);
|
||||||
|
void applyExpired(FullStoryId id);
|
||||||
|
void applyRemovedFromActive(FullStoryId id);
|
||||||
void applyDeletedFromSources(PeerId id, StorySourcesList list);
|
void applyDeletedFromSources(PeerId id, StorySourcesList list);
|
||||||
void removeDependencyStory(not_null<Story*> story);
|
void removeDependencyStory(not_null<Story*> story);
|
||||||
void sort(StorySourcesList list);
|
void sort(StorySourcesList list);
|
||||||
|
|
||||||
|
void addToExpiredMine(not_null<Story*> story);
|
||||||
|
|
||||||
void sendMarkAsReadRequests();
|
void sendMarkAsReadRequests();
|
||||||
void sendMarkAsReadRequest(not_null<PeerData*> peer, StoryId tillId);
|
void sendMarkAsReadRequest(not_null<PeerData*> peer, StoryId tillId);
|
||||||
|
|
||||||
void requestUserStories(not_null<UserData*> user);
|
void requestUserStories(not_null<UserData*> user);
|
||||||
|
void registerExpiring(TimeId expires, FullStoryId id);
|
||||||
|
void scheduleExpireTimer();
|
||||||
|
void processExpired();
|
||||||
|
|
||||||
const not_null<Session*> _owner;
|
const not_null<Session*> _owner;
|
||||||
base::flat_map<
|
base::flat_map<
|
||||||
PeerId,
|
PeerId,
|
||||||
base::flat_map<StoryId, std::unique_ptr<Story>>> _stories;
|
base::flat_map<StoryId, std::unique_ptr<Story>>> _stories;
|
||||||
|
base::flat_multi_map<TimeId, FullStoryId> _expiring;
|
||||||
base::flat_set<FullStoryId> _deleted;
|
base::flat_set<FullStoryId> _deleted;
|
||||||
|
base::Timer _expireTimer;
|
||||||
|
bool _expireSchedulePosted = false;
|
||||||
|
|
||||||
base::flat_map<
|
base::flat_map<
|
||||||
PeerId,
|
PeerId,
|
||||||
|
@ -261,6 +287,13 @@ private:
|
||||||
|
|
||||||
rpl::event_stream<PeerId> _itemsChanged;
|
rpl::event_stream<PeerId> _itemsChanged;
|
||||||
|
|
||||||
|
base::flat_set<StoryId> _expiredMine;
|
||||||
|
int _expiredMineTotal = -1;
|
||||||
|
StoryId _expiredMineLastId = 0;
|
||||||
|
bool _expiredMineLoaded = false;
|
||||||
|
rpl::event_stream<> _expiredMineChanged;
|
||||||
|
mtpRequestId _expiredMineRequestId = 0;
|
||||||
|
|
||||||
base::flat_set<PeerId> _markReadPending;
|
base::flat_set<PeerId> _markReadPending;
|
||||||
base::Timer _markReadTimer;
|
base::Timer _markReadTimer;
|
||||||
base::flat_set<PeerId> _markReadRequests;
|
base::flat_set<PeerId> _markReadRequests;
|
||||||
|
|
|
@ -297,8 +297,10 @@ ClickHandlerPtr JumpToStoryClickHandler(
|
||||||
? separate->sessionController()
|
? separate->sessionController()
|
||||||
: peer->session().tryResolveWindow();
|
: peer->session().tryResolveWindow();
|
||||||
if (controller) {
|
if (controller) {
|
||||||
// #TODO stories decide context
|
controller->openPeerStory(
|
||||||
controller->openPeerStory(peer, storyId, {});
|
peer,
|
||||||
|
storyId,
|
||||||
|
{ Data::StoriesContextSingle() });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -422,14 +422,14 @@ void Controller::show(
|
||||||
stories.loadMore(list);
|
stories.loadMore(list);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const auto idDate = story->idDate();
|
const auto idDates = story->idDates();
|
||||||
if (!source) {
|
if (!source) {
|
||||||
return;
|
return;
|
||||||
} else if (source == &single) {
|
} else if (source == &single) {
|
||||||
single.ids.emplace(idDate);
|
single.ids.emplace(idDates);
|
||||||
_index = 0;
|
_index = 0;
|
||||||
} else {
|
} else {
|
||||||
const auto k = source->ids.find(idDate);
|
const auto k = source->ids.find(idDates);
|
||||||
if (k == end(source->ids)) {
|
if (k == end(source->ids)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
#include "data/data_changes.h"
|
#include "data/data_changes.h"
|
||||||
|
#include "data/data_stories.h"
|
||||||
#include "mainwidget.h"
|
#include "mainwidget.h"
|
||||||
#include "styles/style_window.h"
|
#include "styles/style_window.h"
|
||||||
#include "styles/style_widgets.h"
|
#include "styles/style_widgets.h"
|
||||||
|
@ -759,6 +760,37 @@ void MainMenu::setupMenu() {
|
||||||
)->setClickedCallback([=] {
|
)->setClickedCallback([=] {
|
||||||
controller->showPeerHistory(controller->session().user());
|
controller->showPeerHistory(controller->session().user());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const auto wrap = _menu->add(
|
||||||
|
object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
|
||||||
|
_menu,
|
||||||
|
CreateButton(
|
||||||
|
_menu,
|
||||||
|
rpl::single(u"My Stories"_q),
|
||||||
|
st::mainMenuButton,
|
||||||
|
IconDescriptor{
|
||||||
|
&st::settingsIconSavedMessages,
|
||||||
|
kIconLightOrange
|
||||||
|
})));
|
||||||
|
const auto stories = &controller->session().data().stories();
|
||||||
|
const auto &all = stories->all();
|
||||||
|
const auto mine = all.find(controller->session().userPeerId());
|
||||||
|
if ((mine != end(all) && !mine->second.ids.empty())
|
||||||
|
|| stories->expiredMineCount() > 0) {
|
||||||
|
wrap->toggle(true, anim::type::instant);
|
||||||
|
} else {
|
||||||
|
wrap->toggle(false, anim::type::instant);
|
||||||
|
if (!stories->expiredMineCountKnown()) {
|
||||||
|
stories->expiredMineLoadMore();
|
||||||
|
wrap->toggleOn(stories->expiredMineChanged(
|
||||||
|
) | rpl::map([=] {
|
||||||
|
return stories->expiredMineCount() > 0;
|
||||||
|
}) | rpl::filter(rpl::mappers::_1) | rpl::take(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wrap->entity()->setClickedCallback([=] {
|
||||||
|
controller->showToast(u"My Stories"_q);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
addAction(
|
addAction(
|
||||||
tr::lng_profile_add_contact(),
|
tr::lng_profile_add_contact(),
|
||||||
|
|
|
@ -2488,7 +2488,7 @@ void SessionController::openPeerStories(
|
||||||
const auto i = all.find(peerId);
|
const auto i = all.find(peerId);
|
||||||
if (i != end(all)) {
|
if (i != end(all)) {
|
||||||
const auto j = i->second.ids.lower_bound(
|
const auto j = i->second.ids.lower_bound(
|
||||||
StoryIdDate{ i->second.readTill + 1 });
|
StoryIdDates{ i->second.readTill + 1 });
|
||||||
openPeerStory(
|
openPeerStory(
|
||||||
i->second.user,
|
i->second.user,
|
||||||
j != i->second.ids.end() ? j->id : i->second.ids.front().id,
|
j != i->second.ids.end() ? j->id : i->second.ids.front().id,
|
||||||
|
|
Loading…
Reference in New Issue
Block a user