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);
_stories->loadMore();
_stories->loadMore(Data::StorySourcesList::NotHidden);
});
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -228,10 +228,10 @@ bool Sibling::LoaderVideo::updateAfterGoodCheck() {
Sibling::Sibling(
not_null<Controller*> controller,
const Data::StoriesList &list)
const Data::StoriesSource &source)
: _controller(controller)
, _id{ list.user->id, list.ids.front() }
, _peer(list.user) {
, _id{ source.user->id, source.ids.front().id }
, _peer(source.user) {
checkStory();
_goodShown.stop();
}
@ -279,10 +279,10 @@ not_null<PeerData*> Sibling::peer() const {
return _peer;
}
bool Sibling::shows(const Data::StoriesList &list) const {
Expects(!list.ids.empty());
bool Sibling::shows(const Data::StoriesSource &source) const {
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) {

View File

@ -26,12 +26,12 @@ class Sibling final : public base::has_weak_ptr {
public:
Sibling(
not_null<Controller*> controller,
const Data::StoriesList &list);
const Data::StoriesSource &source);
~Sibling();
[[nodiscard]] FullStoryId shownId() 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(
const SiblingLayout &layout,

View File

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

View File

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

View File

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

View File

@ -4973,15 +4973,13 @@ void OverlayWidget::setContext(
_topicRootId = MsgId();
_history = 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);
_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 {
_message = nullptr;
_topicRootId = MsgId();

View File

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

View File

@ -2466,28 +2466,33 @@ Ui::ChatThemeBackgroundData SessionController::backgroundData(
void SessionController::openPeerStory(
not_null<PeerData*> peer,
StoryId storyId) {
StoryId storyId,
Data::StorySourcesList list) {
using namespace Media::View;
using namespace Data;
auto &stories = session().data().stories();
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 Data;
auto &stories = session().data().stories();
const auto &all = stories.all();
const auto i = ranges::find(all, peerId, [](const StoriesList &list) {
return list.user->id;
});
if (i != end(all) && !i->ids.empty()) {
const auto j = i->ids.lower_bound(i->readTill + 1);
openPeerStory(i->user, j != i->ids.end() ? *j : i->ids.front());
const auto i = all.find(peerId);
if (i != end(all)) {
const auto j = i->second.ids.lower_bound(
StoryIdDate{ i->second.readTill + 1 });
openPeerStory(
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;
} // namespace Adaptive
namespace Data {
enum class StorySourcesList : uchar;
} // namespace Data
namespace ChatHelpers {
class TabbedSelector;
class EmojiInteractions;
@ -564,8 +568,11 @@ public:
return _peerThemeOverride.value();
}
void openPeerStory(not_null<PeerData*> peer, StoryId storyId);
void openPeerStories(PeerId peerId);
void openPeerStory(
not_null<PeerData*> peer,
StoryId storyId,
Data::StorySourcesList list);
void openPeerStories(PeerId peerId, Data::StorySourcesList list);
struct PaintContextArgs {
not_null<Ui::ChatTheme*> theme;