mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-16 06:07:06 +02:00
Support correct saved stories / archive loading.
This commit is contained in:
parent
7f8a985067
commit
5e5b252f2f
15 changed files with 382 additions and 162 deletions
|
@ -3800,6 +3800,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_stories_views#other" = "{count} views";
|
||||
"lng_stories_no_views" = "No views";
|
||||
|
||||
"lng_stories_my_title" = "My Stories";
|
||||
"lng_stories_archive_button" = "Archive";
|
||||
"lng_stories_archive_title" = "Stories Archive";
|
||||
|
||||
// Wnd specific
|
||||
|
||||
"lng_wnd_choose_program_menu" = "Choose Default Program...";
|
||||
|
|
|
@ -33,8 +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;
|
||||
constexpr auto kArchiveFirstPerPage = 30;
|
||||
constexpr auto kArchivePerPage = 100;
|
||||
constexpr auto kSavedFirstPerPage = 30;
|
||||
constexpr auto kSavedPerPage = 100;
|
||||
|
||||
|
@ -333,6 +333,9 @@ void Stories::apply(const MTPDupdateStory &data) {
|
|||
const auto wasInfo = i->second.info();
|
||||
i->second.ids.emplace(idDates);
|
||||
const auto nowInfo = i->second.info();
|
||||
if (user->isSelf() && i->second.readTill < idDates.id) {
|
||||
i->second.readTill = idDates.id;
|
||||
}
|
||||
if (wasInfo == nowInfo) {
|
||||
return;
|
||||
}
|
||||
|
@ -361,15 +364,44 @@ void Stories::apply(not_null<PeerData*> peer, const MTPUserStories *data) {
|
|||
if (i != end(_stories)) {
|
||||
auto stories = base::take(i->second);
|
||||
_stories.erase(i);
|
||||
|
||||
auto archiveChanged = false;
|
||||
auto savedChanged = false;
|
||||
if (peer->isSelf()) {
|
||||
for (const auto &[id, story] : stories) {
|
||||
if (_archive.list.remove(id)) {
|
||||
archiveChanged = true;
|
||||
if (_archiveTotal > 0) {
|
||||
--_archiveTotal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto j = _saved.find(peer->id);
|
||||
const auto saved = (j != end(_saved)) ? &j->second : nullptr;
|
||||
for (const auto &[id, story] : stories) {
|
||||
// Duplicated in Stories::applyDeleted.
|
||||
_deleted.emplace(FullStoryId{ peer->id, id });
|
||||
_expiring.remove(story->expires(), story->fullId());
|
||||
if (story->pinned() && saved) {
|
||||
if (saved->ids.list.remove(id)) {
|
||||
savedChanged = true;
|
||||
if (saved->total > 0) {
|
||||
--saved->total;
|
||||
}
|
||||
}
|
||||
}
|
||||
session().changes().storyUpdated(
|
||||
story.get(),
|
||||
UpdateFlag::Destroyed);
|
||||
removeDependencyStory(story.get());
|
||||
}
|
||||
if (archiveChanged) {
|
||||
_archiveChanged.fire({});
|
||||
}
|
||||
if (savedChanged) {
|
||||
_savedChanged.fire_copy(peer->id);
|
||||
}
|
||||
}
|
||||
_sourceChanged.fire_copy(peer->id);
|
||||
} else {
|
||||
|
@ -471,6 +503,8 @@ void Stories::parseAndApply(const MTPUserStories &stories) {
|
|||
if (result.ids.empty()) {
|
||||
applyDeletedFromSources(peerId, StorySourcesList::All);
|
||||
return;
|
||||
} else if (user->isSelf()) {
|
||||
result.readTill = result.ids.back().id;
|
||||
}
|
||||
const auto info = result.info();
|
||||
const auto i = _all.find(peerId);
|
||||
|
@ -526,15 +560,20 @@ Story *Stories::parseAndApply(
|
|||
auto &stories = _stories[peer->id];
|
||||
const auto i = stories.find(id);
|
||||
if (i != end(stories)) {
|
||||
if (i->second->applyChanges(*media, data)) {
|
||||
const auto result = i->second.get();
|
||||
const auto pinned = result->pinned();
|
||||
if (result->applyChanges(*media, data)) {
|
||||
if (result->pinned() != pinned) {
|
||||
savedStateUpdated(result);
|
||||
}
|
||||
session().changes().storyUpdated(
|
||||
i->second.get(),
|
||||
result,
|
||||
UpdateFlag::Edited);
|
||||
if (const auto item = lookupItem(i->second.get())) {
|
||||
item->applyChanges(i->second.get());
|
||||
if (const auto item = lookupItem(result)) {
|
||||
item->applyChanges(result);
|
||||
}
|
||||
}
|
||||
return i->second.get();
|
||||
return result;
|
||||
}
|
||||
const auto result = stories.emplace(id, std::make_unique<Story>(
|
||||
id,
|
||||
|
@ -543,6 +582,19 @@ Story *Stories::parseAndApply(
|
|||
data.vdate().v,
|
||||
data.vexpire_date().v)).first->second.get();
|
||||
result->applyChanges(*media, data);
|
||||
if (result->pinned()) {
|
||||
savedStateUpdated(result);
|
||||
}
|
||||
|
||||
if (peer->isSelf()) {
|
||||
const auto added = _archive.list.emplace(id).second;
|
||||
if (added) {
|
||||
if (_archiveTotal >= 0 && id > _archiveLastId) {
|
||||
++_archiveTotal;
|
||||
}
|
||||
_archiveChanged.fire({});
|
||||
}
|
||||
}
|
||||
|
||||
if (expired) {
|
||||
_expiring.remove(expires, result->fullId());
|
||||
|
@ -617,6 +669,30 @@ void Stories::unregisterDependentMessage(
|
|||
}
|
||||
}
|
||||
|
||||
void Stories::savedStateUpdated(not_null<Story*> story) {
|
||||
const auto id = story->id();
|
||||
const auto peer = story->peer()->id;
|
||||
const auto pinned = story->pinned();
|
||||
if (pinned) {
|
||||
auto &saved = _saved[peer];
|
||||
const auto added = saved.ids.list.emplace(id).second;
|
||||
if (added) {
|
||||
if (saved.total >= 0 && id > saved.lastId) {
|
||||
++saved.total;
|
||||
}
|
||||
_savedChanged.fire_copy(peer);
|
||||
}
|
||||
} else if (const auto i = _saved.find(peer); i != end(_saved)) {
|
||||
auto &saved = i->second;
|
||||
if (saved.ids.list.remove(id)) {
|
||||
if (saved.total > 0) {
|
||||
--saved.total;
|
||||
}
|
||||
_savedChanged.fire_copy(peer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Stories::loadMore(StorySourcesList list) {
|
||||
const auto index = static_cast<int>(list);
|
||||
if (_loadMoreRequestId[index] || _sourcesLoaded[index]) {
|
||||
|
@ -744,20 +820,38 @@ 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)) {
|
||||
const auto i = _stories.find(id.peer);
|
||||
if (i != end(_stories)) {
|
||||
const auto j = i->second.find(id.story);
|
||||
if (j != end(i->second)) {
|
||||
// Duplicated in Stories::apply(peer, const MTPUserStories*).
|
||||
auto story = std::move(k->second);
|
||||
auto story = std::move(j->second);
|
||||
_expiring.remove(story->expires(), story->fullId());
|
||||
j->second.erase(k);
|
||||
i->second.erase(j);
|
||||
session().changes().storyUpdated(
|
||||
story.get(),
|
||||
UpdateFlag::Destroyed);
|
||||
removeDependencyStory(story.get());
|
||||
if (j->second.empty()) {
|
||||
_stories.erase(j);
|
||||
if (id.peer == session().userPeerId()
|
||||
&& _archive.list.remove(id.story)) {
|
||||
if (_archiveTotal > 0) {
|
||||
--_archiveTotal;
|
||||
}
|
||||
_archiveChanged.fire({});
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (i->second.empty()) {
|
||||
_stories.erase(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -766,9 +860,7 @@ void Stories::applyDeleted(FullStoryId id) {
|
|||
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()) {
|
||||
if (!story->peer()->isSelf() && !story->pinned()) {
|
||||
applyDeleted(id);
|
||||
return;
|
||||
}
|
||||
|
@ -776,13 +868,6 @@ void Stories::applyExpired(FullStoryId id) {
|
|||
applyRemovedFromActive(id);
|
||||
}
|
||||
|
||||
void Stories::addToExpiredMine(not_null<Story*> story) {
|
||||
const auto added = _expiredMine.list.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);
|
||||
|
@ -1172,24 +1257,24 @@ void Stories::loadViewsSlice(
|
|||
}).send();
|
||||
}
|
||||
|
||||
const StoriesIds &Stories::expiredMine() const {
|
||||
return _expiredMine;
|
||||
const StoriesIds &Stories::archive() const {
|
||||
return _archive;
|
||||
}
|
||||
|
||||
rpl::producer<> Stories::expiredMineChanged() const {
|
||||
return _expiredMineChanged.events();
|
||||
rpl::producer<> Stories::archiveChanged() const {
|
||||
return _archiveChanged.events();
|
||||
}
|
||||
|
||||
int Stories::expiredMineCount() const {
|
||||
return std::max(_expiredMineTotal, 0);
|
||||
int Stories::archiveCount() const {
|
||||
return std::max(_archiveTotal, 0);
|
||||
}
|
||||
|
||||
bool Stories::expiredMineCountKnown() const {
|
||||
return _expiredMineTotal >= 0;
|
||||
bool Stories::archiveCountKnown() const {
|
||||
return _archiveTotal >= 0;
|
||||
}
|
||||
|
||||
bool Stories::expiredMineLoaded() const {
|
||||
return _expiredMineLoaded;
|
||||
bool Stories::archiveLoaded() const {
|
||||
return _archiveLoaded;
|
||||
}
|
||||
|
||||
const StoriesIds *Stories::saved(PeerId peerId) const {
|
||||
|
@ -1216,46 +1301,43 @@ bool Stories::savedLoaded(PeerId peerId) const {
|
|||
return (i != end(_saved)) && i->second.loaded;
|
||||
}
|
||||
|
||||
void Stories::expiredMineLoadMore() {
|
||||
if (_expiredMineRequestId) {
|
||||
void Stories::archiveLoadMore() {
|
||||
if (_archiveRequestId || _archiveLoaded) {
|
||||
return;
|
||||
}
|
||||
const auto api = &_owner->session().api();
|
||||
_expiredMineRequestId = api->request(MTPstories_GetExpiredStories(
|
||||
MTP_int(_expiredMineLastId),
|
||||
MTP_int(_expiredMineLastId
|
||||
? kExpiredMinePerPage
|
||||
: kExpiredMineFirstPerPage)
|
||||
_archiveRequestId = api->request(MTPstories_GetStoriesArchive(
|
||||
MTP_int(_archiveLastId),
|
||||
MTP_int(_archiveLastId ? kArchivePerPage : kArchiveFirstPerPage)
|
||||
)).done([=](const MTPstories_Stories &result) {
|
||||
_expiredMineRequestId = 0;
|
||||
_archiveRequestId = 0;
|
||||
|
||||
const auto &data = result.data();
|
||||
const auto self = _owner->session().user();
|
||||
const auto now = base::unixtime::now();
|
||||
_expiredMineTotal = data.vcount().v;
|
||||
_archiveTotal = data.vcount().v;
|
||||
for (const auto &story : data.vstories().v) {
|
||||
const auto id = story.match([&](const auto &id) {
|
||||
return id.vid().v;
|
||||
});
|
||||
_expiredMine.list.emplace(id);
|
||||
_expiredMineLastId = id;
|
||||
_archive.list.emplace(id);
|
||||
_archiveLastId = id;
|
||||
if (!parseAndApply(self, story, now)) {
|
||||
_expiredMine.list.remove(id);
|
||||
if (_expiredMineTotal > 0) {
|
||||
--_expiredMineTotal;
|
||||
_archive.list.remove(id);
|
||||
if (_archiveTotal > 0) {
|
||||
--_archiveTotal;
|
||||
}
|
||||
}
|
||||
}
|
||||
_expiredMineTotal = std::max(
|
||||
_expiredMineTotal,
|
||||
int(_expiredMine.list.size()));
|
||||
_expiredMineLoaded = data.vstories().v.empty();
|
||||
_expiredMineChanged.fire({});
|
||||
const auto ids = int(_archive.list.size());
|
||||
_archiveLoaded = data.vstories().v.empty();
|
||||
_archiveTotal = _archiveLoaded ? ids : std::max(_archiveTotal, ids);
|
||||
_archiveChanged.fire({});
|
||||
}).fail([=] {
|
||||
_expiredMineRequestId = 0;
|
||||
_expiredMineLoaded = true;
|
||||
_expiredMineTotal = int(_expiredMine.list.size());
|
||||
_expiredMineChanged.fire({});
|
||||
_archiveRequestId = 0;
|
||||
_archiveLoaded = true;
|
||||
_archiveTotal = int(_archive.list.size());
|
||||
_archiveChanged.fire({});
|
||||
}).send();
|
||||
}
|
||||
|
||||
|
@ -1263,7 +1345,7 @@ void Stories::savedLoadMore(PeerId peerId) {
|
|||
Expects(peerIsUser(peerId));
|
||||
|
||||
auto &saved = _saved[peerId];
|
||||
if (saved.requestId) {
|
||||
if (saved.requestId || saved.loaded) {
|
||||
return;
|
||||
}
|
||||
const auto api = &_owner->session().api();
|
||||
|
@ -1292,8 +1374,9 @@ void Stories::savedLoadMore(PeerId peerId) {
|
|||
}
|
||||
}
|
||||
}
|
||||
saved.total = std::max(saved.total, int(saved.ids.list.size()));
|
||||
const auto ids = int(saved.ids.list.size());
|
||||
saved.loaded = data.vstories().v.empty();
|
||||
saved.total = saved.loaded ? ids : std::max(saved.total, ids);
|
||||
_savedChanged.fire_copy(peerId);
|
||||
}).fail([=] {
|
||||
auto &saved = _saved[peerId];
|
||||
|
|
|
@ -230,12 +230,12 @@ public:
|
|||
std::optional<StoryView> offset,
|
||||
Fn<void(std::vector<StoryView>)> done);
|
||||
|
||||
[[nodiscard]] const StoriesIds &expiredMine() const;
|
||||
[[nodiscard]] rpl::producer<> expiredMineChanged() const;
|
||||
[[nodiscard]] int expiredMineCount() const;
|
||||
[[nodiscard]] bool expiredMineCountKnown() const;
|
||||
[[nodiscard]] bool expiredMineLoaded() const;
|
||||
void expiredMineLoadMore();
|
||||
[[nodiscard]] const StoriesIds &archive() 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]] rpl::producer<PeerId> savedChanged() const;
|
||||
|
@ -273,6 +273,7 @@ private:
|
|||
void applyRemovedFromActive(FullStoryId id);
|
||||
void applyDeletedFromSources(PeerId id, StorySourcesList list);
|
||||
void removeDependencyStory(not_null<Story*> story);
|
||||
void savedStateUpdated(not_null<Story*> story);
|
||||
void sort(StorySourcesList list);
|
||||
|
||||
[[nodiscard]] std::shared_ptr<HistoryItem> lookupItem(
|
||||
|
@ -282,7 +283,7 @@ private:
|
|||
void sendMarkAsReadRequest(not_null<PeerData*> peer, StoryId tillId);
|
||||
|
||||
void requestUserStories(not_null<UserData*> user);
|
||||
void addToExpiredMine(not_null<Story*> story);
|
||||
void addToArchive(not_null<Story*> story);
|
||||
void registerExpiring(TimeId expires, FullStoryId id);
|
||||
void scheduleExpireTimer();
|
||||
void processExpired();
|
||||
|
@ -321,12 +322,12 @@ private:
|
|||
rpl::event_stream<PeerId> _sourceChanged;
|
||||
rpl::event_stream<PeerId> _itemsChanged;
|
||||
|
||||
StoriesIds _expiredMine;
|
||||
int _expiredMineTotal = -1;
|
||||
StoryId _expiredMineLastId = 0;
|
||||
bool _expiredMineLoaded = false;
|
||||
rpl::event_stream<> _expiredMineChanged;
|
||||
mtpRequestId _expiredMineRequestId = 0;
|
||||
StoriesIds _archive;
|
||||
int _archiveTotal = -1;
|
||||
StoryId _archiveLastId = 0;
|
||||
bool _archiveLoaded = false;
|
||||
rpl::event_stream<> _archiveChanged;
|
||||
mtpRequestId _archiveRequestId = 0;
|
||||
|
||||
std::unordered_map<PeerId, Saved> _saved;
|
||||
rpl::event_stream<PeerId> _savedChanged;
|
||||
|
|
|
@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#include "data/data_stories_ids.h"
|
||||
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_stories.h"
|
||||
|
@ -23,62 +24,108 @@ rpl::producer<StoriesIdsSlice> SavedStoriesIds(
|
|||
|
||||
struct State {
|
||||
StoriesIdsSlice slice;
|
||||
base::has_weak_ptr guard;
|
||||
bool scheduled = false;
|
||||
};
|
||||
const auto state = lifetime.make_state<State>();
|
||||
|
||||
const auto push = [=] {
|
||||
state->scheduled = false;
|
||||
|
||||
const auto stories = &peer->owner().stories();
|
||||
if (!stories->savedCountKnown(peer->id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto source = stories->source(peer->id);
|
||||
const auto saved = stories->saved(peer->id);
|
||||
const auto count = stories->savedCount(peer->id);
|
||||
const auto around = saved->list.lower_bound(aroundId);
|
||||
Assert(saved != nullptr);
|
||||
auto ids = base::flat_set<StoryId>();
|
||||
ids.reserve(saved->list.size() + 1);
|
||||
auto total = count;
|
||||
if (source && !source->ids.empty()) {
|
||||
++total;
|
||||
const auto current = source->ids.front().id;
|
||||
for (const auto id : ranges::views::reverse(saved->list)) {
|
||||
const auto i = source->ids.lower_bound(
|
||||
StoryIdDates{ id });
|
||||
if (i != end(source->ids) && i->id == id) {
|
||||
--total;
|
||||
} else {
|
||||
const auto source = stories->source(peer->id);
|
||||
if (!source || source->ids.empty()) {
|
||||
const auto hasBefore = int(around - begin(saved->list));
|
||||
const auto hasAfter = int(end(saved->list) - around);
|
||||
if (hasAfter < limit) {
|
||||
stories->savedLoadMore(peer->id);
|
||||
}
|
||||
const auto takeBefore = std::min(hasBefore, limit);
|
||||
const auto takeAfter = std::min(hasAfter, limit);
|
||||
auto ids = base::flat_set<StoryId>{
|
||||
std::make_reverse_iterator(around + takeAfter),
|
||||
std::make_reverse_iterator(around - takeBefore)
|
||||
};
|
||||
const auto added = int(ids.size());
|
||||
state->slice = StoriesIdsSlice(
|
||||
std::move(ids),
|
||||
count,
|
||||
(hasBefore - takeBefore),
|
||||
count - hasBefore - added);
|
||||
} else {
|
||||
auto ids = base::flat_set<StoryId>();
|
||||
auto added = 0;
|
||||
auto skipped = 0;
|
||||
auto skippedBefore = (around - begin(saved->list));
|
||||
auto skippedAfter = (end(saved->list) - around);
|
||||
const auto &active = source->ids;
|
||||
const auto process = [&](StoryId id) {
|
||||
const auto i = active.lower_bound(StoryIdDates{ id });
|
||||
if (i == end(active) || i->id != id) {
|
||||
ids.emplace(id);
|
||||
++added;
|
||||
} else {
|
||||
++skipped;
|
||||
}
|
||||
return (added < limit);
|
||||
};
|
||||
ids.reserve(2 * limit + 1);
|
||||
for (auto i = around, b = begin(saved->list); i != b;) {
|
||||
--skippedBefore;
|
||||
if (!process(*--i)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
ids.emplace(current);
|
||||
} else {
|
||||
auto all = saved->list | ranges::views::reverse;
|
||||
ids = { begin(all), end(all) };
|
||||
if (ids.size() < limit) {
|
||||
ids.emplace(active.back().id); // #TODO stories fake max story id
|
||||
} else {
|
||||
++skippedBefore;
|
||||
}
|
||||
added = 0;
|
||||
for (auto i = around, e = end(saved->list); i != e; ++i) {
|
||||
--skippedAfter;
|
||||
if (!process(*i)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
state->slice = StoriesIdsSlice(
|
||||
std::move(ids),
|
||||
count - skipped + 1,
|
||||
skippedBefore,
|
||||
skippedAfter);
|
||||
}
|
||||
const auto added = int(ids.size());
|
||||
state->slice = StoriesIdsSlice(
|
||||
std::move(ids),
|
||||
total,
|
||||
0,
|
||||
total - added);
|
||||
consumer.put_next_copy(state->slice);
|
||||
};
|
||||
const auto schedule = [=] {
|
||||
if (state->scheduled) {
|
||||
return;
|
||||
}
|
||||
state->scheduled = true;
|
||||
Ui::PostponeCall(&state->guard, [=] {
|
||||
if (state->scheduled) {
|
||||
push();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const auto stories = &peer->owner().stories();
|
||||
stories->sourceChanged(
|
||||
) | rpl::filter(
|
||||
rpl::mappers::_1 == peer->id
|
||||
) | rpl::start_with_next([=] {
|
||||
push();
|
||||
}, lifetime);
|
||||
) | rpl::start_with_next(schedule, lifetime);
|
||||
|
||||
stories->savedChanged(
|
||||
) | rpl::filter(
|
||||
rpl::mappers::_1 == peer->id
|
||||
) | rpl::start_with_next([=] {
|
||||
push();
|
||||
}, lifetime);
|
||||
) | rpl::start_with_next(schedule, lifetime);
|
||||
|
||||
if (!stories->savedCountKnown(peer->id)) {
|
||||
stories->savedLoadMore(peer->id);
|
||||
|
@ -99,38 +146,59 @@ rpl::producer<StoriesIdsSlice> ArchiveStoriesIds(
|
|||
|
||||
struct State {
|
||||
StoriesIdsSlice slice;
|
||||
base::has_weak_ptr guard;
|
||||
bool scheduled = false;
|
||||
};
|
||||
const auto state = lifetime.make_state<State>();
|
||||
|
||||
const auto push = [=] {
|
||||
state->scheduled = false;
|
||||
|
||||
const auto stories = &session->data().stories();
|
||||
if (!stories->expiredMineCountKnown()) {
|
||||
if (!stories->archiveCountKnown()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto expired = stories->expiredMine();
|
||||
const auto count = stories->expiredMineCount();
|
||||
auto ids = base::flat_set<StoryId>();
|
||||
ids.reserve(expired.list.size() + 1);
|
||||
auto all = expired.list | ranges::views::reverse;
|
||||
ids = { begin(all), end(all) };
|
||||
const auto &archive = stories->archive();
|
||||
const auto count = stories->archiveCount();
|
||||
const auto i = archive.list.lower_bound(aroundId);
|
||||
const auto hasBefore = int(i - begin(archive.list));
|
||||
const auto hasAfter = int(end(archive.list) - i);
|
||||
if (hasAfter < limit) {
|
||||
stories->archiveLoadMore();
|
||||
}
|
||||
const auto takeBefore = std::min(hasBefore, limit);
|
||||
const auto takeAfter = std::min(hasAfter, limit);
|
||||
auto ids = base::flat_set<StoryId>{
|
||||
std::make_reverse_iterator(i + takeAfter),
|
||||
std::make_reverse_iterator(i - takeBefore)
|
||||
};
|
||||
const auto added = int(ids.size());
|
||||
state->slice = StoriesIdsSlice(
|
||||
std::move(ids),
|
||||
count,
|
||||
0,
|
||||
count - added);
|
||||
(hasBefore - takeBefore),
|
||||
count - hasBefore - added);
|
||||
consumer.put_next_copy(state->slice);
|
||||
};
|
||||
const auto schedule = [=] {
|
||||
if (state->scheduled) {
|
||||
return;
|
||||
}
|
||||
state->scheduled = true;
|
||||
Ui::PostponeCall(&state->guard, [=] {
|
||||
if (state->scheduled) {
|
||||
push();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const auto stories = &session->data().stories();
|
||||
stories->expiredMineChanged(
|
||||
) | rpl::start_with_next([=] {
|
||||
push();
|
||||
}, lifetime);
|
||||
stories->archiveChanged(
|
||||
) | rpl::start_with_next(schedule, lifetime);
|
||||
|
||||
if (!stories->expiredMineCountKnown()) {
|
||||
stories->expiredMineLoadMore();
|
||||
if (!stories->archiveCountKnown()) {
|
||||
stories->archiveLoadMore();
|
||||
}
|
||||
|
||||
push();
|
||||
|
|
|
@ -266,7 +266,8 @@ bool Controller::validateMementoPeer(
|
|||
not_null<ContentMemento*> memento) const {
|
||||
return memento->peer() == peer()
|
||||
&& memento->migratedPeerId() == migratedPeerId()
|
||||
&& memento->settingsSelf() == settingsSelf();
|
||||
&& memento->settingsSelf() == settingsSelf()
|
||||
&& memento->storiesPeer() == storiesPeer();
|
||||
}
|
||||
|
||||
void Controller::setSection(not_null<ContentMemento*> memento) {
|
||||
|
|
|
@ -131,13 +131,13 @@ inline auto AddStoriesButton(
|
|||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<UserData*> user,
|
||||
Ui::MultiSlideTracker &tracker) {
|
||||
auto count = Data::SavedStoriesIds(
|
||||
auto count = rpl::single(0) | rpl::then(Data::SavedStoriesIds(
|
||||
user,
|
||||
ServerMaxStoryId - 1,
|
||||
0
|
||||
) | rpl::map([](const Data::StoriesIdsSlice &slice) {
|
||||
return slice.fullCount();
|
||||
}) | rpl::filter_optional();
|
||||
return slice.fullCount().value_or(0);
|
||||
}));
|
||||
auto result = AddCountedButton(
|
||||
parent,
|
||||
std::move(count),
|
||||
|
|
|
@ -20,10 +20,10 @@ class SearchFieldController;
|
|||
} // namespace Ui
|
||||
|
||||
namespace Info {
|
||||
|
||||
class Controller;
|
||||
} // namespace Info
|
||||
|
||||
namespace Media {
|
||||
namespace Info::Media {
|
||||
|
||||
class Memento;
|
||||
class ListWidget;
|
||||
|
@ -86,5 +86,4 @@ private:
|
|||
|
||||
};
|
||||
|
||||
} // namespace Media
|
||||
} // namespace Info
|
||||
} // namespace Info::Media
|
||||
|
|
|
@ -7,10 +7,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#include "info/stories/info_stories_inner_widget.h"
|
||||
|
||||
#include "info/stories/info_stories_widget.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "info/media/info_media_list_widget.h"
|
||||
#include "info/profile/info_profile_icon.h"
|
||||
#include "info/stories/info_stories_widget.h"
|
||||
#include "info/info_controller.h"
|
||||
#include "info/info_memento.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "settings/settings_common.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "styles/style_info.h"
|
||||
|
||||
namespace Info::Stories {
|
||||
|
@ -83,6 +90,49 @@ InnerWidget::InnerWidget(
|
|||
_list = setupList();
|
||||
}
|
||||
|
||||
void InnerWidget::setupArchive() {
|
||||
const auto key = _controller->key();
|
||||
const auto peer = key.storiesPeer();
|
||||
if (peer
|
||||
&& peer->isSelf()
|
||||
&& key.storiesTab() == Stories::Tab::Saved
|
||||
&& _isStackBottom) {
|
||||
createArchiveButton();
|
||||
} else {
|
||||
_archive.destroy();
|
||||
refreshHeight();
|
||||
}
|
||||
}
|
||||
|
||||
void InnerWidget::createArchiveButton() {
|
||||
_archive.create(this);
|
||||
_archive->show();
|
||||
|
||||
const auto button = ::Settings::AddButton(
|
||||
_archive,
|
||||
tr::lng_stories_archive_button(),
|
||||
st::infoSharedMediaButton);
|
||||
button->addClickHandler([=] {
|
||||
_controller->showSection(Info::Stories::Make(
|
||||
_controller->key().storiesPeer(),
|
||||
Stories::Tab::Archive));
|
||||
});
|
||||
object_ptr<Profile::FloatingIcon>(
|
||||
button,
|
||||
st::infoIconMediaGroup,
|
||||
st::infoSharedMediaButtonIconPosition)->show();
|
||||
_archive->add(object_ptr<Ui::FixedHeightWidget>(
|
||||
_archive,
|
||||
st::infoProfileSkip));
|
||||
_archive->add(object_ptr<Ui::BoxContentDivider>(_archive));
|
||||
|
||||
_archive->resizeToWidth(width());
|
||||
_archive->heightValue(
|
||||
) | rpl::start_with_next([=] {
|
||||
refreshHeight();
|
||||
}, _archive->lifetime());
|
||||
}
|
||||
|
||||
void InnerWidget::visibleTopBottomUpdated(
|
||||
int visibleTop,
|
||||
int visibleBottom) {
|
||||
|
@ -144,6 +194,9 @@ int InnerWidget::resizeGetHeight(int newWidth) {
|
|||
_inResize = true;
|
||||
auto guard = gsl::finally([this] { _inResize = false; });
|
||||
|
||||
if (_archive) {
|
||||
_archive->resizeToWidth(newWidth);
|
||||
}
|
||||
_list->resizeToWidth(newWidth);
|
||||
_empty->resizeToWidth(newWidth);
|
||||
return recountHeight();
|
||||
|
@ -158,6 +211,10 @@ void InnerWidget::refreshHeight() {
|
|||
|
||||
int InnerWidget::recountHeight() {
|
||||
auto top = 0;
|
||||
if (_archive) {
|
||||
_archive->moveToLeft(0, top);
|
||||
top += _archive->heightNoMargins() - st::lineWidth;
|
||||
}
|
||||
auto listHeight = 0;
|
||||
if (_list) {
|
||||
_list->moveToLeft(0, top);
|
||||
|
|
|
@ -38,6 +38,10 @@ public:
|
|||
not_null<Controller*> controller);
|
||||
|
||||
bool showInternal(not_null<Memento*> memento);
|
||||
void setIsStackBottom(bool isStackBottom) {
|
||||
_isStackBottom = isStackBottom;
|
||||
setupArchive();
|
||||
}
|
||||
|
||||
void saveState(not_null<Memento*> memento);
|
||||
void restoreState(not_null<Memento*> memento);
|
||||
|
@ -60,14 +64,19 @@ private:
|
|||
int recountHeight();
|
||||
void refreshHeight();
|
||||
|
||||
void setupArchive();
|
||||
void createArchiveButton();
|
||||
|
||||
object_ptr<Media::ListWidget> setupList();
|
||||
|
||||
const not_null<Controller*> _controller;
|
||||
|
||||
object_ptr<Ui::VerticalLayout> _archive = { nullptr };
|
||||
object_ptr<Media::ListWidget> _list = { nullptr };
|
||||
object_ptr<EmptyWidget> _empty;
|
||||
|
||||
bool _inResize = false;
|
||||
bool _isStackBottom = false;
|
||||
|
||||
rpl::event_stream<Ui::ScrollToRequest> _scrollToRequests;
|
||||
rpl::event_stream<rpl::producer<SelectedItems>> _selectedLists;
|
||||
|
|
|
@ -20,8 +20,8 @@ Memento::Memento(not_null<Controller*> controller)
|
|||
, _media(controller) {
|
||||
}
|
||||
|
||||
Memento::Memento(not_null<PeerData*> peer)
|
||||
: ContentMemento(Tag{ peer })
|
||||
Memento::Memento(not_null<PeerData*> peer, Tab tab)
|
||||
: ContentMemento(Tag{ peer, tab })
|
||||
, _media(peer, 0, Media::Type::PhotoVideo) {
|
||||
}
|
||||
|
||||
|
@ -54,13 +54,21 @@ Widget::Widget(
|
|||
}, _inner->lifetime());
|
||||
}
|
||||
|
||||
void Widget::setIsStackBottom(bool isStackBottom) {
|
||||
ContentWidget::setIsStackBottom(isStackBottom);
|
||||
_inner->setIsStackBottom(isStackBottom);
|
||||
}
|
||||
|
||||
bool Widget::showInternal(not_null<ContentMemento*> memento) {
|
||||
if (!controller()->validateMementoPeer(memento)) {
|
||||
return false;
|
||||
}
|
||||
if (auto storiesMemento = dynamic_cast<Memento*>(memento.get())) {
|
||||
restoreState(storiesMemento);
|
||||
return true;
|
||||
const auto tab = controller()->key().storiesTab();
|
||||
if (storiesMemento->storiesTab() == tab) {
|
||||
restoreState(storiesMemento);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -98,14 +106,16 @@ void Widget::selectionAction(SelectionAction action) {
|
|||
}
|
||||
|
||||
rpl::producer<QString> Widget::title() {
|
||||
return tr::lng_menu_my_stories(); // #TODO stories
|
||||
return (controller()->key().storiesTab() == Tab::Archive)
|
||||
? tr::lng_stories_archive_title()
|
||||
: tr::lng_stories_my_title();
|
||||
}
|
||||
|
||||
std::shared_ptr<Info::Memento> Make(not_null<PeerData*> peer) {
|
||||
std::shared_ptr<Info::Memento> Make(not_null<PeerData*> peer, Tab tab) {
|
||||
return std::make_shared<Info::Memento>(
|
||||
std::vector<std::shared_ptr<ContentMemento>>(
|
||||
1,
|
||||
std::make_shared<Memento>(peer)));
|
||||
std::make_shared<Memento>(peer, tab)));
|
||||
}
|
||||
|
||||
} // namespace Info::Stories
|
||||
|
|
|
@ -13,11 +13,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
namespace Info::Stories {
|
||||
|
||||
class InnerWidget;
|
||||
enum class Tab;
|
||||
|
||||
class Memento final : public ContentMemento {
|
||||
public:
|
||||
Memento(not_null<Controller*> controller);
|
||||
Memento(not_null<PeerData*> peer);
|
||||
Memento(not_null<PeerData*> peer, Tab tab);
|
||||
~Memento();
|
||||
|
||||
object_ptr<ContentWidget> createWidget(
|
||||
|
@ -43,6 +44,8 @@ class Widget final : public ContentWidget {
|
|||
public:
|
||||
Widget(QWidget *parent, not_null<Controller*> controller);
|
||||
|
||||
void setIsStackBottom(bool isStackBottom) override;
|
||||
|
||||
bool showInternal(
|
||||
not_null<ContentMemento*> memento) override;
|
||||
|
||||
|
@ -65,6 +68,8 @@ private:
|
|||
|
||||
};
|
||||
|
||||
[[nodiscard]] std::shared_ptr<Info::Memento> Make(not_null<PeerData*> peer);
|
||||
[[nodiscard]] std::shared_ptr<Info::Memento> Make(
|
||||
not_null<PeerData*> peer,
|
||||
Tab tab = {});
|
||||
|
||||
} // namespace Info::Stories
|
||||
|
|
|
@ -2093,7 +2093,7 @@ stories.togglePinned#51602944 id:Vector<int> pinned:Bool = Vector<int>;
|
|||
stories.getAllStories#eeb0d625 flags:# next:flags.1?true include_hidden:flags.2?true state:flags.0?string = stories.AllStories;
|
||||
stories.getUserStories#96d528e0 user_id:InputUser = stories.UserStories;
|
||||
stories.getPinnedStories#b471137 user_id:InputUser offset_id:int limit:int = stories.Stories;
|
||||
stories.getExpiredStories#8f792f2 offset_id:int limit:int = stories.Stories;
|
||||
stories.getStoriesArchive#1f5bc5d2 offset_id:int limit:int = stories.Stories;
|
||||
stories.getStoriesByID#6a15cf46 user_id:InputUser id:Vector<int> = stories.Stories;
|
||||
stories.readStories#edc5105b user_id:InputUser max_id:int = Vector<int>;
|
||||
stories.incrementStoryViews#22126127 user_id:InputUser id:Vector<int> = Bool;
|
||||
|
|
|
@ -104,10 +104,7 @@ settingsIconMinus: icon {{ "settings/minus", settingsIconFg }};
|
|||
settingsIconTimer: icon {{ "settings/timer", settingsIconFg }};
|
||||
settingsIconLaptop: icon {{ "settings/laptop", settingsIconFg }};
|
||||
settingsIconArrows: icon {{ "settings/arrows", settingsIconFg }};
|
||||
settingsIconOnline: icon {{ "settings/online", settingsIconFg }};
|
||||
settingsIconVideoCalls: icon {{ "settings/video_calls", settingsIconFg }};
|
||||
settingsIconEmail: icon {{ "settings/email", settingsIconFg }};
|
||||
settingsIconForward: icon {{ "settings/forward", settingsIconFg }};
|
||||
settingsIconSound: icon {{ "settings/sound", settingsIconFg }};
|
||||
settingsIconDock: icon {{ "settings/dock", settingsIconFg }};
|
||||
settingsIconPin: icon {{ "settings/pin", settingsIconFg }};
|
||||
|
|
|
@ -125,17 +125,15 @@ void AddPremiumPrivacyButton(
|
|||
not_null<Window::SessionController*> controller,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
rpl::producer<QString> label,
|
||||
IconDescriptor &&descriptor,
|
||||
Privacy::Key key,
|
||||
Fn<std::unique_ptr<EditPrivacyController>()> controllerFactory) {
|
||||
const auto shower = Ui::CreateChild<rpl::lifetime>(container.get());
|
||||
const auto session = &controller->session();
|
||||
const auto &st = st::settingsButton;
|
||||
const auto &st = st::settingsButtonNoIcon;
|
||||
const auto button = AddButton(
|
||||
container,
|
||||
rpl::duplicate(label),
|
||||
st,
|
||||
std::move(descriptor));
|
||||
st);
|
||||
struct State {
|
||||
State(QWidget *parent) : widget(parent) {
|
||||
widget.setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
@ -260,59 +258,50 @@ void SetupPrivacy(
|
|||
using Key = Privacy::Key;
|
||||
const auto add = [&](
|
||||
rpl::producer<QString> label,
|
||||
IconDescriptor &&descriptor,
|
||||
Key key,
|
||||
auto controllerFactory) {
|
||||
AddPrivacyButton(
|
||||
controller,
|
||||
container,
|
||||
std::move(label),
|
||||
std::move(descriptor),
|
||||
{},
|
||||
key,
|
||||
controllerFactory);
|
||||
};
|
||||
add(
|
||||
tr::lng_settings_phone_number_privacy(),
|
||||
{ &st::settingsIconCalls, kIconGreen },
|
||||
Key::PhoneNumber,
|
||||
[=] { return std::make_unique<PhoneNumberPrivacyController>(
|
||||
controller); });
|
||||
add(
|
||||
tr::lng_settings_last_seen(),
|
||||
{ &st::settingsIconOnline, kIconLightBlue },
|
||||
Key::LastSeen,
|
||||
[=] { return std::make_unique<LastSeenPrivacyController>(session); });
|
||||
add(
|
||||
tr::lng_settings_profile_photo_privacy(),
|
||||
{ &st::settingsIconAccount, kIconRed },
|
||||
Key::ProfilePhoto,
|
||||
[] { return std::make_unique<ProfilePhotoPrivacyController>(); });
|
||||
add(
|
||||
tr::lng_settings_forwards_privacy(),
|
||||
{ &st::settingsIconForward, kIconLightOrange },
|
||||
Key::Forwards,
|
||||
[=] { return std::make_unique<ForwardsPrivacyController>(
|
||||
controller); });
|
||||
add(
|
||||
tr::lng_settings_calls(),
|
||||
{ &st::settingsIconVideoCalls, kIconGreen },
|
||||
Key::Calls,
|
||||
[] { return std::make_unique<CallsPrivacyController>(); });
|
||||
add(
|
||||
tr::lng_settings_groups_invite(),
|
||||
{ &st::settingsIconGroup, kIconDarkBlue },
|
||||
Key::Invites,
|
||||
[] { return std::make_unique<GroupsInvitePrivacyController>(); });
|
||||
AddPremiumPrivacyButton(
|
||||
controller,
|
||||
container,
|
||||
tr::lng_settings_voices_privacy(),
|
||||
{ &st::settingsPremiumIconVoice, kIconRed },
|
||||
Key::Voices,
|
||||
[=] { return std::make_unique<VoicesPrivacyController>(session); });
|
||||
add(
|
||||
tr::lng_settings_bio_privacy(),
|
||||
{ &st::settingsIconAccount, kIconDarkOrange },
|
||||
Key::About,
|
||||
[] { return std::make_unique<AboutPrivacyController>(); });
|
||||
|
||||
|
@ -832,7 +821,7 @@ void AddPrivacyButton(
|
|||
container,
|
||||
std::move(label),
|
||||
PrivacyString(session, key),
|
||||
st::settingsButton,
|
||||
st::settingsButtonNoIcon,
|
||||
std::move(descriptor)
|
||||
)->addClickHandler([=] {
|
||||
*shower = session->api().userPrivacy().value(
|
||||
|
|
|
@ -775,18 +775,15 @@ void MainMenu::setupMenu() {
|
|||
kIconLightOrange
|
||||
})));
|
||||
const auto stories = &controller->session().data().stories();
|
||||
const auto mine = stories->source(
|
||||
controller->session().userPeerId());
|
||||
if ((mine && !mine->ids.empty())
|
||||
|| stories->expiredMineCount() > 0) {
|
||||
if (stories->archiveCount() > 0) {
|
||||
wrap->toggle(true, anim::type::instant);
|
||||
} else {
|
||||
wrap->toggle(false, anim::type::instant);
|
||||
if (!stories->expiredMineCountKnown()) {
|
||||
stories->expiredMineLoadMore();
|
||||
wrap->toggleOn(stories->expiredMineChanged(
|
||||
if (!stories->archiveCountKnown()) {
|
||||
stories->archiveLoadMore();
|
||||
wrap->toggleOn(stories->archiveChanged(
|
||||
) | rpl::map([=] {
|
||||
return stories->expiredMineCount() > 0;
|
||||
return stories->archiveCount() > 0;
|
||||
}) | rpl::filter(rpl::mappers::_1) | rpl::take(1));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue