mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-16 06:07:06 +02:00
Track and load ids of expired mine stories.
This commit is contained in:
parent
aba84a6010
commit
8eac04cb90
6 changed files with 331 additions and 61 deletions
|
@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#include "data/data_stories.h"
|
||||
|
||||
#include "base/unixtime.h"
|
||||
#include "api/api_text_entities.h"
|
||||
#include "apiwrap.h"
|
||||
#include "core/application.h"
|
||||
|
@ -32,6 +33,8 @@ constexpr auto kMaxResolveTogether = 100;
|
|||
constexpr auto kIgnorePreloadAroundIfLoaded = 15;
|
||||
constexpr auto kPreloadAroundCount = 30;
|
||||
constexpr auto kMarkAsReadDelay = 3 * crl::time(1000);
|
||||
constexpr auto kExpiredMineFirstPerPage = 30;
|
||||
constexpr auto kExpiredMinePerPage = 100;
|
||||
|
||||
using UpdateFlag = StoryUpdate::Flag;
|
||||
|
||||
|
@ -80,11 +83,13 @@ Story::Story(
|
|||
StoryId id,
|
||||
not_null<PeerData*> peer,
|
||||
StoryMedia media,
|
||||
TimeId date)
|
||||
TimeId date,
|
||||
TimeId expires)
|
||||
: _id(id)
|
||||
, _peer(peer)
|
||||
, _media(std::move(media))
|
||||
, _date(date) {
|
||||
, _date(date)
|
||||
, _expires(expires) {
|
||||
}
|
||||
|
||||
Session &Story::owner() const {
|
||||
|
@ -103,8 +108,12 @@ StoryId Story::id() const {
|
|||
return _id;
|
||||
}
|
||||
|
||||
StoryIdDate Story::idDate() const {
|
||||
return { _id, _date };
|
||||
bool Story::mine() const {
|
||||
return _peer->isSelf();
|
||||
}
|
||||
|
||||
StoryIdDates Story::idDates() const {
|
||||
return { _id, _date, _expires };
|
||||
}
|
||||
|
||||
FullStoryId Story::fullId() const {
|
||||
|
@ -115,6 +124,14 @@ 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());
|
||||
}
|
||||
|
||||
const StoryMedia &Story::media() const {
|
||||
return _media;
|
||||
}
|
||||
|
@ -276,6 +293,7 @@ bool Story::applyChanges(StoryMedia media, const MTPDstoryItem &data) {
|
|||
|
||||
Stories::Stories(not_null<Session*> owner)
|
||||
: _owner(owner)
|
||||
, _expireTimer([=] { processExpired(); })
|
||||
, _markReadTimer([=] { sendMarkAsReadRequests(); }) {
|
||||
}
|
||||
|
||||
|
@ -293,21 +311,27 @@ Main::Session &Stories::session() const {
|
|||
void Stories::apply(const MTPDupdateStory &data) {
|
||||
const auto peerId = peerFromUser(data.vuser_id());
|
||||
const auto user = not_null(_owner->peer(peerId)->asUser());
|
||||
const auto idDate = parseAndApply(user, data.vstory());
|
||||
if (!idDate) {
|
||||
const auto now = base::unixtime::now();
|
||||
const auto idDates = parseAndApply(user, data.vstory(), now);
|
||||
if (!idDates) {
|
||||
return;
|
||||
}
|
||||
const auto expired = (idDates.expires <= now);
|
||||
if (expired) {
|
||||
applyExpired({ peerId, idDates.id });
|
||||
return;
|
||||
}
|
||||
const auto i = _all.find(peerId);
|
||||
if (i == end(_all)) {
|
||||
requestUserStories(user);
|
||||
return;
|
||||
} else if (i->second.ids.contains(idDate)) {
|
||||
} else if (i->second.ids.contains(idDates)) {
|
||||
return;
|
||||
}
|
||||
const auto was = i->second.info();
|
||||
i->second.ids.emplace(idDate);
|
||||
const auto now = i->second.info();
|
||||
if (was == now) {
|
||||
const auto wasInfo = i->second.info();
|
||||
i->second.ids.emplace(idDates);
|
||||
const auto nowInfo = i->second.info();
|
||||
if (wasInfo == nowInfo) {
|
||||
return;
|
||||
}
|
||||
const auto refreshInList = [&](StorySourcesList list) {
|
||||
|
@ -317,7 +341,7 @@ void Stories::apply(const MTPDupdateStory &data) {
|
|||
peerId,
|
||||
&StoriesSourceInfo::id);
|
||||
if (i != end(sources)) {
|
||||
*i = now;
|
||||
*i = nowInfo;
|
||||
sort(list);
|
||||
}
|
||||
};
|
||||
|
@ -342,7 +366,61 @@ void Stories::requestUserStories(not_null<UserData*> user) {
|
|||
_requestingUserStories.remove(user);
|
||||
applyDeletedFromSources(user->id, StorySourcesList::All);
|
||||
}).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) {
|
||||
|
@ -357,9 +435,10 @@ void Stories::parseAndApply(const MTPUserStories &stories) {
|
|||
.hidden = user->hasStoriesHidden(),
|
||||
};
|
||||
const auto &list = data.vstories().v;
|
||||
const auto now = base::unixtime::now();
|
||||
result.ids.reserve(list.size());
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -391,21 +470,31 @@ void Stories::parseAndApply(const MTPUserStories &stories) {
|
|||
}
|
||||
sort(list);
|
||||
};
|
||||
add(StorySourcesList::All);
|
||||
if (result.user->hasStoriesHidden()) {
|
||||
applyDeletedFromSources(peerId, StorySourcesList::NotHidden);
|
||||
if (result.user->isContact()) {
|
||||
add(StorySourcesList::All);
|
||||
if (result.user->hasStoriesHidden()) {
|
||||
applyDeletedFromSources(peerId, StorySourcesList::NotHidden);
|
||||
} else {
|
||||
add(StorySourcesList::NotHidden);
|
||||
}
|
||||
} else {
|
||||
add(StorySourcesList::NotHidden);
|
||||
applyDeletedFromSources(peerId, StorySourcesList::All);
|
||||
}
|
||||
}
|
||||
|
||||
Story *Stories::parseAndApply(
|
||||
not_null<PeerData*> peer,
|
||||
const MTPDstoryItem &data) {
|
||||
const MTPDstoryItem &data,
|
||||
TimeId now) {
|
||||
const auto media = ParseMedia(_owner, data.vmedia());
|
||||
if (!media) {
|
||||
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;
|
||||
auto &stories = _stories[peer->id];
|
||||
const auto i = stories.find(id);
|
||||
|
@ -421,25 +510,51 @@ Story *Stories::parseAndApply(
|
|||
id,
|
||||
peer,
|
||||
StoryMedia{ *media },
|
||||
data.vdate().v)).first->second.get();
|
||||
data.vdate().v,
|
||||
data.vexpire_date().v)).first->second.get();
|
||||
result->applyChanges(*media, data);
|
||||
|
||||
if (expired) {
|
||||
_expiring.remove(expires, result->fullId());
|
||||
applyExpired(result->fullId());
|
||||
} else {
|
||||
registerExpiring(expires, result->fullId());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
StoryIdDate Stories::parseAndApply(
|
||||
StoryIdDates Stories::parseAndApply(
|
||||
not_null<PeerData*> peer,
|
||||
const MTPstoryItem &story) {
|
||||
const MTPstoryItem &story,
|
||||
TimeId now) {
|
||||
return story.match([&](const MTPDstoryItem &data) {
|
||||
if (const auto story = parseAndApply(peer, data)) {
|
||||
return story->idDate();
|
||||
if (const auto story = parseAndApply(peer, data, now)) {
|
||||
return story->idDates();
|
||||
}
|
||||
applyDeleted({ peer->id, data.vid().v });
|
||||
return StoryIdDate();
|
||||
return StoryIdDates();
|
||||
}, [&](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) {
|
||||
applyDeleted({ peer->id, data.vid().v });
|
||||
return StoryIdDate();
|
||||
return StoryIdDates();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -571,9 +686,10 @@ void Stories::sendResolveRequests() {
|
|||
void Stories::processResolvedStories(
|
||||
not_null<PeerData*> peer,
|
||||
const QVector<MTPStoryItem> &list) {
|
||||
const auto now = base::unixtime::now();
|
||||
for (const auto &item : list) {
|
||||
item.match([&](const MTPDstoryItem &data) {
|
||||
if (!parseAndApply(peer, data)) {
|
||||
if (!parseAndApply(peer, data, now)) {
|
||||
applyDeleted({ peer->id, data.vid().v });
|
||||
}
|
||||
}, [&](const MTPDstoryItemSkipped &data) {
|
||||
|
@ -595,6 +711,48 @@ void Stories::finalizeResolve(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 index = static_cast<int>(list);
|
||||
auto &sources = _sources[index];
|
||||
|
@ -609,7 +767,7 @@ void Stories::applyDeleted(FullStoryId id) {
|
|||
};
|
||||
const auto i = _all.find(id.peer);
|
||||
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) {
|
||||
i->second.ids.erase(j);
|
||||
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) {
|
||||
|
@ -755,7 +897,7 @@ void Stories::loadAround(FullStoryId id, StoriesContext context) {
|
|||
if (i == end(_all)) {
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
|
@ -960,6 +1102,67 @@ void Stories::loadViewsSlice(
|
|||
}).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() {
|
||||
if (!_markReadPending.empty()) {
|
||||
sendMarkAsReadRequests();
|
||||
|
|
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
#include "base/expected.h"
|
||||
#include "base/timer.h"
|
||||
#include "base/weak_ptr.h"
|
||||
|
||||
class Image;
|
||||
class PhotoData;
|
||||
|
@ -22,9 +23,10 @@ namespace Data {
|
|||
|
||||
class Session;
|
||||
|
||||
struct StoryIdDate {
|
||||
struct StoryIdDates {
|
||||
StoryId id = 0;
|
||||
TimeId date = 0;
|
||||
TimeId expires = 0;
|
||||
|
||||
[[nodiscard]] bool valid() const {
|
||||
return id != 0;
|
||||
|
@ -33,8 +35,8 @@ struct StoryIdDate {
|
|||
return valid();
|
||||
}
|
||||
|
||||
friend inline auto operator<=>(StoryIdDate, StoryIdDate) = default;
|
||||
friend inline bool operator==(StoryIdDate, StoryIdDate) = default;
|
||||
friend inline auto operator<=>(StoryIdDates, StoryIdDates) = default;
|
||||
friend inline bool operator==(StoryIdDates, StoryIdDates) = default;
|
||||
};
|
||||
|
||||
struct StoryMedia {
|
||||
|
@ -56,16 +58,20 @@ public:
|
|||
StoryId id,
|
||||
not_null<PeerData*> peer,
|
||||
StoryMedia media,
|
||||
TimeId date);
|
||||
TimeId date,
|
||||
TimeId expires);
|
||||
|
||||
[[nodiscard]] Session &owner() const;
|
||||
[[nodiscard]] Main::Session &session() const;
|
||||
[[nodiscard]] not_null<PeerData*> peer() const;
|
||||
|
||||
[[nodiscard]] StoryId id() const;
|
||||
[[nodiscard]] StoryIdDate idDate() const;
|
||||
[[nodiscard]] bool mine() const;
|
||||
[[nodiscard]] StoryIdDates idDates() const;
|
||||
[[nodiscard]] FullStoryId fullId() const;
|
||||
[[nodiscard]] TimeId date() const;
|
||||
[[nodiscard]] TimeId expires() const;
|
||||
[[nodiscard]] bool expired(TimeId now = 0) const;
|
||||
[[nodiscard]] const StoryMedia &media() const;
|
||||
[[nodiscard]] PhotoData *photo() const;
|
||||
[[nodiscard]] DocumentData *document() const;
|
||||
|
@ -101,6 +107,7 @@ private:
|
|||
std::vector<StoryView> _viewsList;
|
||||
int _views = 0;
|
||||
const TimeId _date = 0;
|
||||
const TimeId _expires = 0;
|
||||
bool _pinned = false;
|
||||
|
||||
};
|
||||
|
@ -119,7 +126,7 @@ struct StoriesSourceInfo {
|
|||
|
||||
struct StoriesSource {
|
||||
not_null<UserData*> user;
|
||||
base::flat_set<StoryIdDate> ids;
|
||||
base::flat_set<StoryIdDates> ids;
|
||||
StoryId readTill = 0;
|
||||
bool hidden = false;
|
||||
|
||||
|
@ -167,7 +174,7 @@ struct StoriesContext {
|
|||
|
||||
inline constexpr auto kStorySourcesListCount = 2;
|
||||
|
||||
class Stories final {
|
||||
class Stories final : public base::has_weak_ptr {
|
||||
public:
|
||||
explicit Stories(not_null<Session*> owner);
|
||||
~Stories();
|
||||
|
@ -210,14 +217,23 @@ public:
|
|||
std::optional<StoryView> offset,
|
||||
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:
|
||||
void parseAndApply(const MTPUserStories &stories);
|
||||
[[nodiscard]] Story *parseAndApply(
|
||||
not_null<PeerData*> peer,
|
||||
const MTPDstoryItem &data);
|
||||
StoryIdDate parseAndApply(
|
||||
const MTPDstoryItem &data,
|
||||
TimeId now);
|
||||
StoryIdDates parseAndApply(
|
||||
not_null<PeerData*> peer,
|
||||
const MTPstoryItem &story);
|
||||
const MTPstoryItem &story,
|
||||
TimeId now);
|
||||
void processResolvedStories(
|
||||
not_null<PeerData*> peer,
|
||||
const QVector<MTPStoryItem> &list);
|
||||
|
@ -225,20 +241,30 @@ private:
|
|||
void finalizeResolve(FullStoryId id);
|
||||
|
||||
void applyDeleted(FullStoryId id);
|
||||
void applyExpired(FullStoryId id);
|
||||
void applyRemovedFromActive(FullStoryId id);
|
||||
void applyDeletedFromSources(PeerId id, StorySourcesList list);
|
||||
void removeDependencyStory(not_null<Story*> story);
|
||||
void sort(StorySourcesList list);
|
||||
|
||||
void addToExpiredMine(not_null<Story*> story);
|
||||
|
||||
void sendMarkAsReadRequests();
|
||||
void sendMarkAsReadRequest(not_null<PeerData*> peer, StoryId tillId);
|
||||
|
||||
void requestUserStories(not_null<UserData*> user);
|
||||
void registerExpiring(TimeId expires, FullStoryId id);
|
||||
void scheduleExpireTimer();
|
||||
void processExpired();
|
||||
|
||||
const not_null<Session*> _owner;
|
||||
base::flat_map<
|
||||
PeerId,
|
||||
base::flat_map<StoryId, std::unique_ptr<Story>>> _stories;
|
||||
base::flat_multi_map<TimeId, FullStoryId> _expiring;
|
||||
base::flat_set<FullStoryId> _deleted;
|
||||
base::Timer _expireTimer;
|
||||
bool _expireSchedulePosted = false;
|
||||
|
||||
base::flat_map<
|
||||
PeerId,
|
||||
|
@ -261,6 +287,13 @@ private:
|
|||
|
||||
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::Timer _markReadTimer;
|
||||
base::flat_set<PeerId> _markReadRequests;
|
||||
|
|
|
@ -297,8 +297,10 @@ ClickHandlerPtr JumpToStoryClickHandler(
|
|||
? separate->sessionController()
|
||||
: peer->session().tryResolveWindow();
|
||||
if (controller) {
|
||||
// #TODO stories decide context
|
||||
controller->openPeerStory(peer, storyId, {});
|
||||
controller->openPeerStory(
|
||||
peer,
|
||||
storyId,
|
||||
{ Data::StoriesContextSingle() });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -422,14 +422,14 @@ void Controller::show(
|
|||
stories.loadMore(list);
|
||||
}
|
||||
});
|
||||
const auto idDate = story->idDate();
|
||||
const auto idDates = story->idDates();
|
||||
if (!source) {
|
||||
return;
|
||||
} else if (source == &single) {
|
||||
single.ids.emplace(idDate);
|
||||
single.ids.emplace(idDates);
|
||||
_index = 0;
|
||||
} else {
|
||||
const auto k = source->ids.find(idDate);
|
||||
const auto k = source->ids.find(idDates);
|
||||
if (k == end(source->ids)) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -59,6 +59,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_stories.h"
|
||||
#include "mainwidget.h"
|
||||
#include "styles/style_window.h"
|
||||
#include "styles/style_widgets.h"
|
||||
|
@ -759,6 +760,37 @@ void MainMenu::setupMenu() {
|
|||
)->setClickedCallback([=] {
|
||||
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 {
|
||||
addAction(
|
||||
tr::lng_profile_add_contact(),
|
||||
|
|
|
@ -2488,7 +2488,7 @@ void SessionController::openPeerStories(
|
|||
const auto i = all.find(peerId);
|
||||
if (i != end(all)) {
|
||||
const auto j = i->second.ids.lower_bound(
|
||||
StoryIdDate{ i->second.readTill + 1 });
|
||||
StoryIdDates{ i->second.readTill + 1 });
|
||||
openPeerStory(
|
||||
i->second.user,
|
||||
j != i->second.ids.end() ? j->id : i->second.ids.front().id,
|
||||
|
|
Loading…
Add table
Reference in a new issue