Support channel stories archive.

This commit is contained in:
John Preston 2023-09-05 11:07:32 +04:00
parent b2c9a92c3e
commit 29c5f6b706
13 changed files with 314 additions and 156 deletions

View file

@ -1183,6 +1183,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_profile_loading" = "Loading..."; "lng_profile_loading" = "Loading...";
"lng_profile_saved_stories#one" = "{count} saved story"; "lng_profile_saved_stories#one" = "{count} saved story";
"lng_profile_saved_stories#other" = "{count} saved stories"; "lng_profile_saved_stories#other" = "{count} saved stories";
"lng_profile_posts#one" = "{count} post";
"lng_profile_posts#other" = "{count} posts";
"lng_profile_photos#one" = "{count} photo"; "lng_profile_photos#one" = "{count} photo";
"lng_profile_photos#other" = "{count} photos"; "lng_profile_photos#other" = "{count} photos";
"lng_profile_gifs#one" = "{count} GIF"; "lng_profile_gifs#one" = "{count} GIF";
@ -3974,6 +3976,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_stories_recent_button" = "Recent Stories"; "lng_stories_recent_button" = "Recent Stories";
"lng_stories_archive_title" = "Stories Archive"; "lng_stories_archive_title" = "Stories Archive";
"lng_stories_archive_about" = "Only you can see archived stories unless you choose to save them to your profile."; "lng_stories_archive_about" = "Only you can see archived stories unless you choose to save them to your profile.";
"lng_stories_channel_archive_about" = "Only admins of the channel can see archived stories unless you choose to save them to the channel page.";
"lng_stories_reply_sent" = "Message Sent"; "lng_stories_reply_sent" = "Message Sent";
"lng_stories_hidden_to_contacts" = "Stories from {user} will now be shown in **Archived Chats**."; "lng_stories_hidden_to_contacts" = "Stories from {user} will now be shown in **Archived Chats**.";
"lng_stories_shown_in_chats" = "Stories from {user} will now be shown in the **Chats List**."; "lng_stories_shown_in_chats" = "Stories from {user} will now be shown in the **Chats List**.";

View file

@ -546,6 +546,21 @@ bool ChannelData::canDeleteMessages() const {
|| (adminRights() & AdminRight::DeleteMessages); || (adminRights() & AdminRight::DeleteMessages);
} }
bool ChannelData::canPostStories() const {
return amCreator()
|| (adminRights() & AdminRight::PostStories);
}
bool ChannelData::canEditStories() const {
return amCreator()
|| (adminRights() & AdminRight::EditStories);
}
bool ChannelData::canDeleteStories() const {
return amCreator()
|| (adminRights() & AdminRight::DeleteStories);
}
bool ChannelData::anyoneCanAddMembers() const { bool ChannelData::anyoneCanAddMembers() const {
return !(defaultRestrictions() & Restriction::AddParticipants); return !(defaultRestrictions() & Restriction::AddParticipants);
} }

View file

@ -107,6 +107,31 @@ Stories::Stories(not_null<Session*> owner)
, _incrementViewsTimer([=] { sendIncrementViewsRequests(); }) , _incrementViewsTimer([=] { sendIncrementViewsRequests(); })
, _pollingTimer([=] { sendPollingRequests(); }) , _pollingTimer([=] { sendPollingRequests(); })
, _pollingViewsTimer([=] { sendPollingViewsRequests(); }) { , _pollingViewsTimer([=] { sendPollingViewsRequests(); }) {
crl::on_main(this, [=] {
session().changes().peerUpdates(
Data::PeerUpdate::Flag::Rights
) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
const auto channel = update.peer->asChannel();
if (!channel) {
return;
} else if (!channel->canEditStories()) {
const auto peerId = channel->id;
const auto i = _peersWithDeletedStories.find(peerId);
if (i != end(_peersWithDeletedStories)) {
_peersWithDeletedStories.erase(i);
for (auto j = begin(_deleted); j != end(_deleted);) {
if (j->peer == peerId) {
j = _deleted.erase(j);
} else {
++j;
}
}
}
} else {
clearArchive(channel);
}
}, _lifetime);
});
} }
Stories::~Stories() { Stories::~Stories() {
@ -293,6 +318,36 @@ void Stories::processExpired() {
} }
} }
Stories::Set *Stories::lookupArchive(not_null<PeerData*> peer) {
const auto peerId = peer->id;
if (hasArchive(peer)) {
const auto i = _archive.find(peerId);
return (i != end(_archive))
? &i->second
: &_archive.emplace(peerId, Set()).first->second;
}
clearArchive(peer);
return nullptr;
}
void Stories::clearArchive(not_null<PeerData*> peer) {
const auto peerId = peer->id;
const auto i = _archive.find(peerId);
if (i == end(_archive)) {
return;
}
auto archive = base::take(i->second);
_archive.erase(i);
for (const auto &id : archive.ids.list) {
if (const auto story = lookup({ peerId, id })) {
if ((*story)->expired() && !(*story)->pinned()) {
applyDeleted(peer, id);
}
}
}
_archiveChanged.fire_copy(peerId);
}
void Stories::parseAndApply(const MTPPeerStories &stories) { void Stories::parseAndApply(const MTPPeerStories &stories) {
const auto &data = stories.data(); const auto &data = stories.data();
const auto peerId = peerFromMTP(data.vpeer()); const auto peerId = peerFromMTP(data.vpeer());
@ -377,7 +432,7 @@ Story *Stories::parseAndApply(
} }
const auto expires = data.vexpire_date().v; const auto expires = data.vexpire_date().v;
const auto expired = (expires <= now); const auto expired = (expires <= now);
if (expired && !data.is_pinned() && !peer->isSelf()) { if (expired && !data.is_pinned() && !hasArchive(peer)) {
return nullptr; return nullptr;
} }
const auto id = data.vid().v; const auto id = data.vid().v;
@ -413,13 +468,13 @@ Story *Stories::parseAndApply(
now now
)).first->second.get(); )).first->second.get();
if (peer->isSelf()) { if (const auto archive = lookupArchive(peer)) {
const auto added = _archive.list.emplace(id).second; const auto added = archive->ids.list.emplace(id).second;
if (added) { if (added) {
if (_archiveTotal >= 0 && id > _archiveLastId) { if (archive->total >= 0 && id > archive->lastId) {
++_archiveTotal; ++archive->total;
} }
_archiveChanged.fire({}); _archiveChanged.fire_copy(peer->id);
} }
} }
@ -445,7 +500,7 @@ StoryIdDates Stories::parseAndApply(
if (const auto story = parseAndApply(peer, data, now)) { if (const auto story = parseAndApply(peer, data, now)) {
return story->idDates(); return story->idDates();
} }
applyDeleted({ peer->id, data.vid().v }); applyDeleted(peer, data.vid().v);
return StoryIdDates(); return StoryIdDates();
}, [&](const MTPDstoryItemSkipped &data) { }, [&](const MTPDstoryItemSkipped &data) {
const auto expires = data.vexpire_date().v; const auto expires = data.vexpire_date().v;
@ -453,8 +508,8 @@ StoryIdDates Stories::parseAndApply(
const auto fullId = FullStoryId{ peer->id, data.vid().v }; const auto fullId = FullStoryId{ peer->id, data.vid().v };
if (!expired) { if (!expired) {
registerExpiring(expires, fullId); registerExpiring(expires, fullId);
} else if (!peer->isSelf()) { } else if (!hasArchive(peer)) {
applyDeleted(fullId); applyDeleted(peer, data.vid().v);
return StoryIdDates(); return StoryIdDates();
} else { } else {
_expiring.remove(expires, fullId); _expiring.remove(expires, fullId);
@ -466,7 +521,7 @@ StoryIdDates Stories::parseAndApply(
data.vexpire_date().v, data.vexpire_date().v,
}; };
}, [&](const MTPDstoryItemDeleted &data) { }, [&](const MTPDstoryItemDeleted &data) {
applyDeleted({ peer->id, data.vid().v }); applyDeleted(peer, data.vid().v);
return StoryIdDates(); return StoryIdDates();
}); });
} }
@ -581,8 +636,8 @@ void Stories::preloadListsMore() {
loadMore(StorySourcesList::NotHidden); loadMore(StorySourcesList::NotHidden);
} else if (!countLoaded(StorySourcesList::Hidden)) { } else if (!countLoaded(StorySourcesList::Hidden)) {
loadMore(StorySourcesList::Hidden); loadMore(StorySourcesList::Hidden);
} else if (!archiveCountKnown()) { } else if (!archiveCountKnown(_owner->session().userPeerId())) {
archiveLoadMore(); archiveLoadMore(_owner->session().userPeerId());
} }
} }
@ -681,12 +736,12 @@ void Stories::processResolvedStories(
for (const auto &item : list) { for (const auto &item : list) {
item.match([&](const MTPDstoryItem &data) { item.match([&](const MTPDstoryItem &data) {
if (!parseAndApply(peer, data, now)) { if (!parseAndApply(peer, data, now)) {
applyDeleted({ peer->id, data.vid().v }); applyDeleted(peer, data.vid().v);
} }
}, [&](const MTPDstoryItemSkipped &data) { }, [&](const MTPDstoryItemSkipped &data) {
LOG(("API Error: Unexpected storyItemSkipped in resolve.")); LOG(("API Error: Unexpected storyItemSkipped in resolve."));
}, [&](const MTPDstoryItemDeleted &data) { }, [&](const MTPDstoryItemDeleted &data) {
applyDeleted({ peer->id, data.vid().v }); applyDeleted(peer, data.vid().v);
}); });
} }
} }
@ -697,19 +752,29 @@ void Stories::finalizeResolve(FullStoryId id) {
LOG(("API Error: Could not resolve story %1_%2" LOG(("API Error: Could not resolve story %1_%2"
).arg(id.peer.value ).arg(id.peer.value
).arg(id.story)); ).arg(id.story));
applyDeleted(id); applyDeleted(_owner->peer(id.peer), id.story);
} }
} }
void Stories::applyDeleted(FullStoryId id) { void Stories::applyDeleted(not_null<PeerData*> peer, StoryId id) {
applyRemovedFromActive(id); const auto fullId = FullStoryId{ peer->id, id };
applyRemovedFromActive(fullId);
_deleted.emplace(id); if (const auto channel = peer->asChannel()) {
const auto i = _stories.find(id.peer); if (!hasArchive(channel)) {
_peersWithDeletedStories.emplace(channel->id);
}
}
_deleted.emplace(fullId);
const auto peerId = peer->id;
const auto i = _stories.find(peerId);
if (i != end(_stories)) { if (i != end(_stories)) {
const auto j = i->second.find(id.story); const auto j = i->second.find(id);
if (j != end(i->second)) { if (j != end(i->second)) {
const auto &story = _deletingStories[id] = std::move(j->second); const auto &story
= _deletingStories[fullId]
= std::move(j->second);
_expiring.remove(story->expires(), story->fullId()); _expiring.remove(story->expires(), story->fullId());
i->second.erase(j); i->second.erase(j);
@ -717,32 +782,37 @@ void Stories::applyDeleted(FullStoryId id) {
story.get(), story.get(),
UpdateFlag::Destroyed); UpdateFlag::Destroyed);
removeDependencyStory(story.get()); removeDependencyStory(story.get());
if (id.peer == session().userPeerId() if (hasArchive(story->peer())) {
&& _archive.list.remove(id.story)) { if (const auto k = _archive.find(peerId)
if (_archiveTotal > 0) { ; k != end(_archive)) {
--_archiveTotal; const auto archive = &k->second;
} if (archive->ids.list.remove(id)) {
_archiveChanged.fire({}); if (archive->total > 0) {
} --archive->total;
if (story->pinned()) {
if (const auto k = _saved.find(id.peer); k != end(_saved)) {
const auto saved = &k->second;
if (saved->ids.list.remove(id.story)) {
if (saved->total > 0) {
--saved->total;
} }
_savedChanged.fire_copy(id.peer); _archiveChanged.fire_copy(peerId);
} }
} }
} }
if (_preloading && _preloading->id() == id) { if (story->pinned()) {
_preloading = nullptr; if (const auto k = _saved.find(peerId); k != end(_saved)) {
preloadFinished(id); const auto saved = &k->second;
if (saved->ids.list.remove(id)) {
if (saved->total > 0) {
--saved->total;
}
_savedChanged.fire_copy(peerId);
}
}
} }
_owner->refreshStoryItemViews(id); if (_preloading && _preloading->id() == fullId) {
_preloading = nullptr;
preloadFinished(fullId);
}
_owner->refreshStoryItemViews(fullId);
Assert(!_pollingSettings.contains(story.get())); Assert(!_pollingSettings.contains(story.get()));
if (const auto j = _items.find(id.peer); j != end(_items)) { if (const auto j = _items.find(peerId); j != end(_items)) {
const auto k = j->second.find(id.story); const auto k = j->second.find(id);
if (k != end(j->second)) { if (k != end(j->second)) {
Assert(!k->second.lock()); Assert(!k->second.lock());
j->second.erase(k); j->second.erase(k);
@ -754,7 +824,7 @@ void Stories::applyDeleted(FullStoryId id) {
if (i->second.empty()) { if (i->second.empty()) {
_stories.erase(i); _stories.erase(i);
} }
_deletingStories.remove(id); _deletingStories.remove(fullId);
} }
} }
} }
@ -762,8 +832,8 @@ void Stories::applyDeleted(FullStoryId id) {
void Stories::applyExpired(FullStoryId id) { void Stories::applyExpired(FullStoryId id) {
if (const auto maybeStory = lookup(id)) { if (const auto maybeStory = lookup(id)) {
const auto story = *maybeStory; const auto story = *maybeStory;
if (!story->peer()->isSelf() && !story->pinned()) { if (!hasArchive(story->peer()) && !story->pinned()) {
applyDeleted(id); applyDeleted(story->peer(), id.story);
return; return;
} }
} }
@ -1312,29 +1382,44 @@ void Stories::loadViewsSlice(
}).send(); }).send();
} }
const StoriesIds &Stories::archive() const { bool Stories::hasArchive(not_null<PeerData*> peer) const {
return _archive; if (peer->isSelf()) {
return true;
} else if (const auto channel = peer->asChannel()) {
return channel->canEditStories();
}
return false;
} }
rpl::producer<> Stories::archiveChanged() const { const StoriesIds &Stories::archive(PeerId peerId) const {
static const auto empty = StoriesIds();
const auto i = _archive.find(peerId);
return (i != end(_archive)) ? i->second.ids : empty;
}
rpl::producer<PeerId> Stories::archiveChanged() const {
return _archiveChanged.events(); return _archiveChanged.events();
} }
int Stories::archiveCount() const { int Stories::archiveCount(PeerId peerId) const {
return std::max(_archiveTotal, 0); const auto i = _archive.find(peerId);
return (i != end(_archive)) ? i->second.total : 0;
} }
bool Stories::archiveCountKnown() const { bool Stories::archiveCountKnown(PeerId peerId) const {
return _archiveTotal >= 0; const auto i = _archive.find(peerId);
return (i != end(_archive)) && (i->second.total >= 0);
} }
bool Stories::archiveLoaded() const { bool Stories::archiveLoaded(PeerId peerId) const {
return _archiveLoaded; const auto i = _archive.find(peerId);
return (i != end(_archive)) && i->second.loaded;
} }
const StoriesIds *Stories::saved(PeerId peerId) const { const StoriesIds &Stories::saved(PeerId peerId) const {
static const auto empty = StoriesIds();
const auto i = _saved.find(peerId); const auto i = _saved.find(peerId);
return (i != end(_saved)) ? &i->second.ids : nullptr; return (i != end(_saved)) ? i->second.ids : empty;
} }
rpl::producer<PeerId> Stories::savedChanged() const { rpl::producer<PeerId> Stories::savedChanged() const {
@ -1356,44 +1441,53 @@ bool Stories::savedLoaded(PeerId peerId) const {
return (i != end(_saved)) && i->second.loaded; return (i != end(_saved)) && i->second.loaded;
} }
void Stories::archiveLoadMore() { void Stories::archiveLoadMore(PeerId peerId) {
if (_archiveRequestId || _archiveLoaded) { const auto peer = _owner->peer(peerId);
const auto archive = lookupArchive(peer);
if (!archive || archive->requestId || archive->loaded) {
return; return;
} }
const auto api = &_owner->session().api(); const auto api = &_owner->session().api();
_archiveRequestId = api->request(MTPstories_GetStoriesArchive( archive->requestId = api->request(MTPstories_GetStoriesArchive(
MTP_inputPeerSelf(), peer->input,
MTP_int(_archiveLastId), MTP_int(archive->lastId),
MTP_int(_archiveLastId ? kArchivePerPage : kArchiveFirstPerPage) MTP_int(archive->lastId ? kArchivePerPage : kArchiveFirstPerPage)
)).done([=](const MTPstories_Stories &result) { )).done([=](const MTPstories_Stories &result) {
_archiveRequestId = 0; const auto archive = lookupArchive(peer);
if (!archive) {
return;
}
archive->requestId = 0;
const auto &data = result.data(); const auto &data = result.data();
const auto self = _owner->session().user();
const auto now = base::unixtime::now(); const auto now = base::unixtime::now();
_archiveTotal = data.vcount().v; archive->total = data.vcount().v;
for (const auto &story : data.vstories().v) { for (const auto &story : data.vstories().v) {
const auto id = story.match([&](const auto &id) { const auto id = story.match([&](const auto &id) {
return id.vid().v; return id.vid().v;
}); });
_archive.list.emplace(id); archive->ids.list.emplace(id);
_archiveLastId = id; archive->lastId = id;
if (!parseAndApply(self, story, now)) { if (!parseAndApply(peer, story, now)) {
_archive.list.remove(id); archive->ids.list.remove(id);
if (_archiveTotal > 0) { if (archive->total > 0) {
--_archiveTotal; --archive->total;
} }
} }
} }
const auto ids = int(_archive.list.size()); const auto ids = int(archive->ids.list.size());
_archiveLoaded = data.vstories().v.empty(); archive->loaded = data.vstories().v.empty();
_archiveTotal = _archiveLoaded ? ids : std::max(_archiveTotal, ids); archive->total = archive->loaded ? ids : std::max(archive->total, ids);
_archiveChanged.fire({}); _archiveChanged.fire_copy(peerId);
}).fail([=] { }).fail([=] {
_archiveRequestId = 0; const auto archive = lookupArchive(peer);
_archiveLoaded = true; if (!archive) {
_archiveTotal = int(_archive.list.size()); return;
_archiveChanged.fire({}); }
archive->requestId = 0;
archive->loaded = true;
archive->total = int(archive->ids.list.size());
_archiveChanged.fire_copy(peerId);
}).send(); }).send();
} }
@ -1457,7 +1551,7 @@ void Stories::deleteList(const std::vector<FullStoryId> &ids) {
MTP_vector<MTPint>(list) MTP_vector<MTPint>(list)
)).done([=](const MTPVector<MTPint> &result) { )).done([=](const MTPVector<MTPint> &result) {
for (const auto &id : result.v) { for (const auto &id : result.v) {
applyDeleted({ selfId, id.v }); applyDeleted(_owner->session().user(), id.v);
} }
}).send(); }).send();
} }

View file

@ -182,14 +182,16 @@ public:
QString offset, QString offset,
Fn<void(StoryViews)> done); Fn<void(StoryViews)> done);
[[nodiscard]] const StoriesIds &archive() const; [[nodiscard]] bool hasArchive(not_null<PeerData*> peer) const;
[[nodiscard]] rpl::producer<> archiveChanged() const;
[[nodiscard]] int archiveCount() const;
[[nodiscard]] bool archiveCountKnown() const;
[[nodiscard]] bool archiveLoaded() const;
void archiveLoadMore();
[[nodiscard]] const StoriesIds *saved(PeerId peerId) const; [[nodiscard]] const StoriesIds &archive(PeerId peerId) const;
[[nodiscard]] rpl::producer<PeerId> archiveChanged() const;
[[nodiscard]] int archiveCount(PeerId peerId) const;
[[nodiscard]] bool archiveCountKnown(PeerId peerId) const;
[[nodiscard]] bool archiveLoaded(PeerId peerId) const;
void archiveLoadMore(PeerId peerId);
[[nodiscard]] const StoriesIds &saved(PeerId peerId) const;
[[nodiscard]] rpl::producer<PeerId> savedChanged() const; [[nodiscard]] rpl::producer<PeerId> savedChanged() const;
[[nodiscard]] int savedCount(PeerId peerId) const; [[nodiscard]] int savedCount(PeerId peerId) const;
[[nodiscard]] bool savedCountKnown(PeerId peerId) const; [[nodiscard]] bool savedCountKnown(PeerId peerId) const;
@ -243,13 +245,14 @@ public:
void sendReaction(FullStoryId id, Data::ReactionId reaction); void sendReaction(FullStoryId id, Data::ReactionId reaction);
private: private:
struct Saved { struct Set {
StoriesIds ids; StoriesIds ids;
int total = -1; int total = -1;
StoryId lastId = 0; StoryId lastId = 0;
bool loaded = false; bool loaded = false;
mtpRequestId requestId = 0; mtpRequestId requestId = 0;
}; };
struct PollingSettings { struct PollingSettings {
int chat = 0; int chat = 0;
int viewer = 0; int viewer = 0;
@ -271,7 +274,10 @@ private:
void finalizeResolve(FullStoryId id); void finalizeResolve(FullStoryId id);
void updatePeerStoriesState(not_null<PeerData*> peer); void updatePeerStoriesState(not_null<PeerData*> peer);
void applyDeleted(FullStoryId id); [[nodiscard]] Set *lookupArchive(not_null<PeerData*> peer);
void clearArchive(not_null<PeerData*> peer);
void applyDeleted(not_null<PeerData*> peer, StoryId id);
void applyExpired(FullStoryId id); void applyExpired(FullStoryId id);
void applyRemovedFromActive(FullStoryId id); void applyRemovedFromActive(FullStoryId id);
void applyDeletedFromSources(PeerId id, StorySourcesList list); void applyDeletedFromSources(PeerId id, StorySourcesList list);
@ -319,6 +325,7 @@ private:
PeerId, PeerId,
base::flat_map<StoryId, std::weak_ptr<HistoryItem>>> _items; base::flat_map<StoryId, std::weak_ptr<HistoryItem>>> _items;
base::flat_multi_map<TimeId, FullStoryId> _expiring; base::flat_multi_map<TimeId, FullStoryId> _expiring;
base::flat_set<PeerId> _peersWithDeletedStories;
base::flat_set<FullStoryId> _deleted; base::flat_set<FullStoryId> _deleted;
base::Timer _expireTimer; base::Timer _expireTimer;
bool _expireSchedulePosted = false; bool _expireSchedulePosted = false;
@ -346,14 +353,10 @@ private:
rpl::event_stream<PeerId> _sourceChanged; rpl::event_stream<PeerId> _sourceChanged;
rpl::event_stream<PeerId> _itemsChanged; rpl::event_stream<PeerId> _itemsChanged;
StoriesIds _archive; std::unordered_map<PeerId, Set> _archive;
int _archiveTotal = -1; rpl::event_stream<PeerId> _archiveChanged;
StoryId _archiveLastId = 0;
bool _archiveLoaded = false;
rpl::event_stream<> _archiveChanged;
mtpRequestId _archiveRequestId = 0;
std::unordered_map<PeerId, Saved> _saved; std::unordered_map<PeerId, Set> _saved;
rpl::event_stream<PeerId> _savedChanged; rpl::event_stream<PeerId> _savedChanged;
base::flat_set<PeerId> _markReadPending; base::flat_set<PeerId> _markReadPending;
@ -392,6 +395,8 @@ private:
rpl::variable<StealthMode> _stealthMode; rpl::variable<StealthMode> _stealthMode;
rpl::lifetime _lifetime;
}; };
} // namespace Data } // namespace Data

