Support two lists of stories sources.

This commit is contained in:
John Preston 2023-06-02 18:26:39 +04:00
parent d0e1ac1238
commit f40391b4f0
18 changed files with 462 additions and 284 deletions

View file

@ -314,7 +314,7 @@ Session::Session(not_null<Main::Session*> session)
} }
}, _lifetime); }, _lifetime);
_stories->loadMore(); _stories->loadMore(Data::StorySourcesList::NotHidden);
}); });
} }

View file

@ -35,7 +35,7 @@ constexpr auto kMarkAsReadDelay = 3 * crl::time(1000);
using UpdateFlag = StoryUpdate::Flag; using UpdateFlag = StoryUpdate::Flag;
std::optional<StoryMedia> ParseMedia( [[nodiscard]] std::optional<StoryMedia> ParseMedia(
not_null<Session*> owner, not_null<Session*> owner,
const MTPMessageMedia &media) { const MTPMessageMedia &media) {
return media.match([&](const MTPDmessageMediaPhoto &data) return media.match([&](const MTPDmessageMediaPhoto &data)
@ -62,8 +62,18 @@ std::optional<StoryMedia> ParseMedia(
} // namespace } // namespace
bool StoriesList::unread() const { StoriesSourceInfo StoriesSource::info() const {
return !ids.empty() && readTill < ids.back(); return {
.id = user->id,
.last = ids.empty() ? 0 : ids.back().date,
.unread = unread(),
.premium = user->isPremium(),
.hidden = hidden,
};
}
bool StoriesSource::unread() const {
return !ids.empty() && readTill < ids.back().id;
} }
Story::Story( Story::Story(
@ -93,6 +103,10 @@ StoryId Story::id() const {
return _id; return _id;
} }
StoryIdDate Story::idDate() const {
return { _id, _date };
}
FullStoryId Story::fullId() const { FullStoryId Story::fullId() const {
return { _peer->id, _id }; return { _peer->id, _id };
} }
@ -278,51 +292,109 @@ Main::Session &Stories::session() const {
void Stories::apply(const MTPDupdateStory &data) { void Stories::apply(const MTPDupdateStory &data) {
const auto peerId = peerFromUser(data.vuser_id()); const auto peerId = peerFromUser(data.vuser_id());
const auto peer = _owner->peer(peerId); const auto user = not_null(_owner->peer(peerId)->asUser());
const auto i = ranges::find(_all, peer, &StoriesList::user); const auto idDate = parseAndApply(user, data.vstory());
const auto id = parseAndApply(peer, data.vstory()); if (!idDate) {
if (i != end(_all)) { return;
auto added = false; }
if (id && !i->ids.contains(id)) { const auto i = _all.find(peerId);
i->ids.emplace(id); if (i == end(_all)) {
++i->total; requestUserStories(user);
added = true; return;
} } else if (i->second.ids.contains(idDate)) {
if (added) { return;
ranges::rotate(begin(_all), i, i + 1); }
} const auto was = i->second.info();
} else if (id) { i->second.ids.emplace(idDate);
_all.insert(begin(_all), StoriesList{ const auto now = i->second.info();
.user = peer->asUser(), if (was == now) {
.ids = { id }, return;
.readTill = 0, }
.total = 1, const auto refreshInList = [&](StorySourcesList list) {
}); auto &sources = _sources[static_cast<int>(list)];
const auto i = ranges::find(
sources,
peerId,
&StoriesSourceInfo::id);
if (i != end(sources)) {
*i = now;
sort(list);
}
};
refreshInList(StorySourcesList::All);
if (!user->hasStoriesHidden()) {
refreshInList(StorySourcesList::NotHidden);
} }
_allChanged.fire({});
} }
StoriesList Stories::parse(const MTPUserStories &stories) { void Stories::requestUserStories(not_null<UserData*> user) {
if (!_requestingUserStories.emplace(user).second) {
return;
}
_owner->session().api().request(MTPstories_GetUserStories(
user->inputUser
)).done([=](const MTPstories_UserStories &result) {
_requestingUserStories.remove(user);
const auto &data = result.data();
_owner->processUsers(data.vusers());
parseAndApply(data.vstories());
}).fail([=] {
_requestingUserStories.remove(user);
applyDeletedFromSources(user->id, StorySourcesList::All);
}).send();
}
void Stories::parseAndApply(const MTPUserStories &stories) {
const auto &data = stories.data(); const auto &data = stories.data();
const auto userId = UserId(data.vuser_id()); const auto peerId = peerFromUser(data.vuser_id());
const auto readTill = data.vmax_read_id().value_or_empty(); const auto readTill = data.vmax_read_id().value_or_empty();
const auto count = int(data.vstories().v.size()); const auto count = int(data.vstories().v.size());
auto result = StoriesList{ auto result = StoriesSource{
.user = _owner->user(userId), .user = _owner->peer(peerId)->asUser(),
.readTill = readTill, .readTill = readTill,
.total = count,
}; };
const auto &list = data.vstories().v; const auto &list = data.vstories().v;
result.ids.reserve(list.size()); result.ids.reserve(list.size());
for (const auto &story : list) { for (const auto &story : list) {
if (const auto id = parseAndApply(result.user, story)) { if (const auto id = parseAndApply(result.user, story)) {
result.ids.emplace(id); result.ids.emplace(id);
} else {
--result.total;
} }
} }
result.total = std::max(result.total, int(result.ids.size())); if (result.ids.empty()) {
return result; applyDeletedFromSources(peerId, StorySourcesList::All);
return;
}
const auto info = result.info();
const auto i = _all.find(peerId);
if (i != end(_all)) {
if (i->second != result) {
i->second = std::move(result);
}
} else {
_all.emplace(peerId, std::move(result)).first;
}
const auto add = [&](StorySourcesList list) {
auto &sources = _sources[static_cast<int>(list)];
const auto i = ranges::find(
sources,
peerId,
&StoriesSourceInfo::id);
if (i == end(sources)) {
sources.push_back(info);
} else if (*i == info) {
return;
} else {
*i = info;
}
sort(list);
};
add(StorySourcesList::All);
if (result.user->hasStoriesHidden()) {
applyDeletedFromSources(peerId, StorySourcesList::NotHidden);
} else {
add(StorySourcesList::NotHidden);
}
} }
Story *Stories::parseAndApply( Story *Stories::parseAndApply(
@ -352,20 +424,20 @@ Story *Stories::parseAndApply(
return result; return result;
} }
StoryId Stories::parseAndApply( StoryIdDate Stories::parseAndApply(
not_null<PeerData*> peer, not_null<PeerData*> peer,
const MTPstoryItem &story) { const MTPstoryItem &story) {
return story.match([&](const MTPDstoryItem &data) { return story.match([&](const MTPDstoryItem &data) {
if (const auto story = parseAndApply(peer, data)) { if (const auto story = parseAndApply(peer, data)) {
return story->id(); return story->idDate();
} }
applyDeleted({ peer->id, data.vid().v }); applyDeleted({ peer->id, data.vid().v });
return StoryId(); return StoryIdDate();
}, [&](const MTPDstoryItemSkipped &data) { }, [&](const MTPDstoryItemSkipped &data) {
return StoryId(data.vid().v); return StoryIdDate{ data.vid().v, data.vdate().v };
}, [&](const MTPDstoryItemDeleted &data) { }, [&](const MTPDstoryItemDeleted &data) {
applyDeleted({ peer->id, data.vid().v }); applyDeleted({ peer->id, data.vid().v });
return StoryId(); return StoryIdDate();
}); });
} }
@ -398,32 +470,34 @@ void Stories::unregisterDependentMessage(
} }
} }
void Stories::loadMore() { void Stories::loadMore(StorySourcesList list) {
if (_loadMoreRequestId || _allLoaded) { const auto index = static_cast<int>(list);
if (_loadMoreRequestId[index] || _sourcesLoaded[index]) {
return; return;
} }
const auto all = (list == StorySourcesList::All);
const auto api = &_owner->session().api(); const auto api = &_owner->session().api();
using Flag = MTPstories_GetAllStories::Flag; using Flag = MTPstories_GetAllStories::Flag;
_loadMoreRequestId = api->request(MTPstories_GetAllStories( _loadMoreRequestId[index] = api->request(MTPstories_GetAllStories(
MTP_flags(_state.isEmpty() MTP_flags((all ? Flag::f_include_hidden : Flag())
? Flag(0) | (_sourcesStates[index].isEmpty()
: (Flag::f_next | Flag::f_state)), ? Flag(0)
MTP_string(_state) : (Flag::f_next | Flag::f_state))),
MTP_string(_sourcesStates[index])
)).done([=](const MTPstories_AllStories &result) { )).done([=](const MTPstories_AllStories &result) {
_loadMoreRequestId = 0; _loadMoreRequestId[index] = 0;
result.match([&](const MTPDstories_allStories &data) { result.match([&](const MTPDstories_allStories &data) {
_owner->processUsers(data.vusers()); _owner->processUsers(data.vusers());
_state = qs(data.vstate()); _sourcesStates[index] = qs(data.vstate());
_allLoaded = !data.is_has_more(); _sourcesLoaded[index] = !data.is_has_more();
for (const auto &single : data.vuser_stories().v) { for (const auto &single : data.vuser_stories().v) {
pushToBack(parse(single)); parseAndApply(single);
} }
_allChanged.fire({});
}, [](const MTPDstories_allStoriesNotModified &) { }, [](const MTPDstories_allStoriesNotModified &) {
}); });
}).fail([=] { }).fail([=] {
_loadMoreRequestId = 0; _loadMoreRequestId[index] = 0;
}).send(); }).send();
} }
@ -519,37 +593,64 @@ void Stories::finalizeResolve(FullStoryId id) {
} }
void Stories::applyDeleted(FullStoryId id) { void Stories::applyDeleted(FullStoryId id) {
const auto i = _stories.find(id.peer); const auto removeFromList = [&](StorySourcesList list) {
if (i != end(_stories)) { const auto index = static_cast<int>(list);
const auto j = i->second.find(id.story); auto &sources = _sources[index];
if (j != end(i->second)) { const auto i = ranges::find(
auto story = std::move(j->second); sources,
i->second.erase(j); id.peer,
&StoriesSourceInfo::id);
if (i != end(sources)) {
sources.erase(i);
_sourcesChanged[index].fire({});
}
};
const auto i = _all.find(id.peer);
if (i != end(_all)) {
const auto j = i->second.ids.lower_bound(StoryIdDate{ id.story });
if (j != end(i->second.ids) && j->id == id.story) {
i->second.ids.erase(j);
if (i->second.ids.empty()) {
_all.erase(i);
removeFromList(StorySourcesList::NotHidden);
removeFromList(StorySourcesList::All);
}
}
}
_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( session().changes().storyUpdated(
story.get(), story.get(),
UpdateFlag::Destroyed); UpdateFlag::Destroyed);
removeDependencyStory(story.get()); removeDependencyStory(story.get());
if (i->second.empty()) { if (j->second.empty()) {
_stories.erase(i); _stories.erase(j);
} }
} }
} }
const auto j = ranges::find(_all, id.peer, [](const StoriesList &list) { }
return list.user->id;
}); void Stories::applyDeletedFromSources(PeerId id, StorySourcesList list) {
if (j != end(_all)) { const auto removeFromList = [&](StorySourcesList from) {
const auto removed = j->ids.remove(id.story); auto &sources = _sources[static_cast<int>(from)];
if (removed) { const auto i = ranges::find(
if (j->ids.empty()) { sources,
_all.erase(j); id,
} else { &StoriesSourceInfo::id);
Assert(j->total > 0); if (i != end(sources)) {
--j->total; sources.erase(i);
}
_allChanged.fire({});
} }
_sourcesChanged[static_cast<int>(from)].fire({});
};
removeFromList(StorySourcesList::NotHidden);
if (list == StorySourcesList::All) {
removeFromList(StorySourcesList::All);
} }
_deleted.emplace(id);
} }
void Stories::removeDependencyStory(not_null<Story*> story) { void Stories::removeDependencyStory(not_null<Story*> story) {
@ -564,16 +665,36 @@ void Stories::removeDependencyStory(not_null<Story*> story) {
} }
} }
const std::vector<StoriesList> &Stories::all() { void Stories::sort(StorySourcesList list) {
const auto index = static_cast<int>(list);
auto &sources = _sources[index];
const auto self = _owner->session().user()->id;
const auto proj = [&](const StoriesSourceInfo &info) {
const auto key = int64(info.last)
+ (info.premium ? (int64(1) << 48) : 0)
+ (info.unread ? (int64(1) << 49) : 0)
+ ((info.id == self) ? (int64(1) << 50) : 0);
return std::make_pair(key, info.id);
};
ranges::sort(sources, ranges::greater(), proj);
_sourcesChanged[index].fire({});
}
const base::flat_map<PeerId, StoriesSource> &Stories::all() const {
return _all; return _all;
} }
bool Stories::allLoaded() const { const std::vector<StoriesSourceInfo> &Stories::sources(
return _allLoaded; StorySourcesList list) const {
return _sources[static_cast<int>(list)];
} }
rpl::producer<> Stories::allChanged() const { bool Stories::sourcesLoaded(StorySourcesList list) const {
return _allChanged.events(); return _sourcesLoaded[static_cast<int>(list)];
}
rpl::producer<> Stories::sourcesChanged(StorySourcesList list) const {
return _sourcesChanged[static_cast<int>(list)].events();
} }
rpl::producer<PeerId> Stories::itemsChanged() const { rpl::producer<PeerId> Stories::itemsChanged() const {
@ -621,35 +742,21 @@ void Stories::resolve(FullStoryId id, Fn<void()> done) {
} }
} }
void Stories::pushToBack(StoriesList &&list) {
const auto i = ranges::find(_all, list.user, &StoriesList::user);
if (i != end(_all)) {
if (*i == list) {
return;
}
*i = std::move(list);
} else {
_all.push_back(std::move(list));
}
}
void Stories::loadAround(FullStoryId id) { void Stories::loadAround(FullStoryId id) {
const auto i = ranges::find(_all, id.peer, [](const StoriesList &list) { const auto i = _all.find(id.peer);
return list.user->id;
});
if (i == end(_all)) { if (i == end(_all)) {
return; return;
} }
const auto j = ranges::find(i->ids, id.story); const auto j = i->second.ids.lower_bound(StoryIdDate{ id.story });
if (j == end(i->ids)) { if (j == end(i->second.ids) || j->id != id.story) {
return; return;
} }
const auto ignore = [&] { const auto ignore = [&] {
const auto side = kIgnorePreloadAroundIfLoaded; const auto side = kIgnorePreloadAroundIfLoaded;
const auto left = ranges::min(int(j - begin(i->ids)), side); const auto left = ranges::min(int(j - begin(i->second.ids)), side);
const auto right = ranges::min(int(end(i->ids) - j), side); const auto right = ranges::min(int(end(i->second.ids) - j), side);
for (auto k = j - left; k != j + right; ++k) { for (auto k = j - left; k != j + right; ++k) {
const auto maybeStory = lookup({ id.peer, *k }); const auto maybeStory = lookup({ id.peer, k->id });
if (!maybeStory && maybeStory.error() == NoStory::Unknown) { if (!maybeStory && maybeStory.error() == NoStory::Unknown) {
return false; return false;
} }
@ -660,29 +767,43 @@ void Stories::loadAround(FullStoryId id) {
return; return;
} }
const auto side = kPreloadAroundCount; const auto side = kPreloadAroundCount;
const auto left = ranges::min(int(j - begin(i->ids)), side); const auto left = ranges::min(int(j - begin(i->second.ids)), side);
const auto right = ranges::min(int(end(i->ids) - j), side); const auto right = ranges::min(int(end(i->second.ids) - j), side);
const auto from = j - left; const auto from = j - left;
const auto till = j + right; const auto till = j + right;
for (auto k = from; k != till; ++k) { for (auto k = from; k != till; ++k) {
resolve({ id.peer, *k }, nullptr); resolve({ id.peer, k->id }, nullptr);
} }
} }
void Stories::markAsRead(FullStoryId id, bool viewed) { void Stories::markAsRead(FullStoryId id, bool viewed) {
const auto i = ranges::find(_all, id.peer, [](const StoriesList &list) { const auto i = _all.find(id.peer);
return list.user->id;
});
Assert(i != end(_all)); Assert(i != end(_all));
if (i->readTill >= id.story) { if (i->second.readTill >= id.story) {
return; return;
} else if (!_markReadPending.contains(id.peer)) { } else if (!_markReadPending.contains(id.peer)) {
sendMarkAsReadRequests(); sendMarkAsReadRequests();
} }
_markReadPending.emplace(id.peer); _markReadPending.emplace(id.peer);
i->readTill = id.story; const auto wasUnread = i->second.unread();
i->second.readTill = id.story;
const auto nowUnread = i->second.unread();
if (wasUnread != nowUnread) {
const auto refreshInList = [&](StorySourcesList list) {
auto &sources = _sources[static_cast<int>(list)];
const auto i = ranges::find(
sources,
id.peer,
&StoriesSourceInfo::id);
if (i != end(sources)) {
i->unread = nowUnread;
sort(list);
}
};
refreshInList(StorySourcesList::All);
refreshInList(StorySourcesList::NotHidden);
}
_markReadTimer.callOnce(kMarkAsReadDelay); _markReadTimer.callOnce(kMarkAsReadDelay);
_allChanged.fire({});
} }
void Stories::sendMarkAsReadRequest( void Stories::sendMarkAsReadRequest(
@ -721,12 +842,9 @@ void Stories::sendMarkAsReadRequests() {
++i; ++i;
continue; continue;
} }
const auto j = ranges::find(_all, peerId, []( const auto j = _all.find(peerId);
const StoriesList &list) {
return list.user->id;
});
if (j != end(_all)) { if (j != end(_all)) {
sendMarkAsReadRequest(j->user, j->readTill); sendMarkAsReadRequest(j->second.user, j->second.readTill);
} }
i = _markReadPending.erase(i); i = _markReadPending.erase(i);
} }

View file

@ -22,6 +22,21 @@ namespace Data {
class Session; class Session;
struct StoryIdDate {
StoryId id = 0;
TimeId date = 0;
[[nodiscard]] bool valid() const {
return id != 0;
}
explicit operator bool() const {
return valid();
}
friend inline auto operator<=>(StoryIdDate, StoryIdDate) = default;
friend inline bool operator==(StoryIdDate, StoryIdDate) = default;
};
struct StoryMedia { struct StoryMedia {
std::variant<not_null<PhotoData*>, not_null<DocumentData*>> data; std::variant<not_null<PhotoData*>, not_null<DocumentData*>> data;
@ -35,7 +50,7 @@ struct StoryView {
friend inline bool operator==(StoryView, StoryView) = default; friend inline bool operator==(StoryView, StoryView) = default;
}; };
class Story { class Story final {
public: public:
Story( Story(
StoryId id, StoryId id,
@ -48,6 +63,7 @@ public:
[[nodiscard]] not_null<PeerData*> peer() const; [[nodiscard]] not_null<PeerData*> peer() const;
[[nodiscard]] StoryId id() const; [[nodiscard]] StoryId id() const;
[[nodiscard]] StoryIdDate idDate() const;
[[nodiscard]] FullStoryId fullId() const; [[nodiscard]] FullStoryId fullId() const;
[[nodiscard]] TimeId date() const; [[nodiscard]] TimeId date() const;
[[nodiscard]] const StoryMedia &media() const; [[nodiscard]] const StoryMedia &media() const;
@ -89,16 +105,28 @@ private:
}; };
struct StoriesList { struct StoriesSourceInfo {
not_null<UserData*> user; PeerId id = 0;
base::flat_set<StoryId> ids; TimeId last = 0;
StoryId readTill = 0; bool unread = false;
int total = 0; bool premium = false;
bool hidden = false; bool hidden = false;
friend inline bool operator==(
StoriesSourceInfo,
StoriesSourceInfo) = default;
};
struct StoriesSource {
not_null<UserData*> user;
base::flat_set<StoryIdDate> ids;
StoryId readTill = 0;
bool hidden = false;
[[nodiscard]] StoriesSourceInfo info() const;
[[nodiscard]] bool unread() const; [[nodiscard]] bool unread() const;
friend inline bool operator==(StoriesList, StoriesList) = default; friend inline bool operator==(StoriesSource, StoriesSource) = default;
}; };
enum class NoStory : uchar { enum class NoStory : uchar {
@ -106,6 +134,13 @@ enum class NoStory : uchar {
Deleted, Deleted,
}; };
enum class StorySourcesList : uchar {
NotHidden,
All,
};
inline constexpr auto kStorySourcesListCount = 2;
class Stories final { class Stories final {
public: public:
explicit Stories(not_null<Session*> owner); explicit Stories(not_null<Session*> owner);
@ -122,13 +157,16 @@ public:
not_null<HistoryItem*> dependent, not_null<HistoryItem*> dependent,
not_null<Data::Story*> dependency); not_null<Data::Story*> dependency);
void loadMore(); void loadMore(StorySourcesList list);
void apply(const MTPDupdateStory &data); void apply(const MTPDupdateStory &data);
void loadAround(FullStoryId id); void loadAround(FullStoryId id);
[[nodiscard]] const std::vector<StoriesList> &all(); [[nodiscard]] const base::flat_map<PeerId, StoriesSource> &all() const;
[[nodiscard]] bool allLoaded() const; [[nodiscard]] const std::vector<StoriesSourceInfo> &sources(
[[nodiscard]] rpl::producer<> allChanged() const; StorySourcesList list) const;
[[nodiscard]] bool sourcesLoaded(StorySourcesList list) const;
[[nodiscard]] rpl::producer<> sourcesChanged(
StorySourcesList list) const;
[[nodiscard]] rpl::producer<PeerId> itemsChanged() const; [[nodiscard]] rpl::producer<PeerId> itemsChanged() const;
[[nodiscard]] base::expected<not_null<Story*>, NoStory> lookup( [[nodiscard]] base::expected<not_null<Story*>, NoStory> lookup(
@ -145,11 +183,11 @@ public:
Fn<void(std::vector<StoryView>)> done); Fn<void(std::vector<StoryView>)> done);
private: private:
[[nodiscard]] StoriesList parse(const MTPUserStories &stories); void parseAndApply(const MTPUserStories &stories);
[[nodiscard]] Story *parseAndApply( [[nodiscard]] Story *parseAndApply(
not_null<PeerData*> peer, not_null<PeerData*> peer,
const MTPDstoryItem &data); const MTPDstoryItem &data);
StoryId parseAndApply( StoryIdDate parseAndApply(
not_null<PeerData*> peer, not_null<PeerData*> peer,
const MTPstoryItem &story); const MTPstoryItem &story);
void processResolvedStories( void processResolvedStories(
@ -158,13 +196,16 @@ private:
void sendResolveRequests(); void sendResolveRequests();
void finalizeResolve(FullStoryId id); void finalizeResolve(FullStoryId id);
void pushToBack(StoriesList &&list);
void applyDeleted(FullStoryId id); void applyDeleted(FullStoryId id);
void applyDeletedFromSources(PeerId id, StorySourcesList list);
void removeDependencyStory(not_null<Story*> story); void removeDependencyStory(not_null<Story*> story);
void sort(StorySourcesList list);
void sendMarkAsReadRequests(); void sendMarkAsReadRequests();
void sendMarkAsReadRequest(not_null<PeerData*> peer, StoryId tillId); void sendMarkAsReadRequest(not_null<PeerData*> peer, StoryId tillId);
void requestUserStories(not_null<UserData*> user);
const not_null<Session*> _owner; const not_null<Session*> _owner;
base::flat_map< base::flat_map<
PeerId, PeerId,
@ -182,17 +223,20 @@ private:
not_null<Data::Story*>, not_null<Data::Story*>,
base::flat_set<not_null<HistoryItem*>>> _dependentMessages; base::flat_set<not_null<HistoryItem*>>> _dependentMessages;
std::vector<StoriesList> _all; base::flat_map<PeerId, StoriesSource> _all;
rpl::event_stream<> _allChanged; std::vector<StoriesSourceInfo> _sources[kStorySourcesListCount];
rpl::event_stream<PeerId> _itemsChanged; rpl::event_stream<> _sourcesChanged[kStorySourcesListCount];
QString _state; bool _sourcesLoaded[kStorySourcesListCount] = { false };
bool _allLoaded = false; QString _sourcesStates[kStorySourcesListCount];
mtpRequestId _loadMoreRequestId = 0; mtpRequestId _loadMoreRequestId[kStorySourcesListCount] = { 0 };
rpl::event_stream<PeerId> _itemsChanged;
base::flat_set<PeerId> _markReadPending; base::flat_set<PeerId> _markReadPending;
base::Timer _markReadTimer; base::Timer _markReadTimer;
base::flat_set<PeerId> _markReadRequests; base::flat_set<PeerId> _markReadRequests;
base::flat_set<not_null<UserData*>> _requestingUserStories;
StoryId _viewsStoryId = 0; StoryId _viewsStoryId = 0;
std::optional<StoryView> _viewsOffset; std::optional<StoryView> _viewsOffset;

View file

@ -142,7 +142,9 @@ InnerWidget::InnerWidget(
, _controller(controller) , _controller(controller)
, _stories(std::make_unique<Stories::List>( , _stories(std::make_unique<Stories::List>(
this, this,
Stories::ContentForSession(&controller->session()), Stories::ContentForSession(
&controller->session(),
Data::StorySourcesList::NotHidden),
[=] { return _stories->height() - _visibleTop; })) [=] { return _stories->height() - _visibleTop; }))
, _shownList(controller->session().data().chatsList()->indexed()) , _shownList(controller->session().data().chatsList()->indexed())
, _st(&st::defaultDialogRow) , _st(&st::defaultDialogRow)
@ -339,7 +341,7 @@ InnerWidget::InnerWidget(
_stories->clicks( _stories->clicks(
) | rpl::start_with_next([=](uint64 id) { ) | rpl::start_with_next([=](uint64 id) {
_controller->openPeerStories(PeerId(int64(id))); _controller->openPeerStories(PeerId(int64(id)), {});
}, lifetime()); }, lifetime());
_stories->showProfileRequests( _stories->showProfileRequests(
@ -365,7 +367,8 @@ InnerWidget::InnerWidget(
_stories->loadMoreRequests( _stories->loadMoreRequests(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
session().data().stories().loadMore(); session().data().stories().loadMore(
Data::StorySourcesList::NotHidden);
}, lifetime()); }, lifetime());
handleChatListEntryRefreshes(); handleChatListEntryRefreshes();

View file

@ -15,8 +15,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h" #include "main/main_session.h"
#include "ui/painter.h" #include "ui/painter.h"
#include "history/history.h" // #TODO stories testing
namespace Dialogs::Stories { namespace Dialogs::Stories {
namespace { namespace {
@ -51,12 +49,13 @@ private:
class State final { class State final {
public: public:
explicit State(not_null<Data::Stories*> data); State(not_null<Data::Stories*> data, Data::StorySourcesList list);
[[nodiscard]] Content next(); [[nodiscard]] Content next();
private: private:
const not_null<Data::Stories*> _data; const not_null<Data::Stories*> _data;
const Data::StorySourcesList _list;
base::flat_map<not_null<UserData*>, std::shared_ptr<Userpic>> _userpics; base::flat_map<not_null<UserData*>, std::shared_ptr<Userpic>> _userpics;
}; };
@ -122,27 +121,23 @@ void PeerUserpic::processNewPhoto() {
}, _subscribed->downloadLifetime); }, _subscribed->downloadLifetime);
} }
State::State(not_null<Data::Stories*> data) State::State(not_null<Data::Stories*> data, Data::StorySourcesList list)
: _data(data) { : _data(data)
, _list(list) {
} }
Content State::next() { Content State::next() {
auto result = Content(); auto result = Content();
#if 1 // #TODO stories testing
const auto &all = _data->all(); const auto &all = _data->all();
result.users.reserve(all.size()); const auto &sources = _data->sources(_list);
for (const auto &list : all) { result.users.reserve(sources.size());
for (const auto &info : sources) {
const auto i = all.find(info.id);
Assert(i != end(all));
const auto &source = i->second;
auto userpic = std::shared_ptr<Userpic>(); auto userpic = std::shared_ptr<Userpic>();
const auto user = list.user; const auto user = source.user;
#else
const auto list = _data->owner().chatsList();
const auto &all = list->indexed()->all();
result.users.reserve(all.size());
for (const auto &entry : all) {
if (const auto history = entry->history()) {
if (const auto user = history->peer->asUser(); user && !user->isBot()) {
auto userpic = std::shared_ptr<Userpic>();
#endif
if (const auto i = _userpics.find(user); i != end(_userpics)) { if (const auto i = _userpics.find(user); i != end(_userpics)) {
userpic = i->second; userpic = i->second;
} else { } else {
@ -153,41 +148,25 @@ Content State::next() {
.id = uint64(user->id.value), .id = uint64(user->id.value),
.name = user->shortName(), .name = user->shortName(),
.userpic = std::move(userpic), .userpic = std::move(userpic),
#if 1 // #TODO stories testing .unread = info.unread,
.unread = list.unread(),
#else
.unread = history->chatListBadgesState().unread
}); });
} }
#endif
});
}
ranges::stable_partition(result.users, [](const User &user) {
return user.unread;
});
return result; return result;
} }
} // namespace } // namespace
rpl::producer<Content> ContentForSession(not_null<Main::Session*> session) { rpl::producer<Content> ContentForSession(
not_null<Main::Session*> session,
Data::StorySourcesList list) {
return [=](auto consumer) { return [=](auto consumer) {
auto result = rpl::lifetime(); auto result = rpl::lifetime();
const auto stories = &session->data().stories(); const auto stories = &session->data().stories();
const auto state = result.make_state<State>(stories); const auto state = result.make_state<State>(stories, list);
rpl::single( rpl::single(
rpl::empty rpl::empty
) | rpl::then( ) | rpl::then(
#if 1 // #TODO stories testing stories->sourcesChanged(list)
stories->allChanged()
#else
rpl::merge(
session->data().chatsListChanges(
) | rpl::filter(
rpl::mappers::_1 == nullptr
) | rpl::to_empty,
session->data().unreadBadgeChanges())
#endif
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
consumer.put_next(state->next()); consumer.put_next(state->next());
}, result); }, result);

View file

@ -7,6 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
namespace Data {
enum class StorySourcesList : uchar;
} // namespace Data
namespace Main { namespace Main {
class Session; class Session;
} // namespace Main } // namespace Main
@ -16,6 +20,7 @@ namespace Dialogs::Stories {
struct Content; struct Content;
[[nodiscard]] rpl::producer<Content> ContentForSession( [[nodiscard]] rpl::producer<Content> ContentForSession(
not_null<Main::Session*> session); not_null<Main::Session*> session,
Data::StorySourcesList list);
} // namespace Dialogs::Stories } // namespace Dialogs::Stories

View file

@ -297,7 +297,8 @@ ClickHandlerPtr JumpToStoryClickHandler(
? separate->sessionController() ? separate->sessionController()
: peer->session().tryResolveWindow(); : peer->session().tryResolveWindow();
if (controller) { if (controller) {
controller->openPeerStory(peer, storyId); // #TODO stories decide context
controller->openPeerStory(peer, storyId, {});
} }
}); });
} }

View file

@ -386,25 +386,30 @@ auto Controller::stickerOrEmojiChosen() const
} }
void Controller::show( void Controller::show(
const std::vector<Data::StoriesList> &lists, not_null<Data::Story*> story,
int index, Data::StorySourcesList list) {
int subindex) { auto &stories = story->owner().stories();
Expects(index >= 0 && index < lists.size()); const auto &all = stories.all();
Expects(subindex >= 0 && subindex < lists[index].ids.size()); const auto &sources = stories.sources(list);
const auto storyId = story->fullId();
showSiblings(lists, index); const auto id = storyId.story;
const auto i = ranges::find(
const auto &list = lists[index]; sources,
const auto id = *(begin(list.ids) + subindex); storyId.peer,
const auto storyId = FullStoryId{ &Data::StoriesSourceInfo::id);
.peer = list.user->id, if (i == end(sources)) {
.story = id,
};
const auto maybeStory = list.user->owner().stories().lookup(storyId);
if (!maybeStory) {
return; return;
} }
const auto story = *maybeStory; const auto j = all.find(storyId.peer);
if (j == end(all)) {
return;
}
const auto &source = j->second;
const auto k = source.ids.lower_bound(Data::StoryIdDate{ id });
if (k == end(source.ids) || k->id != id) {
return;
}
showSiblings(&story->session(), sources, (i - begin(sources)));
const auto guard = gsl::finally([&] { const auto guard = gsl::finally([&] {
_paused = false; _paused = false;
_started = false; _started = false;
@ -414,10 +419,10 @@ void Controller::show(
_photoPlayback = nullptr; _photoPlayback = nullptr;
} }
}); });
if (_list != list) { if (_source != source) {
_list = list; _source = source;
} }
_index = subindex; _index = (k - begin(source.ids));
_waitingForId = {}; _waitingForId = {};
if (_shown == storyId) { if (_shown == storyId) {
@ -431,16 +436,16 @@ void Controller::show(
unfocusReply(); unfocusReply();
} }
_header->show({ .user = list.user, .date = story->date() }); _header->show({ .user = source.user, .date = story->date() });
_slider->show({ .index = _index, .total = list.total }); _slider->show({ .index = _index, .total = int(source.ids.size()) });
_replyArea->show({ .user = list.user, .id = id }); _replyArea->show({ .user = source.user, .id = id });
_recentViews->show({ _recentViews->show({
.list = story->recentViewers(), .list = story->recentViewers(),
.total = story->views(), .total = story->views(),
.valid = list.user->isSelf(), .valid = source.user->isSelf(),
}); });
const auto session = &list.user->session(); const auto session = &story->session();
if (_session != session) { if (_session != session) {
_session = session; _session = session;
_sessionLifetime = session->changes().storyUpdates( _sessionLifetime = session->changes().storyUpdates(
@ -458,14 +463,13 @@ void Controller::show(
}, _sessionLifetime); }, _sessionLifetime);
} }
auto &stories = session->data().stories(); if (int(sources.end() - i) < kPreloadUsersCount) {
if (int(lists.size()) - index < kPreloadUsersCount) { stories.loadMore(list);
stories.loadMore();
} }
stories.loadAround(storyId); stories.loadAround(storyId);
updatePlayingAllowed(); updatePlayingAllowed();
list.user->updateFull(); source.user->updateFull();
} }
void Controller::updatePlayingAllowed() { void Controller::updatePlayingAllowed() {
@ -492,21 +496,33 @@ void Controller::setPlayingAllowed(bool allowed) {
} }
void Controller::showSiblings( void Controller::showSiblings(
const std::vector<Data::StoriesList> &lists, not_null<Main::Session*> session,
const std::vector<Data::StoriesSourceInfo> &sources,
int index) { int index) {
showSibling(_siblingLeft, (index > 0) ? &lists[index - 1] : nullptr); showSibling(
_siblingLeft,
session,
(index > 0) ? sources[index - 1].id : PeerId());
showSibling( showSibling(
_siblingRight, _siblingRight,
(index + 1 < lists.size()) ? &lists[index + 1] : nullptr); session,
(index + 1 < sources.size()) ? sources[index + 1].id : PeerId());
} }
void Controller::showSibling( void Controller::showSibling(
std::unique_ptr<Sibling> &sibling, std::unique_ptr<Sibling> &sibling,
const Data::StoriesList *list) { not_null<Main::Session*> session,
if (!list || list->ids.empty()) { PeerId peerId) {
if (!peerId) {
sibling = nullptr; sibling = nullptr;
} else if (!sibling || !sibling->shows(*list)) { return;
sibling = std::make_unique<Sibling>(this, *list); }
const auto &all = session->data().stories().all();
const auto i = all.find(peerId);
if (i == end(all)) {
sibling = nullptr;
} else if (!sibling || !sibling->shows(i->second)) {
sibling = std::make_unique<Sibling>(this, i->second);
} }
} }
@ -552,19 +568,19 @@ void Controller::maybeMarkAsRead(const Player::TrackState &state) {
} }
void Controller::markAsRead() { void Controller::markAsRead() {
Expects(_list.has_value()); Expects(_source.has_value());
_list->user->owner().stories().markAsRead(_shown, _started); _source->user->owner().stories().markAsRead(_shown, _started);
} }
bool Controller::subjumpAvailable(int delta) const { bool Controller::subjumpAvailable(int delta) const {
const auto index = _index + delta; const auto index = _index + delta;
if (index < 0) { if (index < 0) {
return _siblingLeft && _siblingLeft->shownId().valid(); return _siblingLeft && _siblingLeft->shownId().valid();
} else if (index >= _list->total) { } else if (index >= int(_source->ids.size())) {
return _siblingRight && _siblingRight->shownId().valid(); return _siblingRight && _siblingRight->shownId().valid();
} }
return index >= 0 && index < _list->total; return index >= 0 && index < int(_source->ids.size());
} }
bool Controller::subjumpFor(int delta) { bool Controller::subjumpFor(int delta) {
@ -575,32 +591,32 @@ bool Controller::subjumpFor(int delta) {
if (index < 0) { if (index < 0) {
if (_siblingLeft && _siblingLeft->shownId().valid()) { if (_siblingLeft && _siblingLeft->shownId().valid()) {
return jumpFor(-1); return jumpFor(-1);
} else if (!_list || _list->ids.empty()) { } else if (!_source || _source->ids.empty()) {
return false; return false;
} }
subjumpTo(0); subjumpTo(0);
return true; return true;
} else if (index >= _list->total) { } else if (index >= int(_source->ids.size())) {
return _siblingRight return _siblingRight
&& _siblingRight->shownId().valid() && _siblingRight->shownId().valid()
&& jumpFor(1); && jumpFor(1);
} else if (index < _list->ids.size()) { } else {
subjumpTo(index); subjumpTo(index);
} }
return true; return true;
} }
void Controller::subjumpTo(int index) { void Controller::subjumpTo(int index) {
Expects(_list.has_value()); Expects(_source.has_value());
Expects(index >= 0 && index < _list->ids.size()); Expects(index >= 0 && index < _source->ids.size());
const auto id = FullStoryId{ const auto id = FullStoryId{
.peer = _list->user->id, .peer = _source->user->id,
.story = *(begin(_list->ids) + index) .story = (begin(_source->ids) + index)->id,
}; };
auto &stories = _list->user->owner().stories(); auto &stories = _source->user->owner().stories();
if (stories.lookup(id)) { if (stories.lookup(id)) {
_delegate->storiesJumpTo(&_list->user->session(), id); _delegate->storiesJumpTo(&_source->user->session(), id);
} else if (_waitingForId != id) { } else if (_waitingForId != id) {
_waitingForId = id; _waitingForId = id;
stories.loadAround(id); stories.loadAround(id);
@ -609,9 +625,9 @@ void Controller::subjumpTo(int index) {
void Controller::checkWaitingFor() { void Controller::checkWaitingFor() {
Expects(_waitingForId.valid()); Expects(_waitingForId.valid());
Expects(_list.has_value()); Expects(_source.has_value());
auto &stories = _list->user->owner().stories(); auto &stories = _source->user->owner().stories();
const auto maybe = stories.lookup(_waitingForId); const auto maybe = stories.lookup(_waitingForId);
if (!maybe) { if (!maybe) {
if (maybe.error() == Data::NoStory::Deleted) { if (maybe.error() == Data::NoStory::Deleted) {
@ -620,7 +636,7 @@ void Controller::checkWaitingFor() {
return; return;
} }
_delegate->storiesJumpTo( _delegate->storiesJumpTo(
&_list->user->session(), &_source->user->session(),
base::take(_waitingForId)); base::take(_waitingForId));
} }
@ -633,7 +649,7 @@ bool Controller::jumpFor(int delta) {
return true; return true;
} }
} else if (delta == 1) { } else if (delta == 1) {
if (_list && _index + 1 >= _list->total) { if (_source && _index + 1 >= int(_source->ids.size())) {
markAsRead(); markAsRead();
} }
if (const auto right = _siblingRight.get()) { if (const auto right = _siblingRight.get()) {
@ -665,7 +681,7 @@ void Controller::setMenuShown(bool shown) {
} }
bool Controller::canDownload() const { bool Controller::canDownload() const {
return _list && _list->user->isSelf(); return _source && _source->user->isSelf();
} }
void Controller::repaintSibling(not_null<Sibling*> sibling) { void Controller::repaintSibling(not_null<Sibling*> sibling) {
@ -706,7 +722,7 @@ Fn<void(std::vector<Data::StoryView>)> Controller::viewsGotMoreCallback() {
return crl::guard(&_viewsLoadGuard, [=]( return crl::guard(&_viewsLoadGuard, [=](
const std::vector<Data::StoryView> &result) { const std::vector<Data::StoryView> &result) {
if (_viewsSlice.list.empty()) { if (_viewsSlice.list.empty()) {
auto &stories = _list->user->owner().stories(); auto &stories = _source->user->owner().stories();
if (const auto maybeStory = stories.lookup(_shown)) { if (const auto maybeStory = stories.lookup(_shown)) {
_viewsSlice = { _viewsSlice = {
.list = result, .list = result,
@ -728,11 +744,11 @@ Fn<void(std::vector<Data::StoryView>)> Controller::viewsGotMoreCallback() {
} }
void Controller::refreshViewsFromData() { void Controller::refreshViewsFromData() {
Expects(_list.has_value()); Expects(_source.has_value());
auto &stories = _list->user->owner().stories(); auto &stories = _source->user->owner().stories();
const auto maybeStory = stories.lookup(_shown); const auto maybeStory = stories.lookup(_shown);
if (!maybeStory || !_list->user->isSelf()) { if (!maybeStory || !_source->user->isSelf()) {
_viewsSlice = {}; _viewsSlice = {};
return; return;
} }
@ -750,11 +766,11 @@ void Controller::refreshViewsFromData() {
} }
bool Controller::sliceViewsTo(PeerId offset) { bool Controller::sliceViewsTo(PeerId offset) {
Expects(_list.has_value()); Expects(_source.has_value());
auto &stories = _list->user->owner().stories(); auto &stories = _source->user->owner().stories();
const auto maybeStory = stories.lookup(_shown); const auto maybeStory = stories.lookup(_shown);
if (!maybeStory || !_list->user->isSelf()) { if (!maybeStory || !_source->user->isSelf()) {
_viewsSlice = {}; _viewsSlice = {};
return true; return true;
} }

View file

@ -20,7 +20,6 @@ struct FileChosen;
} // namespace ChatHelpers } // namespace ChatHelpers
namespace Data { namespace Data {
struct StoriesList;
struct FileOrigin; struct FileOrigin;
} // namespace Data } // namespace Data
@ -100,10 +99,7 @@ public:
[[nodiscard]] auto stickerOrEmojiChosen() const [[nodiscard]] auto stickerOrEmojiChosen() const
-> rpl::producer<ChatHelpers::FileChosen>; -> rpl::producer<ChatHelpers::FileChosen>;
void show( void show(not_null<Data::Story*> story, Data::StorySourcesList list);
const std::vector<Data::StoriesList> &lists,
int index,
int subindex);
void ready(); void ready();
void updateVideoPlayback(const Player::TrackState &state); void updateVideoPlayback(const Player::TrackState &state);
@ -142,11 +138,13 @@ private:
void setPlayingAllowed(bool allowed); void setPlayingAllowed(bool allowed);
void showSiblings( void showSiblings(
const std::vector<Data::StoriesList> &lists, not_null<Main::Session*> session,
const std::vector<Data::StoriesSourceInfo> &lists,
int index); int index);
void showSibling( void showSibling(
std::unique_ptr<Sibling> &sibling, std::unique_ptr<Sibling> &sibling,
const Data::StoriesList *list); not_null<Main::Session*> session,
PeerId peerId);
void subjumpTo(int index); void subjumpTo(int index);
void checkWaitingFor(); void checkWaitingFor();
@ -180,7 +178,7 @@ private:
FullStoryId _shown; FullStoryId _shown;
TextWithEntities _captionText; TextWithEntities _captionText;
std::optional<Data::StoriesList> _list; std::optional<Data::StoriesSource> _source;
FullStoryId _waitingForId; FullStoryId _waitingForId;
int _index = 0; int _index = 0;
bool _started = false; bool _started = false;

View file

@ -228,10 +228,10 @@ bool Sibling::LoaderVideo::updateAfterGoodCheck() {
Sibling::Sibling( Sibling::Sibling(
not_null<Controller*> controller, not_null<Controller*> controller,
const Data::StoriesList &list) const Data::StoriesSource &source)
: _controller(controller) : _controller(controller)
, _id{ list.user->id, list.ids.front() } , _id{ source.user->id, source.ids.front().id }
, _peer(list.user) { , _peer(source.user) {
checkStory(); checkStory();
_goodShown.stop(); _goodShown.stop();
} }
@ -279,10 +279,10 @@ not_null<PeerData*> Sibling::peer() const {
return _peer; return _peer;
} }
bool Sibling::shows(const Data::StoriesList &list) const { bool Sibling::shows(const Data::StoriesSource &source) const {
Expects(!list.ids.empty()); Expects(!source.ids.empty());
return _id == FullStoryId{ list.user->id, list.ids.front() }; return _id == FullStoryId{ source.user->id, source.ids.front().id };
} }
SiblingView Sibling::view(const SiblingLayout &layout, float64 over) { SiblingView Sibling::view(const SiblingLayout &layout, float64 over) {

View file

@ -26,12 +26,12 @@ class Sibling final : public base::has_weak_ptr {
public: public:
Sibling( Sibling(
not_null<Controller*> controller, not_null<Controller*> controller,
const Data::StoriesList &list); const Data::StoriesSource &source);
~Sibling(); ~Sibling();
[[nodiscard]] FullStoryId shownId() const; [[nodiscard]] FullStoryId shownId() const;
[[nodiscard]] not_null<PeerData*> peer() const; [[nodiscard]] not_null<PeerData*> peer() const;
[[nodiscard]] bool shows(const Data::StoriesList &list) const; [[nodiscard]] bool shows(const Data::StoriesSource &source) const;
[[nodiscard]] SiblingView view( [[nodiscard]] SiblingView view(
const SiblingLayout &layout, const SiblingLayout &layout,

View file

@ -22,11 +22,8 @@ View::View(not_null<Delegate*> delegate)
View::~View() = default; View::~View() = default;
void View::show( void View::show(not_null<Data::Story*> story, Data::StorySourcesList list) {
const std::vector<Data::StoriesList> &lists, _controller->show(story, list);
int index,
int subindex) {
_controller->show(lists, index, subindex);
} }
void View::ready() { void View::ready() {

View file

@ -8,7 +8,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once #pragma once
namespace Data { namespace Data {
struct StoriesList; class Story;
enum class StorySourcesList : uchar;
struct FileOrigin; struct FileOrigin;
} // namespace Data } // namespace Data
@ -52,10 +53,7 @@ public:
explicit View(not_null<Delegate*> delegate); explicit View(not_null<Delegate*> delegate);
~View(); ~View();
void show( void show(not_null<Data::Story*> story, Data::StorySourcesList list);
const std::vector<Data::StoriesList> &lists,
int index,
int subindex);
void ready(); void ready();
[[nodiscard]] bool canDownload() const; [[nodiscard]] bool canDownload() const;

View file

@ -16,6 +16,7 @@ class HistoryItem;
namespace Data { namespace Data {
class Story; class Story;
enum class StorySourcesList : uchar;
} // namespace Data } // namespace Data
namespace Window { namespace Window {
@ -74,10 +75,12 @@ public:
OpenRequest( OpenRequest(
Window::SessionController *controller, Window::SessionController *controller,
not_null<Data::Story*> story, not_null<Data::Story*> story,
Data::StorySourcesList list,
bool continueStreaming = false, bool continueStreaming = false,
crl::time startTime = 0) crl::time startTime = 0)
: _controller(controller) : _controller(controller)
, _story(story) , _story(story)
, _storiesList(list)
, _continueStreaming(continueStreaming) , _continueStreaming(continueStreaming)
, _startTime(startTime) { , _startTime(startTime) {
} }
@ -105,6 +108,9 @@ public:
[[nodiscard]] Data::Story *story() const { [[nodiscard]] Data::Story *story() const {
return _story; return _story;
} }
[[nodiscard]] Data::StorySourcesList storiesList() const {
return _storiesList;
}
[[nodiscard]] std::optional<Data::CloudTheme> cloudTheme() const { [[nodiscard]] std::optional<Data::CloudTheme> cloudTheme() const {
return _cloudTheme; return _cloudTheme;
@ -127,6 +133,7 @@ private:
DocumentData *_document = nullptr; DocumentData *_document = nullptr;
PhotoData *_photo = nullptr; PhotoData *_photo = nullptr;
Data::Story *_story = nullptr; Data::Story *_story = nullptr;
Data::StorySourcesList _storiesList = {};
PeerData *_peer = nullptr; PeerData *_peer = nullptr;
HistoryItem *_item = nullptr; HistoryItem *_item = nullptr;
MsgId _topicRootId = 0; MsgId _topicRootId = 0;

View file

@ -4973,15 +4973,13 @@ void OverlayWidget::setContext(
_topicRootId = MsgId(); _topicRootId = MsgId();
_history = nullptr; _history = nullptr;
_peer = nullptr; _peer = nullptr;
const auto &all = story->peer->owner().stories().all();
const auto i = ranges::find(
all,
story->peer,
&Data::StoriesList::user);
Assert(i != end(all));
const auto j = ranges::find(i->ids, story->id);
setStoriesPeer(story->peer); setStoriesPeer(story->peer);
_stories->show(all, (i - begin(all)), j - begin(i->ids)); auto &stories = story->peer->owner().stories();
const auto maybeStory = stories.lookup(
{ story->peer->id, story->id });
if (maybeStory) {
_stories->show(*maybeStory, story->list);
}
} else { } else {
_message = nullptr; _message = nullptr;
_topicRootId = MsgId(); _topicRootId = MsgId();

View file

@ -30,6 +30,7 @@ enum class activation : uchar;
namespace Data { namespace Data {
class PhotoMedia; class PhotoMedia;
class DocumentMedia; class DocumentMedia;
enum class StorySourcesList : uchar;
} // namespace Data } // namespace Data
namespace Ui { namespace Ui {
@ -306,6 +307,7 @@ private:
struct StoriesContext { struct StoriesContext {
not_null<PeerData*> peer; not_null<PeerData*> peer;
StoryId id = 0; StoryId id = 0;
Data::StorySourcesList list = {};
}; };
void setContext(std::variant< void setContext(std::variant<
v::null_t, v::null_t,

View file

@ -2466,28 +2466,33 @@ Ui::ChatThemeBackgroundData SessionController::backgroundData(
void SessionController::openPeerStory( void SessionController::openPeerStory(
not_null<PeerData*> peer, not_null<PeerData*> peer,
StoryId storyId) { StoryId storyId,
Data::StorySourcesList list) {
using namespace Media::View; using namespace Media::View;
using namespace Data; using namespace Data;
auto &stories = session().data().stories(); auto &stories = session().data().stories();
if (const auto from = stories.lookup({ peer->id, storyId })) { if (const auto from = stories.lookup({ peer->id, storyId })) {
window().openInMediaView(OpenRequest(this, *from)); window().openInMediaView(OpenRequest(this, *from, list));
} }
} }
void SessionController::openPeerStories(PeerId peerId) { void SessionController::openPeerStories(
PeerId peerId,
Data::StorySourcesList list) {
using namespace Media::View; using namespace Media::View;
using namespace Data; using namespace Data;
auto &stories = session().data().stories(); auto &stories = session().data().stories();
const auto &all = stories.all(); const auto &all = stories.all();
const auto i = ranges::find(all, peerId, [](const StoriesList &list) { const auto i = all.find(peerId);
return list.user->id; if (i != end(all)) {
}); const auto j = i->second.ids.lower_bound(
if (i != end(all) && !i->ids.empty()) { StoryIdDate{ i->second.readTill + 1 });
const auto j = i->ids.lower_bound(i->readTill + 1); openPeerStory(
openPeerStory(i->user, j != i->ids.end() ? *j : i->ids.front()); i->second.user,
j != i->second.ids.end() ? j->id : i->second.ids.front().id,
list);
} }
} }

View file

@ -29,6 +29,10 @@ namespace Adaptive {
enum class WindowLayout; enum class WindowLayout;
} // namespace Adaptive } // namespace Adaptive
namespace Data {
enum class StorySourcesList : uchar;
} // namespace Data
namespace ChatHelpers { namespace ChatHelpers {
class TabbedSelector; class TabbedSelector;
class EmojiInteractions; class EmojiInteractions;
@ -564,8 +568,11 @@ public:
return _peerThemeOverride.value(); return _peerThemeOverride.value();
} }
void openPeerStory(not_null<PeerData*> peer, StoryId storyId); void openPeerStory(
void openPeerStories(PeerId peerId); not_null<PeerData*> peer,
StoryId storyId,
Data::StorySourcesList list);
void openPeerStories(PeerId peerId, Data::StorySourcesList list);
struct PaintContextArgs { struct PaintContextArgs {
not_null<Ui::ChatTheme*> theme; not_null<Ui::ChatTheme*> theme;