View file

@ -32,19 +32,19 @@ rpl::producer<StoriesIdsSlice> SavedStoriesIds(
const auto push = [=] { const auto push = [=] {
state->scheduled = false; state->scheduled = false;
const auto peerId = peer->id;
const auto stories = &peer->owner().stories(); const auto stories = &peer->owner().stories();
if (!stories->savedCountKnown(peer->id)) { if (!stories->savedCountKnown(peerId)) {
return; return;
} }
const auto saved = stories->saved(peer->id); const auto &saved = stories->saved(peerId);
Assert(saved != nullptr); const auto count = stories->savedCount(peerId);
const auto count = stories->savedCount(peer->id); const auto around = saved.list.lower_bound(aroundId);
const auto around = saved->list.lower_bound(aroundId); const auto hasBefore = int(around - begin(saved.list));
const auto hasBefore = int(around - begin(saved->list)); const auto hasAfter = int(end(saved.list) - around);
const auto hasAfter = int(end(saved->list) - around);
if (hasAfter < limit) { if (hasAfter < limit) {
stories->savedLoadMore(peer->id); stories->savedLoadMore(peerId);
} }
const auto takeBefore = std::min(hasBefore, limit); const auto takeBefore = std::min(hasBefore, limit);
const auto takeAfter = std::min(hasAfter, limit); const auto takeAfter = std::min(hasAfter, limit);
@ -72,14 +72,15 @@ rpl::producer<StoriesIdsSlice> SavedStoriesIds(
}); });
}; };
const auto peerId = peer->id;
const auto stories = &peer->owner().stories(); const auto stories = &peer->owner().stories();
stories->savedChanged( stories->savedChanged(
) | rpl::filter( ) | rpl::filter(
rpl::mappers::_1 == peer->id rpl::mappers::_1 == peerId
) | rpl::start_with_next(schedule, lifetime); ) | rpl::start_with_next(schedule, lifetime);
if (!stories->savedCountKnown(peer->id)) { if (!stories->savedCountKnown(peerId)) {
stories->savedLoadMore(peer->id); stories->savedLoadMore(peerId);
} }
push(); push();
@ -89,7 +90,7 @@ rpl::producer<StoriesIdsSlice> SavedStoriesIds(
} }
rpl::producer<StoriesIdsSlice> ArchiveStoriesIds( rpl::producer<StoriesIdsSlice> ArchiveStoriesIds(
not_null<Main::Session*> session, not_null<PeerData*> peer,
StoryId aroundId, StoryId aroundId,
int limit) { int limit) {
return [=](auto consumer) { return [=](auto consumer) {
@ -105,18 +106,19 @@ rpl::producer<StoriesIdsSlice> ArchiveStoriesIds(
const auto push = [=] { const auto push = [=] {
state->scheduled = false; state->scheduled = false;
const auto stories = &session->data().stories(); const auto peerId = peer->id;
if (!stories->archiveCountKnown()) { const auto stories = &peer->owner().stories();
if (!stories->archiveCountKnown(peerId)) {
return; return;
} }
const auto &archive = stories->archive(); const auto &archive = stories->archive(peerId);
const auto count = stories->archiveCount(); const auto count = stories->archiveCount(peerId);
const auto i = archive.list.lower_bound(aroundId); const auto i = archive.list.lower_bound(aroundId);
const auto hasBefore = int(i - begin(archive.list)); const auto hasBefore = int(i - begin(archive.list));
const auto hasAfter = int(end(archive.list) - i); const auto hasAfter = int(end(archive.list) - i);
if (hasAfter < limit) { if (hasAfter < limit) {
stories->archiveLoadMore(); stories->archiveLoadMore(peerId);
} }
const auto takeBefore = std::min(hasBefore, limit); const auto takeBefore = std::min(hasBefore, limit);
const auto takeAfter = std::min(hasAfter, limit); const auto takeAfter = std::min(hasAfter, limit);
@ -144,12 +146,13 @@ rpl::producer<StoriesIdsSlice> ArchiveStoriesIds(
}); });
}; };
const auto stories = &session->data().stories(); const auto peerId = peer->id;
const auto stories = &peer->owner().stories();
stories->archiveChanged( stories->archiveChanged(
) | rpl::start_with_next(schedule, lifetime); ) | rpl::start_with_next(schedule, lifetime);
if (!stories->archiveCountKnown()) { if (!stories->archiveCountKnown(peerId)) {
stories->archiveLoadMore(); stories->archiveLoadMore(peerId);
} }
push(); push();

View file

@ -25,7 +25,7 @@ using StoriesIdsSlice = AbstractSparseIds<base::flat_set<StoryId>>;
int limit); int limit);
[[nodiscard]] rpl::producer<StoriesIdsSlice> ArchiveStoriesIds( [[nodiscard]] rpl::producer<StoriesIdsSlice> ArchiveStoriesIds(
not_null<Main::Session*> session, not_null<PeerData*> peer,
StoryId aroundId, StoryId aroundId,
int limit); int limit);

View file

@ -45,6 +45,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_group_call.h" // GroupCall::input. #include "data/data_group_call.h" // GroupCall::input.
#include "data/data_folder.h" #include "data/data_folder.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_stories.h"
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_chat.h" #include "data/data_chat.h"
#include "data/data_user.h" #include "data/data_user.h"
@ -754,6 +755,14 @@ void TopBarWidget::setActiveChat(
updateControlsVisibility(); updateControlsVisibility();
updateControlsGeometry(); updateControlsGeometry();
}, _activeChatLifetime); }, _activeChatLifetime);
if (const auto channel = peer->asChannel()) {
if (channel->canEditStories()
&& !channel->owner().stories().archiveCountKnown(
channel->id)) {
channel->owner().stories().archiveLoadMore(channel->id);
}
}
} }
if (const auto history = _activeChat.key.history()) { if (const auto history = _activeChat.key.history()) {

View file

@ -138,12 +138,15 @@ inline auto AddStoriesButton(
) | rpl::map([](const Data::StoriesIdsSlice &slice) { ) | rpl::map([](const Data::StoriesIdsSlice &slice) {
return slice.fullCount().value_or(0); return slice.fullCount().value_or(0);
})); }));
const auto phrase = peer->isChannel() ? (+[](int count) {
return tr::lng_profile_posts(tr::now, lt_count, count);
}) : (+[](int count) {
return tr::lng_profile_saved_stories(tr::now, lt_count, count);
});
auto result = AddCountedButton( auto result = AddCountedButton(
parent, parent,
std::move(count), std::move(count),
[](int count) { phrase,
return tr::lng_profile_saved_stories(tr::now, lt_count, count);
},
tracker)->entity(); tracker)->entity();
result->addClickHandler([=] { result->addClickHandler([=] {
navigation->showSection(Info::Stories::Make(peer)); navigation->showSection(Info::Stories::Make(peer));

View file

@ -103,11 +103,11 @@ void InnerWidget::setupTop() {
const auto key = _controller->key(); const auto key = _controller->key();
const auto peer = key.storiesPeer(); const auto peer = key.storiesPeer();
if (peer if (peer
&& peer->isSelf()
&& key.storiesTab() == Stories::Tab::Saved && key.storiesTab() == Stories::Tab::Saved
&& peer->owner().stories().hasArchive(peer)
&& _isStackBottom) { && _isStackBottom) {
createButtons(); createButtons();
} else if (key.storiesTab() == Stories::Tab::Archive) { } else if (peer && key.storiesTab() == Stories::Tab::Archive) {
createAboutArchive(); createAboutArchive();
} else { } else {
_top.destroy(); _top.destroy();
@ -120,8 +120,9 @@ void InnerWidget::createButtons() {
_top->show(); _top->show();
_topHeight = _top->heightValue(); _topHeight = _top->heightValue();
const auto stories = &_controller->session().data().stories(); const auto key = _controller->key();
const auto self = _controller->session().user(); const auto peer = key.storiesPeer();
const auto stories = &peer->owner().stories();
const auto archive = ::Settings::AddButton( const auto archive = ::Settings::AddButton(
_top, _top,
tr::lng_stories_archive_button(), tr::lng_stories_archive_button(),
@ -133,8 +134,13 @@ void InnerWidget::createButtons() {
}); });
auto count = rpl::single( auto count = rpl::single(
rpl::empty rpl::empty
) | rpl::then(stories->archiveChanged()) | rpl::map([=] { ) | rpl::then(
const auto value = stories->archiveCount(); stories->archiveChanged(
) | rpl::filter(
rpl::mappers::_1 == peer->id
) | rpl::to_empty
) | rpl::map([=] {
const auto value = stories->archiveCount(peer->id);
return (value > 0) ? QString::number(value) : QString(); return (value > 0) ? QString::number(value) : QString();
}); });
::Settings::CreateRightLabel( ::Settings::CreateRightLabel(
@ -157,7 +163,7 @@ void InnerWidget::createButtons() {
using namespace Dialogs::Stories; using namespace Dialogs::Stories;
auto last = LastForPeer( auto last = LastForPeer(
self peer
) | rpl::map([=](Content &&content) { ) | rpl::map([=](Content &&content) {
for (auto &element : content.elements) { for (auto &element : content.elements) {
element.unreadCount = 0; element.unreadCount = 0;
@ -190,7 +196,7 @@ void InnerWidget::createButtons() {
}, thumbs->lifetime()); }, thumbs->lifetime());
thumbs->setAttribute(Qt::WA_TransparentForMouseEvents); thumbs->setAttribute(Qt::WA_TransparentForMouseEvents);
recent->addClickHandler([=] { recent->addClickHandler([=] {
_controller->parentController()->openPeerStories(self->id); _controller->parentController()->openPeerStories(peer->id);
}); });
object_ptr<Profile::FloatingIcon>( object_ptr<Profile::FloatingIcon>(
recent, recent,
@ -219,11 +225,14 @@ void InnerWidget::createAboutArchive() {
_top->show(); _top->show();
_topHeight = _top->heightValue(); _topHeight = _top->heightValue();
const auto peer = _controller->key().storiesPeer();
_top->add(object_ptr<Ui::DividerLabel>( _top->add(object_ptr<Ui::DividerLabel>(
_top, _top,
object_ptr<Ui::FlatLabel>( object_ptr<Ui::FlatLabel>(
_top, _top,
tr::lng_stories_archive_about(), (peer->isChannel()
? tr::lng_stories_channel_archive_about
: tr::lng_stories_archive_about)(),
st::infoStoriesAboutArchive), st::infoStoriesAboutArchive),
st::infoStoriesAboutArchivePadding)); st::infoStoriesAboutArchivePadding));

View file

@ -177,7 +177,7 @@ void Provider::refreshViewer() {
const auto session = &_peer->session(); const auto session = &_peer->session();
auto ids = (_tab == Tab::Saved) auto ids = (_tab == Tab::Saved)
? Data::SavedStoriesIds(_peer, idForViewer, _idsLimit) ? Data::SavedStoriesIds(_peer, idForViewer, _idsLimit)
: Data::ArchiveStoriesIds(session, idForViewer, _idsLimit); : Data::ArchiveStoriesIds(_peer, idForViewer, _idsLimit);
std::move( std::move(
ids ids
) | rpl::start_with_next([=](Data::StoriesIdsSlice &&slice) { ) | rpl::start_with_next([=](Data::StoriesIdsSlice &&slice) {

View file

@ -654,41 +654,38 @@ void Controller::rebuildFromContext(
hideSiblings(); hideSiblings();
}, [&](StoriesContextSaved) { }, [&](StoriesContextSaved) {
if (stories.savedCountKnown(peerId)) { if (stories.savedCountKnown(peerId)) {
if (const auto saved = stories.saved(peerId)) { const auto &saved = stories.saved(peerId);
const auto &ids = saved->list; const auto &ids = saved.list;
const auto i = ids.find(id); const auto i = ids.find(id);
if (i != end(ids)) { if (i != end(ids)) {
list = StoriesList{ list = StoriesList{
.peer = peer, .peer = peer,
.ids = *saved, .ids = saved,
.total = stories.savedCount(peerId), .total = stories.savedCount(peerId),
}; };
_index = int(i - begin(ids)); _index = int(i - begin(ids));
if (ids.size() < list->total if (ids.size() < list->total
&& (end(ids) - i) < kPreloadStoriesCount) { && (end(ids) - i) < kPreloadStoriesCount) {
stories.savedLoadMore(peerId); stories.savedLoadMore(peerId);
}
} }
} }
} }
hideSiblings(); hideSiblings();
}, [&](StoriesContextArchive) { }, [&](StoriesContextArchive) {
Expects(peer->isSelf()); if (stories.archiveCountKnown(peerId)) {
const auto &archive = stories.archive(peerId);
if (stories.archiveCountKnown()) {
const auto &archive = stories.archive();
const auto &ids = archive.list; const auto &ids = archive.list;
const auto i = ids.find(id); const auto i = ids.find(id);
if (i != end(ids)) { if (i != end(ids)) {
list = StoriesList{ list = StoriesList{
.peer = peer, .peer = peer,
.ids = archive, .ids = archive,
.total = stories.archiveCount(), .total = stories.archiveCount(peerId),
}; };
_index = int(i - begin(ids)); _index = int(i - begin(ids));
if (ids.size() < list->total if (ids.size() < list->total
&& (end(ids) - i) < kPreloadStoriesCount) { && (end(ids) - i) < kPreloadStoriesCount) {
stories.archiveLoadMore(); stories.archiveLoadMore(peerId);
} }
} }
} }
@ -1390,9 +1387,7 @@ void Controller::loadMoreToList() {
v::match(_context.data, [&](StoriesContextSaved) { v::match(_context.data, [&](StoriesContextSaved) {
stories.savedLoadMore(peerId); stories.savedLoadMore(peerId);
}, [&](StoriesContextArchive) { }, [&](StoriesContextArchive) {
Expects(peer->isSelf()); stories.archiveLoadMore(peerId);
stories.archiveLoadMore();
}, [](const auto &) { }, [](const auto &) {
}); });
} }

View file

@ -859,16 +859,19 @@ void MainMenu::setupMenu() {
tr::lng_menu_my_stories(), tr::lng_menu_my_stories(),
st::mainMenuButton, st::mainMenuButton,
IconDescriptor{ &st::menuIconStoriesSavedSection }))); IconDescriptor{ &st::menuIconStoriesSavedSection })));
const auto selfId = controller->session().userPeerId();
const auto stories = &controller->session().data().stories(); const auto stories = &controller->session().data().stories();
if (stories->archiveCount() > 0) { if (stories->archiveCount(selfId) > 0) {
wrap->toggle(true, anim::type::instant); wrap->toggle(true, anim::type::instant);
} else { } else {
wrap->toggle(false, anim::type::instant); wrap->toggle(false, anim::type::instant);
if (!stories->archiveCountKnown()) { if (!stories->archiveCountKnown(selfId)) {
stories->archiveLoadMore(); stories->archiveLoadMore(selfId);
wrap->toggleOn(stories->archiveChanged( wrap->toggleOn(stories->archiveChanged(
) | rpl::filter(
rpl::mappers::_1 == selfId
) | rpl::map([=] { ) | rpl::map([=] {
return stories->archiveCount() > 0; return stories->archiveCount(selfId) > 0;
}) | rpl::filter(rpl::mappers::_1) | rpl::take(1)); }) | rpl::filter(rpl::mappers::_1) | rpl::take(1));
} }
} }

View file

@ -66,6 +66,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "info/info_memento.h" #include "info/info_memento.h"
#include "info/info_controller.h" #include "info/info_controller.h"
#include "info/profile/info_profile_values.h" #include "info/profile/info_profile_values.h"
#include "info/stories/info_stories_widget.h"
#include "data/notify/data_notify_settings.h" #include "data/notify/data_notify_settings.h"
#include "data/data_changes.h" #include "data/data_changes.h"
#include "data/data_session.h" #include "data/data_session.h"
@ -249,6 +250,7 @@ private:
void addToggleMuteSubmenu(bool addSeparator); void addToggleMuteSubmenu(bool addSeparator);
void addSupportInfo(); void addSupportInfo();
void addInfo(); void addInfo();
void addStoryArchive();
void addNewWindow(); void addNewWindow();
void addToggleFolder(); void addToggleFolder();
void addToggleUnreadMark(); void addToggleUnreadMark();
@ -548,6 +550,22 @@ void Filler::addInfo() {
}, _peer->isUser() ? &st::menuIconProfile : &st::menuIconInfo); }, _peer->isUser() ? &st::menuIconProfile : &st::menuIconInfo);
} }
void Filler::addStoryArchive() {
const auto channel = _peer ? _peer->asChannel() : nullptr;
if (!channel || !channel->canEditStories()) {
return;
}
const auto controller = _controller;
const auto weak = base::make_weak(_thread);
_addAction(tr::lng_stories_archive_button(tr::now), [=] {
if (const auto strong = weak.get()) {
controller->showSection(Info::Stories::Make(
channel,
Info::Stories::Tab::Archive));
}
}, &st::menuIconStoriesArchiveSection);
}
void Filler::addToggleFolder() { void Filler::addToggleFolder() {
const auto controller = _controller; const auto controller = _controller;
const auto history = _request.key.history(); const auto history = _request.key.history();
@ -1196,6 +1214,7 @@ void Filler::fillContextMenuActions() {
void Filler::fillHistoryActions() { void Filler::fillHistoryActions() {
addToggleMuteSubmenu(true); addToggleMuteSubmenu(true);
addInfo(); addInfo();
addStoryArchive();
addSupportInfo(); addSupportInfo();
addManageChat(); addManageChat();
addCreatePoll(); addCreatePoll();