Use real stories data, open from chats list.

This commit is contained in:
John Preston 2023-05-26 11:21:19 +04:00
parent ff902f2a1f
commit 7a042c23e9
14 changed files with 482 additions and 278 deletions

View file

@ -26,7 +26,78 @@ namespace {
} // namespace } // namespace
bool StoriesList::unread() const { bool StoriesList::unread() const {
return !items.empty() && readTill < items.front().id; return !ids.empty() && readTill < ids.front();
}
Story::Story(
StoryId id,
not_null<PeerData*> peer,
StoryMedia media,
TimeId date)
: _id(id)
, _peer(peer)
, _media(std::move(media))
, _date(date) {
}
Session &Story::owner() const {
return _peer->owner();
}
Main::Session &Story::session() const {
return _peer->session();
}
not_null<PeerData*> Story::peer() const {
return _peer;
}
StoryId Story::id() const {
return _id;
}
TimeId Story::date() const {
return _date;
}
const StoryMedia &Story::media() const {
return _media;
}
PhotoData *Story::photo() const {
const auto result = std::get_if<not_null<PhotoData*>>(&_media.data);
return result ? result->get() : nullptr;
}
DocumentData *Story::document() const {
const auto result = std::get_if<not_null<DocumentData*>>(&_media.data);
return result ? result->get() : nullptr;
}
void Story::setPinned(bool pinned) {
_pinned = pinned;
}
bool Story::pinned() const {
return _pinned;
}
void Story::setCaption(TextWithEntities &&caption) {
_caption = std::move(caption);
}
const TextWithEntities &Story::caption() const {
return _caption;
}
void Story::apply(const MTPDstoryItem &data) {
_pinned = data.is_pinned();
_caption = TextWithEntities{
data.vcaption().value_or_empty(),
Api::EntitiesFromMTP(
&owner().session(),
data.ventities().value_or_empty()),
};
} }
Stories::Stories(not_null<Session*> owner) : _owner(owner) { Stories::Stories(not_null<Session*> owner) : _owner(owner) {
@ -41,6 +112,7 @@ Session &Stories::owner() const {
void Stories::apply(const MTPDupdateStories &data) { void Stories::apply(const MTPDupdateStories &data) {
pushToFront(parse(data.vstories())); pushToFront(parse(data.vstories()));
_allChanged.fire({});
} }
StoriesList Stories::parse(const MTPUserStories &stories) { StoriesList Stories::parse(const MTPUserStories &stories) {
@ -54,25 +126,36 @@ StoriesList Stories::parse(const MTPUserStories &stories) {
.total = count, .total = count,
}; };
const auto &list = data.vstories().v; const auto &list = data.vstories().v;
result.items.reserve(list.size()); result.ids.reserve(list.size());
for (const auto &story : list) { for (const auto &story : list) {
story.match([&](const MTPDstoryItem &data) { story.match([&](const MTPDstoryItem &data) {
if (auto entry = parse(data)) { if (const auto story = parse(result.user, data)) {
result.items.push_back(std::move(*entry)); result.ids.push_back(story->id());
} else { } else {
--result.total; --result.total;
} }
}, [&](const MTPDstoryItemSkipped &) { }, [&](const MTPDstoryItemSkipped &data) {
}, [&](const MTPDstoryItemDeleted &) { result.ids.push_back(data.vid().v);
}, [&](const MTPDstoryItemDeleted &data) {
_deleted.emplace(FullStoryId{
.peer = peerFromUser(userId),
.story = data.vid().v,
});
--result.total; --result.total;
}); });
} }
result.total = std::min(result.total, int(result.items.size())); result.total = std::max(result.total, int(result.ids.size()));
return result; return result;
} }
std::optional<StoryItem> Stories::parse(const MTPDstoryItem &data) { Story *Stories::parse(not_null<PeerData*> peer, const MTPDstoryItem &data) {
const auto id = data.vid().v; const auto id = data.vid().v;
auto &stories = _stories[peer->id];
const auto i = stories.find(id);
if (i != end(stories)) {
i->second->apply(data);
return i->second.get();
}
using MaybeMedia = std::optional< using MaybeMedia = std::optional<
std::variant<not_null<PhotoData*>, not_null<DocumentData*>>>; std::variant<not_null<PhotoData*>, not_null<DocumentData*>>>;
const auto media = data.vmedia().match([&]( const auto media = data.vmedia().match([&](
@ -95,24 +178,15 @@ std::optional<StoryItem> Stories::parse(const MTPDstoryItem &data) {
return {}; return {};
}, [](const auto &) { return MaybeMedia(); }); }, [](const auto &) { return MaybeMedia(); });
if (!media) { if (!media) {
return {}; return nullptr;
} }
auto caption = TextWithEntities{ const auto result = stories.emplace(id, std::make_unique<Story>(
data.vcaption().value_or_empty(), id,
Api::EntitiesFromMTP( peer,
&_owner->session(), StoryMedia{ *media },
data.ventities().value_or_empty()), data.vdate().v)).first->second.get();
}; result->apply(data);
auto privacy = StoryPrivacy(); return result;
const auto date = data.vdate().v;
return StoryItem{
.id = data.vid().v,
.media = { *media },
.caption = std::move(caption),
.date = date,
.privacy = privacy,
};
} }
void Stories::loadMore() { void Stories::loadMore() {
@ -134,6 +208,7 @@ void Stories::loadMore() {
for (const auto &single : data.vuser_stories().v) { for (const auto &single : data.vuser_stories().v) {
pushToBack(parse(single)); pushToBack(parse(single));
} }
_allChanged.fire({});
}, [](const MTPDstories_allStoriesNotModified &) { }, [](const MTPDstories_allStoriesNotModified &) {
}); });
}).fail([=] { }).fail([=] {
@ -153,96 +228,20 @@ rpl::producer<> Stories::allChanged() const {
return _allChanged.events(); return _allChanged.events();
} }
// #TODO stories testing base::expected<not_null<Story*>, NoStory> Stories::lookup(
StoryId Stories::generate( FullStoryId id) const {
not_null<HistoryItem*> item, const auto i = _stories.find(id.peer);
std::variant< if (i != end(_stories)) {
v::null_t, const auto j = i->second.find(id.story);
not_null<PhotoData*>, if (j != end(i->second)) {
not_null<DocumentData*>> media) { return j->second.get();
if (v::is_null(media) }
|| !item->from()->isUser()
|| !item->isRegular()) {
return {};
} }
const auto document = v::is<not_null<DocumentData*>>(media) return base::make_unexpected(
? v::get<not_null<DocumentData*>>(media).get() _deleted.contains(id) ? NoStory::Deleted : NoStory::Unknown);
: nullptr; }
if (document && !document->isVideoFile()) {
return {}; void Stories::resolve(FullStoryId id, Fn<void()> done) {
}
using namespace Storage;
auto resultId = StoryId();
const auto listType = SharedMediaType::PhotoVideo;
const auto itemId = item->id;
const auto peer = item->history()->peer;
const auto session = &peer->session();
auto full = std::vector<StoriesList>();
const auto lifetime = session->storage().query(SharedMediaQuery(
SharedMediaKey(peer->id, MsgId(0), listType, itemId),
32,
32
)) | rpl::start_with_next([&](SharedMediaResult &&result) {
if (!result.messageIds.contains(itemId)) {
result.messageIds.emplace(itemId);
}
auto index = StoryId();
const auto owner = &peer->owner();
for (const auto id : result.messageIds) {
if (const auto item = owner->message(peer, id)) {
const auto user = item->from()->asUser();
if (!user) {
continue;
}
const auto i = ranges::find(
full,
not_null(user),
&StoriesList::user);
auto &stories = (i == end(full))
? full.emplace_back(StoriesList{ .user = user })
: *i;
if (id == itemId) {
resultId = ++index;
stories.items.push_back({
.id = resultId,
.media = (document
? StoryMedia{ not_null(document) }
: StoryMedia{
v::get<not_null<PhotoData*>>(media) }),
.caption = item->originalText(),
.date = item->date(),
});
++stories.total;
} else if (const auto media = item->media()) {
const auto photo = media->photo();
const auto document = media->document();
if (photo || (document && document->isVideoFile())) {
stories.items.push_back({
.id = ++index,
.media = (document
? StoryMedia{ not_null(document) }
: StoryMedia{ not_null(photo) }),
.caption = item->originalText(),
.date = item->date(),
});
++stories.total;
}
}
}
}
for (auto &stories : full) {
const auto i = ranges::find(
_all,
stories.user,
&StoriesList::user);
if (i != end(_all)) {
*i = std::move(stories);
} else {
_all.push_back(std::move(stories));
}
}
});
return resultId;
} }
void Stories::pushToBack(StoriesList &&list) { void Stories::pushToBack(StoriesList &&list) {
@ -255,7 +254,6 @@ void Stories::pushToBack(StoriesList &&list) {
} else { } else {
_all.push_back(std::move(list)); _all.push_back(std::move(list));
} }
_allChanged.fire({});
} }
void Stories::pushToFront(StoriesList &&list) { void Stories::pushToFront(StoriesList &&list) {

View file

@ -7,37 +7,64 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
#include "base/expected.h"
class PhotoData; class PhotoData;
class DocumentData; class DocumentData;
namespace Main {
class Session;
} // namespace Main
namespace Data { namespace Data {
class Session; class Session;
struct StoryPrivacy {
friend inline bool operator==(StoryPrivacy, StoryPrivacy) = default;
};
struct StoryMedia { struct StoryMedia {
std::variant<not_null<PhotoData*>, not_null<DocumentData*>> data; std::variant<not_null<PhotoData*>, not_null<DocumentData*>> data;
friend inline bool operator==(StoryMedia, StoryMedia) = default; friend inline bool operator==(StoryMedia, StoryMedia) = default;
}; };
struct StoryItem { class Story {
StoryId id = 0; public:
StoryMedia media; Story(
TextWithEntities caption; StoryId id,
TimeId date = 0; not_null<PeerData*> peer,
StoryPrivacy privacy; StoryMedia media,
bool pinned = false; TimeId date);
[[nodiscard]] Session &owner() const;
[[nodiscard]] Main::Session &session() const;
[[nodiscard]] not_null<PeerData*> peer() const;
[[nodiscard]] StoryId id() const;
[[nodiscard]] TimeId date() const;
[[nodiscard]] const StoryMedia &media() const;
[[nodiscard]] PhotoData *photo() const;
[[nodiscard]] DocumentData *document() const;
void setPinned(bool pinned);
[[nodiscard]] bool pinned() const;
void setCaption(TextWithEntities &&caption);
[[nodiscard]] const TextWithEntities &caption() const;
void apply(const MTPDstoryItem &data);
private:
const StoryId _id = 0;
const not_null<PeerData*> _peer;
const StoryMedia _media;
TextWithEntities _caption;
const TimeId _date = 0;
bool _pinned = false;
friend inline bool operator==(StoryItem, StoryItem) = default;
}; };
struct StoriesList { struct StoriesList {
not_null<UserData*> user; not_null<UserData*> user;
std::vector<StoryItem> items; std::vector<StoryId> ids;
StoryId readTill = 0; StoryId readTill = 0;
int total = 0; int total = 0;
@ -46,6 +73,11 @@ struct StoriesList {
friend inline bool operator==(StoriesList, StoriesList) = default; friend inline bool operator==(StoriesList, StoriesList) = default;
}; };
enum class NoStory : uchar {
Unknown,
Deleted,
};
class Stories final { class Stories final {
public: public:
explicit Stories(not_null<Session*> owner); explicit Stories(not_null<Session*> owner);
@ -60,22 +92,24 @@ public:
[[nodiscard]] bool allLoaded() const; [[nodiscard]] bool allLoaded() const;
[[nodiscard]] rpl::producer<> allChanged() const; [[nodiscard]] rpl::producer<> allChanged() const;
// #TODO stories testing [[nodiscard]] base::expected<not_null<Story*>, NoStory> lookup(
[[nodiscard]] StoryId generate( FullStoryId id) const;
not_null<HistoryItem*> item, void resolve(FullStoryId id, Fn<void()> done);
std::variant<
v::null_t,
not_null<PhotoData*>,
not_null<DocumentData*>> media);
private: private:
[[nodiscard]] StoriesList parse(const MTPUserStories &stories); [[nodiscard]] StoriesList parse(const MTPUserStories &stories);
[[nodiscard]] std::optional<StoryItem> parse(const MTPDstoryItem &data); [[nodiscard]] Story *parse(
not_null<PeerData*> peer,
const MTPDstoryItem &data);
void pushToBack(StoriesList &&list); void pushToBack(StoriesList &&list);
void pushToFront(StoriesList &&list); void pushToFront(StoriesList &&list);
const not_null<Session*> _owner; const not_null<Session*> _owner;
base::flat_map<
PeerId,
base::flat_map<StoryId, std::unique_ptr<Story>>> _stories;
base::flat_set<FullStoryId> _deleted;
std::vector<StoriesList> _all; std::vector<StoriesList> _all;
rpl::event_stream<> _allChanged; rpl::event_stream<> _allChanged;

View file

@ -38,6 +38,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_chat_filters.h" #include "data/data_chat_filters.h"
#include "data/data_cloud_file.h" #include "data/data_cloud_file.h"
#include "data/data_changes.h" #include "data/data_changes.h"
#include "data/data_stories.h"
#include "data/stickers/data_stickers.h" #include "data/stickers/data_stickers.h"
#include "data/data_send_action.h" #include "data/data_send_action.h"
#include "base/unixtime.h" #include "base/unixtime.h"
@ -335,6 +336,11 @@ InnerWidget::InnerWidget(
clearSelection(); clearSelection();
}, lifetime()); }, lifetime());
_stories->clicks(
) | rpl::start_with_next([=](uint64 id) {
_controller->openPeerStories(PeerId(int64(id)));
}, lifetime());
handleChatListEntryRefreshes(); handleChatListEntryRefreshes();
refreshWithCollapsedRows(true); refreshWithCollapsedRows(true);
@ -590,6 +596,11 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
if (_controller->contentOverlapped(this, e)) { if (_controller->contentOverlapped(this, e)) {
return; return;
} }
const auto fillGuard = gsl::finally([&] {
// We translate painter down, but it'll be cropped below rect.
p.fillRect(rect(), st::dialogsBg);
});
const auto activeEntry = _controller->activeChatEntryCurrent(); const auto activeEntry = _controller->activeChatEntryCurrent();
const auto videoPaused = _controller->isGifPausedAtLeastFor( const auto videoPaused = _controller->isGifPausedAtLeastFor(
Window::GifPauseReason::Any); Window::GifPauseReason::Any);

View file

@ -128,13 +128,13 @@ State::State(not_null<Data::Stories*> data)
Content State::next() { Content State::next() {
auto result = Content(); auto result = Content();
#if 0 // #TODO stories testing #if 1 // #TODO stories testing
const auto &all = _data->all(); const auto &all = _data->all();
result.users.reserve(all.size()); result.users.reserve(all.size());
for (const auto &list : all) { for (const auto &list : all) {
auto userpic = std::shared_ptr<Userpic>(); auto userpic = std::shared_ptr<Userpic>();
const auto user = list.user; const auto user = list.user;
#endif #else
const auto list = _data->owner().chatsList(); const auto list = _data->owner().chatsList();
const auto &all = list->indexed()->all(); const auto &all = list->indexed()->all();
result.users.reserve(all.size()); result.users.reserve(all.size());
@ -142,6 +142,7 @@ Content State::next() {
if (const auto history = entry->history()) { if (const auto history = entry->history()) {
if (const auto user = history->peer->asUser(); user && !user->isBot()) { if (const auto user = history->peer->asUser(); user && !user->isBot()) {
auto userpic = std::shared_ptr<Userpic>(); 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 {
@ -152,10 +153,14 @@ 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),
.unread = history->chatListBadgesState().unread// list.unread(), #if 1 // #TODO stories testing
.unread = list.unread(),
#else
.unread = history->chatListBadgesState().unread
}); });
} }
} #endif
});
} }
return result; return result;
} }
@ -170,15 +175,16 @@ rpl::producer<Content> ContentForSession(not_null<Main::Session*> session) {
rpl::single( rpl::single(
rpl::empty rpl::empty
) | rpl::then( ) | rpl::then(
#if 0 // #TODO stories testing #if 1 // #TODO stories testing
stories->allChanged() stories->allChanged()
#endif #else
rpl::merge( rpl::merge(
session->data().chatsListChanges( session->data().chatsListChanges(
) | rpl::filter( ) | rpl::filter(
rpl::mappers::_1 == nullptr rpl::mappers::_1 == nullptr
) | rpl::to_empty, ) | rpl::to_empty,
session->data().unreadBadgeChanges()) 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

@ -29,6 +29,20 @@ constexpr auto kSummaryExpandLeft = 1.5;
} // namespace } // namespace
struct List::Layout {
int itemsCount = 0;
int shownHeight = 0;
float64 ratio = 0.;
float64 userpicLeft = 0.;
float64 photoLeft = 0.;
float64 left = 0.;
int startIndexSmall = 0;
int endIndexSmall = 0;
int startIndexFull = 0;
int endIndexFull = 0;
int singleFull = 0;
};
List::List( List::List(
not_null<QWidget*> parent, not_null<QWidget*> parent,
rpl::producer<Content> content, rpl::producer<Content> content,
@ -42,6 +56,7 @@ List::List(
}, lifetime()); }, lifetime());
_shownAnimation.stop(); _shownAnimation.stop();
setMouseTracking(true);
resize(0, _data.empty() ? 0 : st::dialogsStoriesFull.height); resize(0, _data.empty() ? 0 : st::dialogsStoriesFull.height);
} }
@ -214,30 +229,29 @@ void List::resizeEvent(QResizeEvent *e) {
updateScrollMax(); updateScrollMax();
} }
void List::paintEvent(QPaintEvent *e) { List::Layout List::computeLayout() const {
const auto &st = st::dialogsStories; const auto &st = st::dialogsStories;
const auto &full = st::dialogsStoriesFull; const auto &full = st::dialogsStoriesFull;
const auto shownHeight = std::max(_shownHeight(), st.height); const auto shownHeight = std::max(_shownHeight(), st.height);
const auto ratio = float64(shownHeight - st.height) const auto ratio = float64(shownHeight - st.height)
/ (full.height - st.height); / (full.height - st.height);
const auto lerp = [=](float64 a, float64 b) { const auto lerp = [&](float64 a, float64 b) {
return a + (b - a) * ratio; return a + (b - a) * ratio;
}; };
auto &rendering = _data.empty() ? _hidingData : _data; auto &rendering = _data.empty() ? _hidingData : _data;
const auto photo = lerp(st.photo, full.photo);
const auto photoTopSmall = (st.height - st.photo) / 2.;
const auto photoTop = lerp(photoTopSmall, full.photoTop);
const auto line = lerp(st.lineTwice, full.lineTwice) / 2.;
const auto lineRead = lerp(st.lineReadTwice, full.lineReadTwice) / 2.;
const auto summaryTop = st.nameTop
- (st.photoTop + (st.photo / 2.))
+ (photoTop + (photo / 2.));
const auto singleSmall = st.shift;
const auto singleFull = full.photoLeft * 2 + full.photo; const auto singleFull = full.photoLeft * 2 + full.photo;
const auto single = lerp(singleSmall, singleFull);
const auto itemsCount = int(rendering.items.size()); const auto itemsCount = int(rendering.items.size());
const auto leftSmall = st.left; const auto narrowWidth = st::defaultDialogRow.padding.left()
const auto leftFull = full.left - _scrollLeft; + st::defaultDialogRow.photoSize
+ st::defaultDialogRow.padding.left();
const auto narrow = (width() <= narrowWidth);
const auto smallWidth = st.photo + (itemsCount - 1) * st.shift;
const auto leftSmall = narrow
? ((narrowWidth - smallWidth) / 2 - st.photoLeft)
: st.left;
const auto leftFull = (narrow
? ((narrowWidth - full.photo) / 2 - full.photoLeft)
: full.left) - _scrollLeft;
const auto startIndexFull = std::max(-leftFull, 0) / singleFull; const auto startIndexFull = std::max(-leftFull, 0) / singleFull;
const auto cellLeftFull = leftFull + (startIndexFull * singleFull); const auto cellLeftFull = leftFull + (startIndexFull * singleFull);
const auto endIndexFull = std::min( const auto endIndexFull = std::min(
@ -250,18 +264,51 @@ void List::paintEvent(QPaintEvent *e) {
const auto userpicLeftSmall = cellLeftSmall + st.photoLeft; const auto userpicLeftSmall = cellLeftSmall + st.photoLeft;
const auto userpicLeft = lerp(userpicLeftSmall, userpicLeftFull); const auto userpicLeft = lerp(userpicLeftSmall, userpicLeftFull);
const auto photoLeft = lerp(st.photoLeft, full.photoLeft); const auto photoLeft = lerp(st.photoLeft, full.photoLeft);
const auto left = userpicLeft - photoLeft; return Layout{
const auto nameScale = shownHeight / float64(full.height); .itemsCount = itemsCount,
.shownHeight = shownHeight,
.ratio = ratio,
.userpicLeft = userpicLeft,
.photoLeft = photoLeft,
.left = userpicLeft - photoLeft,
.startIndexSmall = startIndexSmall,
.endIndexSmall = endIndexSmall,
.startIndexFull = startIndexFull,
.endIndexFull = endIndexFull,
.singleFull = singleFull,
};
}
void List::paintEvent(QPaintEvent *e) {
const auto &st = st::dialogsStories;
const auto &full = st::dialogsStoriesFull;
const auto layout = computeLayout();
const auto ratio = layout.ratio;
const auto lerp = [&](float64 a, float64 b) {
return a + (b - a) * ratio;
};
auto &rendering = _data.empty() ? _hidingData : _data;
const auto line = lerp(st.lineTwice, full.lineTwice) / 2.;
const auto lineRead = lerp(st.lineReadTwice, full.lineReadTwice) / 2.;
const auto singleSmall = st.shift;
const auto single = lerp(singleSmall, layout.singleFull);
const auto photoTopSmall = (st.height - st.photo) / 2.;
const auto photoTop = lerp(photoTopSmall, full.photoTop);
const auto photo = lerp(st.photo, full.photo);
const auto summaryTop = st.nameTop
- (st.photoTop + (st.photo / 2.))
+ (photoTop + (photo / 2.));
const auto nameScale = layout.shownHeight / float64(full.height);
const auto nameTop = nameScale * full.nameTop; const auto nameTop = nameScale * full.nameTop;
const auto nameWidth = nameScale * AvailableNameWidth(); const auto nameWidth = nameScale * AvailableNameWidth();
const auto nameHeight = nameScale * full.nameStyle.font->height; const auto nameHeight = nameScale * full.nameStyle.font->height;
const auto nameLeft = photoLeft + (photo - nameWidth) / 2.; const auto nameLeft = layout.photoLeft + (photo - nameWidth) / 2.;
const auto readUserpicOpacity = lerp(kSmallReadOpacity, 1.); const auto readUserpicOpacity = lerp(kSmallReadOpacity, 1.);
const auto readUserpicAppearingOpacity = lerp(kSmallReadOpacity, 0.); const auto readUserpicAppearingOpacity = lerp(kSmallReadOpacity, 0.);
auto p = QPainter(this); auto p = QPainter(this);
p.fillRect(e->rect(), st::dialogsBg); p.fillRect(e->rect(), st::dialogsBg);
p.translate(0, height() - shownHeight); p.translate(0, height() - layout.shownHeight);
const auto drawSmall = (ratio < 1.); const auto drawSmall = (ratio < 1.);
const auto drawFull = (ratio > 0.); const auto drawFull = (ratio > 0.);
@ -270,8 +317,8 @@ void List::paintEvent(QPaintEvent *e) {
paintSummary(p, rendering, summaryTop, ratio); paintSummary(p, rendering, summaryTop, ratio);
const auto count = std::max( const auto count = std::max(
endIndexFull - startIndexFull, layout.endIndexFull - layout.startIndexFull,
endIndexSmall - startIndexSmall); layout.endIndexSmall - layout.startIndexSmall);
struct Single { struct Single {
float64 x = 0.; float64 x = 0.;
@ -285,15 +332,15 @@ void List::paintEvent(QPaintEvent *e) {
} }
}; };
const auto lookup = [&](int index) { const auto lookup = [&](int index) {
const auto indexSmall = startIndexSmall + index; const auto indexSmall = layout.startIndexSmall + index;
const auto indexFull = startIndexFull + index; const auto indexFull = layout.startIndexFull + index;
const auto small = (drawSmall && indexSmall < endIndexSmall) const auto small = (drawSmall && indexSmall < layout.endIndexSmall)
? &rendering.items[indexSmall] ? &rendering.items[indexSmall]
: nullptr; : nullptr;
const auto full = (drawFull && indexFull < endIndexFull) const auto full = (drawFull && indexFull < layout.endIndexFull)
? &rendering.items[indexFull] ? &rendering.items[indexFull]
: nullptr; : nullptr;
const auto x = left + single * index; const auto x = layout.left + single * index;
return Single{ x, indexSmall, small, indexFull, full }; return Single{ x, indexSmall, small, indexFull, full };
}; };
const auto hasUnread = [&](const Single &single) { const auto hasUnread = [&](const Single &single) {
@ -334,7 +381,7 @@ void List::paintEvent(QPaintEvent *e) {
// Unread gradient. // Unread gradient.
const auto x = single.x; const auto x = single.x;
const auto userpic = QRectF(x + photoLeft, photoTop, photo, photo); const auto userpic = QRectF(x + layout.photoLeft, photoTop, photo, photo);
const auto small = single.itemSmall; const auto small = single.itemSmall;
const auto itemFull = single.itemFull; const auto itemFull = single.itemFull;
const auto smallUnread = small && small->user.unread; const auto smallUnread = small && small->user.unread;
@ -367,7 +414,7 @@ void List::paintEvent(QPaintEvent *e) {
Expects(single.itemSmall || single.itemFull); Expects(single.itemSmall || single.itemFull);
const auto x = single.x; const auto x = single.x;
const auto userpic = QRectF(x + photoLeft, photoTop, photo, photo); const auto userpic = QRectF(x + layout.photoLeft, photoTop, photo, photo);
const auto small = single.itemSmall; const auto small = single.itemSmall;
const auto itemFull = single.itemFull; const auto itemFull = single.itemFull;
const auto smallUnread = small && small->user.unread; const auto smallUnread = small && small->user.unread;
@ -549,7 +596,7 @@ void List::wheelEvent(QWheelEvent *e) {
if (next != now) { if (next != now) {
_expandRequests.fire({}); _expandRequests.fire({});
_scrollLeft = next; _scrollLeft = next;
//updateSelected(); updateSelected();
update(); update();
} }
e->accept(); e->accept();
@ -559,13 +606,16 @@ void List::mousePressEvent(QMouseEvent *e) {
if (e->button() != Qt::LeftButton) { if (e->button() != Qt::LeftButton) {
return; return;
} }
_mouseDownPosition = _lastMousePosition = e->globalPos(); _lastMousePosition = e->globalPos();
//updateSelected(); updateSelected();
_mouseDownPosition = _lastMousePosition;
_pressed = _selected;
} }
void List::mouseMoveEvent(QMouseEvent *e) { void List::mouseMoveEvent(QMouseEvent *e) {
_lastMousePosition = e->globalPos(); _lastMousePosition = e->globalPos();
//updateSelected(); updateSelected();
if (!_dragging && _mouseDownPosition) { if (!_dragging && _mouseDownPosition) {
if ((_lastMousePosition - *_mouseDownPosition).manhattanLength() if ((_lastMousePosition - *_mouseDownPosition).manhattanLength()
@ -601,11 +651,18 @@ void List::mouseReleaseEvent(QMouseEvent *e) {
_mouseDownPosition = std::nullopt; _mouseDownPosition = std::nullopt;
}); });
//const auto wasDown = std::exchange(_pressed, SpecialOver::None); const auto pressed = std::exchange(_pressed, -1);
if (finishDragging()) { if (finishDragging()) {
return; return;
} }
//updateSelected(); updateSelected();
if (_selected == pressed) {
if (_selected < 0) {
_expandRequests.fire({});
} else if (_selected < _data.items.size()) {
_clicks.fire_copy(_data.items[_selected].user.id);
}
}
} }
bool List::finishDragging() { bool List::finishDragging() {
@ -614,8 +671,62 @@ bool List::finishDragging() {
} }
checkDragging(); checkDragging();
_dragging = false; _dragging = false;
//updateSelected(); updateSelected();
return true; return true;
} }
void List::updateSelected() {
if (_pressed >= 0) {
return;
}
const auto &st = st::dialogsStories;
const auto &full = st::dialogsStoriesFull;
const auto p = mapFromGlobal(_lastMousePosition);
const auto layout = computeLayout();
const auto firstRightFull = full.left + layout.singleFull;
const auto firstRightSmall = st.left
+ st.photoLeft
+ st.photo;
const auto stepFull = layout.singleFull;
const auto stepSmall = st.shift;
const auto lastRightAddFull = 0;
const auto lastRightAddSmall = st.photoLeft;
const auto lerp = [&](float64 a, float64 b) {
return a + (b - a) * layout.ratio;
};
const auto firstRight = lerp(firstRightSmall, firstRightFull);
const auto step = lerp(stepSmall, stepFull);
const auto lastRightAdd = lerp(lastRightAddSmall, lastRightAddFull);
const auto activateFull = (layout.ratio >= 0.5);
const auto startIndex = activateFull
? layout.startIndexFull
: layout.startIndexSmall;
const auto endIndex = activateFull
? layout.endIndexFull
: layout.endIndexSmall;
const auto x = p.x();
const auto infiniteIndex = (x < firstRight)
? 0
: int(std::floor(((x - firstRight) / step) + 1));
const auto index = (endIndex == startIndex)
? -1
: (infiniteIndex == endIndex - startIndex
&& x < firstRight
+ (endIndex - startIndex - 1) * step
+ lastRightAdd)
? (infiniteIndex - 1) // Last small part should still be clickable.
: infiniteIndex;
const auto selected = (index < 0
|| startIndex + index >= layout.itemsCount)
? -1
: (startIndex + index);
if (_selected != selected) {
const auto over = (selected >= 0);
if (over != (_selected >= 0)) {
setCursor(over ? style::cur_pointer : style::cur_default);
}
_selected = selected;
}
}
} // namespace Dialogs::Stories } // namespace Dialogs::Stories

View file

@ -49,6 +49,7 @@ public:
[[nodiscard]] rpl::producer<> entered() const; [[nodiscard]] rpl::producer<> entered() const;
private: private:
struct Layout;
struct Item { struct Item {
User user; User user;
QImage nameCache; QImage nameCache;
@ -106,6 +107,7 @@ private:
void validateName(not_null<Item*> item); void validateName(not_null<Item*> item);
void updateScrollMax(); void updateScrollMax();
void updateSummary(Data &data); void updateSummary(Data &data);
void updateSelected();
void checkDragging(); void checkDragging();
bool finishDragging(); bool finishDragging();
@ -117,6 +119,8 @@ private:
float64 summaryTop, float64 summaryTop,
float64 hidden); float64 hidden);
[[nodiscard]] Layout computeLayout() const;
Content _content; Content _content;
Data _data; Data _data;
Data _hidingData; Data _hidingData;
@ -134,6 +138,9 @@ private:
int _scrollLeftMax = 0; int _scrollLeftMax = 0;
bool _dragging = false; bool _dragging = false;
int _selected = -1;
int _pressed = -1;
}; };
} // namespace Dialogs::Stories } // namespace Dialogs::Stories

View file

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/timer.h" #include "base/timer.h"
#include "base/power_save_blocker.h" #include "base/power_save_blocker.h"
#include "chat_helpers/compose/compose_show.h" #include "chat_helpers/compose/compose_show.h"
#include "data/data_session.h"
#include "data/data_stories.h" #include "data/data_stories.h"
#include "data/data_user.h" #include "data/data_user.h"
#include "media/stories/media_stories_caption_full_view.h" #include "media/stories/media_stories_caption_full_view.h"
@ -127,7 +128,9 @@ Controller::Controller(not_null<Delegate*> delegate)
focused ? 0. : 1., focused ? 0. : 1.,
focused ? 1. : 0., focused ? 1. : 0.,
st::fadeWrapDuration); st::fadeWrapDuration);
togglePaused(focused); if (_started) {
togglePaused(focused);
}
}, _lifetime); }, _lifetime);
_contentFadeAnimation.stop(); _contentFadeAnimation.stop();
@ -344,15 +347,23 @@ void Controller::show(
int index, int index,
int subindex) { int subindex) {
Expects(index >= 0 && index < lists.size()); Expects(index >= 0 && index < lists.size());
Expects(subindex >= 0 && subindex < lists[index].items.size()); Expects(subindex >= 0 && subindex < lists[index].ids.size());
showSiblings(lists, index); showSiblings(lists, index);
const auto &list = lists[index]; const auto &list = lists[index];
const auto &item = list.items[subindex]; const auto id = list.ids[subindex];
const auto maybeStory = list.user->owner().stories().lookup({
.peer = list.user->id,
.story = id,
});
if (!maybeStory) {
return;
}
const auto story = *maybeStory;
const auto guard = gsl::finally([&] { const auto guard = gsl::finally([&] {
_started = false; _started = false;
if (v::is<not_null<PhotoData*>>(item.media.data)) { if (story->photo()) {
_photoPlayback = std::make_unique<PhotoPlayback>(this); _photoPlayback = std::make_unique<PhotoPlayback>(this);
} else { } else {
_photoPlayback = nullptr; _photoPlayback = nullptr;
@ -363,20 +374,20 @@ void Controller::show(
} }
_index = subindex; _index = subindex;
const auto id = FullStoryId{ const auto storyId = FullStoryId{
.peer = list.user->id, .peer = list.user->id,
.story = item.id, .story = id,
}; };
if (_shown == id) { if (_shown == storyId) {
return; return;
} }
_shown = id; _shown = storyId;
_captionText = item.caption; _captionText = story->caption();
_captionFullView = nullptr; _captionFullView = nullptr;
_header->show({ .user = list.user, .date = item.date }); _header->show({ .user = list.user, .date = story->date() });
_slider->show({ .index = _index, .total = list.total }); _slider->show({ .index = _index, .total = list.total });
_replyArea->show({ .user = list.user, .id = id.story }); _replyArea->show({ .user = list.user, .id = id });
if (_contentFaded) { if (_contentFaded) {
togglePaused(true); togglePaused(true);
@ -395,7 +406,7 @@ void Controller::showSiblings(
void Controller::showSibling( void Controller::showSibling(
std::unique_ptr<Sibling> &sibling, std::unique_ptr<Sibling> &sibling,
const Data::StoriesList *list) { const Data::StoriesList *list) {
if (!list || list->items.empty()) { if (!list || list->ids.empty()) {
sibling = nullptr; sibling = nullptr;
} else if (!sibling || !sibling->shows(*list)) { } else if (!sibling || !sibling->shows(*list)) {
sibling = std::make_unique<Sibling>(this, *list); sibling = std::make_unique<Sibling>(this, *list);
@ -407,7 +418,7 @@ void Controller::ready() {
return; return;
} }
_started = true; _started = true;
if (_photoPlayback) { if (!_contentFaded && _photoPlayback) {
_photoPlayback->togglePaused(false); _photoPlayback->togglePaused(false);
} }
} }
@ -445,23 +456,23 @@ 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->items.empty()) { } else if (!_list || _list->ids.empty()) {
return false; return false;
} }
_delegate->storiesJumpTo(&_list->user->session(), { _delegate->storiesJumpTo(&_list->user->session(), {
.peer = _list->user->id, .peer = _list->user->id,
.story = _list->items.front().id .story = _list->ids.front()
}); });
return true; return true;
} else if (index >= _list->total) { } else if (index >= _list->total) {
return _siblingRight return _siblingRight
&& _siblingRight->shownId().valid() && _siblingRight->shownId().valid()
&& jumpFor(1); && jumpFor(1);
} else if (index < _list->items.size()) { } else if (index < _list->ids.size()) {
// #TODO stories load more // #TODO stories load more
_delegate->storiesJumpTo(&_list->user->session(), { _delegate->storiesJumpTo(&_list->user->session(), {
.peer = _list->user->id, .peer = _list->user->id,
.story = _list->items[index].id .story = _list->ids[index]
}); });
} }
return true; return true;

View file

@ -217,29 +217,47 @@ Sibling::Sibling(
not_null<Controller*> controller, not_null<Controller*> controller,
const Data::StoriesList &list) const Data::StoriesList &list)
: _controller(controller) : _controller(controller)
, _id{ list.user->id, list.items.front().id } , _id{ list.user->id, list.ids.front() }
, _peer(list.user) { , _peer(list.user) {
const auto &item = list.items.front(); checkStory();
const auto &data = item.media.data;
const auto origin = Data::FileOrigin();
if (const auto video = std::get_if<not_null<DocumentData*>>(&data)) {
_loader = std::make_unique<LoaderVideo>((*video), origin, [=] {
check();
});
} else if (const auto photo = std::get_if<not_null<PhotoData*>>(&data)) {
_loader = std::make_unique<LoaderPhoto>((*photo), origin, [=] {
check();
});
} else {
Unexpected("Media type in stories list.");
}
_blurred = _loader->blurred();
check();
_goodShown.stop(); _goodShown.stop();
} }
Sibling::~Sibling() = default; Sibling::~Sibling() = default;
void Sibling::checkStory() {
const auto maybeStory = _peer->owner().stories().lookup(_id);
if (!maybeStory) {
if (_blurred.isNull()) {
_blurred = QImage(
st::storiesMaxSize,
QImage::Format_ARGB32_Premultiplied);
_blurred.fill(Qt::black);
if (maybeStory.error() == Data::NoStory::Unknown) {
_peer->owner().stories().resolve(_id, crl::guard(this, [=] {
checkStory();
}));
}
}
return;
}
const auto story = *maybeStory;
const auto &data = story->media().data;
const auto origin = Data::FileOrigin();
v::match(story->media().data, [&](not_null<PhotoData*> photo) {
_loader = std::make_unique<LoaderPhoto>(photo, origin, [=] {
check();
});
}, [&](not_null<DocumentData*> document) {
_loader = std::make_unique<LoaderVideo>(document, origin, [=] {
check();
});
});
_blurred = _loader->blurred();
check();
}
FullStoryId Sibling::shownId() const { FullStoryId Sibling::shownId() const {
return _id; return _id;
} }
@ -249,9 +267,9 @@ not_null<PeerData*> Sibling::peer() const {
} }
bool Sibling::shows(const Data::StoriesList &list) const { bool Sibling::shows(const Data::StoriesList &list) const {
Expects(!list.items.empty()); Expects(!list.ids.empty());
return _id == FullStoryId{ list.user->id, list.items.front().id }; return _id == FullStoryId{ list.user->id, list.ids.front() };
} }
SiblingView Sibling::view(const SiblingLayout &layout, float64 over) { SiblingView Sibling::view(const SiblingLayout &layout, float64 over) {

View file

@ -7,8 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
#include "base/weak_ptr.h"
#include "data/data_stories.h" #include "data/data_stories.h"
#include "ui/effects/animations.h" #include "ui/effects/animations.h"
#include "ui/userpic_view.h" #include "ui/userpic_view.h"
@ -22,7 +22,7 @@ class Controller;
struct SiblingView; struct SiblingView;
struct SiblingLayout; struct SiblingLayout;
class Sibling final { class Sibling final : public base::has_weak_ptr {
public: public:
Sibling( Sibling(
not_null<Controller*> controller, not_null<Controller*> controller,
@ -42,6 +42,7 @@ private:
class LoaderPhoto; class LoaderPhoto;
class LoaderVideo; class LoaderVideo;
void checkStory();
void check(); void check();
[[nodiscard]] QImage userpicImage(const SiblingLayout &layout); [[nodiscard]] QImage userpicImage(const SiblingLayout &layout);

View file

@ -14,6 +14,10 @@ class PeerData;
class PhotoData; class PhotoData;
class HistoryItem; class HistoryItem;
namespace Data {
class Story;
} // namespace Data
namespace Window { namespace Window {
class SessionController; class SessionController;
} // namespace Window } // namespace Window
@ -67,6 +71,17 @@ public:
, _cloudTheme(cloudTheme) { , _cloudTheme(cloudTheme) {
} }
OpenRequest(
Window::SessionController *controller,
not_null<Data::Story*> story,
bool continueStreaming = false,
crl::time startTime = 0)
: _controller(controller)
, _story(story)
, _continueStreaming(continueStreaming)
, _startTime(startTime) {
}
[[nodiscard]] PeerData *peer() const { [[nodiscard]] PeerData *peer() const {
return _peer; return _peer;
} }
@ -87,6 +102,10 @@ public:
return _document; return _document;
} }
[[nodiscard]] Data::Story *story() const {
return _story;
}
[[nodiscard]] std::optional<Data::CloudTheme> cloudTheme() const { [[nodiscard]] std::optional<Data::CloudTheme> cloudTheme() const {
return _cloudTheme; return _cloudTheme;
} }
@ -107,6 +126,7 @@ private:
Window::SessionController *_controller = nullptr; Window::SessionController *_controller = nullptr;
DocumentData *_document = nullptr; DocumentData *_document = nullptr;
PhotoData *_photo = nullptr; PhotoData *_photo = nullptr;
Data::Story *_story = nullptr;
PeerData *_peer = nullptr; PeerData *_peer = nullptr;
HistoryItem *_item = nullptr; HistoryItem *_item = nullptr;
MsgId _topicRootId = 0; MsgId _topicRootId = 0;

View file

@ -3036,8 +3036,9 @@ void OverlayWidget::activate() {
} }
void OverlayWidget::show(OpenRequest request) { void OverlayWidget::show(OpenRequest request) {
const auto document = request.document(); const auto story = request.story();
const auto photo = request.photo(); const auto document = story ? story->document() : request.document();
const auto photo = story ? story->photo() : request.photo();
const auto contextItem = request.item(); const auto contextItem = request.item();
const auto contextPeer = request.peer(); const auto contextPeer = request.peer();
const auto contextTopicRootId = request.topicRootId(); const auto contextTopicRootId = request.topicRootId();
@ -3057,15 +3058,9 @@ void OverlayWidget::show(OpenRequest request) {
} }
setSession(&photo->session()); setSession(&photo->session());
// #TODO stories testing if (story) {
if (const auto storyId = (!contextPeer && contextItem) setContext(StoriesContext{ story->peer(), story->id() });
? contextItem->history()->owner().stories().generate( } else if (contextPeer) {
contextItem,
photo)
: StoryId()) {
setContext(StoriesContext{ contextItem->from()->asUser(), storyId });
} else
if (contextPeer) {
setContext(contextPeer); setContext(contextPeer);
} else if (contextItem) { } else if (contextItem) {
setContext(ItemContext{ contextItem, contextTopicRootId }); setContext(ItemContext{ contextItem, contextTopicRootId });
@ -3083,15 +3078,9 @@ void OverlayWidget::show(OpenRequest request) {
} else if (document) { } else if (document) {
setSession(&document->session()); setSession(&document->session());
// #TODO stories testing if (story) {
if (const auto storyId = contextItem setContext(StoriesContext{ story->peer(), story->id() });
? contextItem->history()->owner().stories().generate( } else if (contextItem) {
contextItem,
document)
: StoryId()) {
setContext(StoriesContext{ contextItem->from()->asUser(), storyId });
} else
if (contextItem) {
setContext(ItemContext{ contextItem, contextTopicRootId }); setContext(ItemContext{ contextItem, contextTopicRootId });
} else { } else {
setContext(v::null); setContext(v::null);
@ -4034,30 +4023,20 @@ void OverlayWidget::storiesJumpTo(
Expects(_stories != nullptr); Expects(_stories != nullptr);
Expects(id.valid()); Expects(id.valid());
const auto &all = session->data().stories().all(); const auto maybeStory = session->data().stories().lookup(id);
const auto i = ranges::find( if (!maybeStory) {
all,
id.peer,
[](const Data::StoriesList &list) { return list.user->id; });
if (i == end(all)) {
close(); close();
return; return;
} }
const auto j = ranges::find(i->items, id.story, &Data::StoryItem::id); const auto story = *maybeStory;
if (j == end(i->items)) { setContext(StoriesContext{ story->peer(), story->id() });
close();
return;
}
setContext(StoriesContext{ i->user, id.story });
clearStreaming(); clearStreaming();
_streamingStartPaused = false; _streamingStartPaused = false;
const auto &data = j->media.data; v::match(story->media().data, [&](not_null<PhotoData*> photo) {
const auto activation = anim::activation::background; displayPhoto(photo, anim::activation::background);
if (const auto photo = std::get_if<not_null<PhotoData*>>(&data)) { }, [&](not_null<DocumentData*> document) {
displayPhoto(*photo, activation); displayDocument(document, anim::activation::background);
} else { });
displayDocument(v::get<not_null<DocumentData*>>(data), activation);
}
} }
void OverlayWidget::storiesClose() { void OverlayWidget::storiesClose() {
@ -4976,36 +4955,33 @@ void OverlayWidget::setContext(
_history = _message->history(); _history = _message->history();
_peer = _history->peer; _peer = _history->peer;
_topicRootId = _peer->isForum() ? item->topicRootId : MsgId(); _topicRootId = _peer->isForum() ? item->topicRootId : MsgId();
setStoriesUser(nullptr); setStoriesPeer(nullptr);
} else if (const auto peer = std::get_if<not_null<PeerData*>>(&context)) { } else if (const auto peer = std::get_if<not_null<PeerData*>>(&context)) {
_peer = *peer; _peer = *peer;
_history = _peer->owner().history(_peer); _history = _peer->owner().history(_peer);
_message = nullptr; _message = nullptr;
_topicRootId = MsgId(); _topicRootId = MsgId();
setStoriesUser(nullptr); setStoriesPeer(nullptr);
} else if (const auto story = std::get_if<StoriesContext>(&context)) { } else if (const auto story = std::get_if<StoriesContext>(&context)) {
_message = nullptr; _message = nullptr;
_topicRootId = MsgId(); _topicRootId = MsgId();
_history = nullptr; _history = nullptr;
_peer = nullptr; _peer = nullptr;
const auto &all = story->user->owner().stories().all(); const auto &all = story->peer->owner().stories().all();
const auto i = ranges::find( const auto i = ranges::find(
all, all,
story->user, story->peer,
&Data::StoriesList::user); &Data::StoriesList::user);
Assert(i != end(all)); Assert(i != end(all));
const auto j = ranges::find( const auto j = ranges::find(i->ids, story->id);
i->items, setStoriesPeer(story->peer);
story->id, _stories->show(all, (i - begin(all)), j - begin(i->ids));
&Data::StoryItem::id);
setStoriesUser(story->user);
_stories->show(all, (i - begin(all)), j - begin(i->items));
} else { } else {
_message = nullptr; _message = nullptr;
_topicRootId = MsgId(); _topicRootId = MsgId();
_history = nullptr; _history = nullptr;
_peer = nullptr; _peer = nullptr;
setStoriesUser(nullptr); setStoriesPeer(nullptr);
} }
_migrated = nullptr; _migrated = nullptr;
if (_history) { if (_history) {
@ -5020,11 +4996,11 @@ void OverlayWidget::setContext(
_user = _peer ? _peer->asUser() : nullptr; _user = _peer ? _peer->asUser() : nullptr;
} }
void OverlayWidget::setStoriesUser(UserData *user) { void OverlayWidget::setStoriesPeer(PeerData *peer) {
const auto session = user ? &user->session() : nullptr; const auto session = peer ? &peer->session() : nullptr;
if (!session && !_storiesSession) { if (!session && !_storiesSession) {
Assert(!_stories); Assert(!_stories);
} else if (!user) { } else if (!peer) {
_stories = nullptr; _stories = nullptr;
_storiesSession = nullptr; _storiesSession = nullptr;
_storiesChanged.fire({}); _storiesChanged.fire({});
@ -5099,14 +5075,6 @@ bool OverlayWidget::moveToEntity(const Entity &entity, int preloadDelta) {
if (v::is_null(entity.data) && !entity.item) { if (v::is_null(entity.data) && !entity.item) {
return false; return false;
} }
// #TODO stories testing
if (const auto storyId = entity.item
? entity.item->history()->owner().stories().generate(
entity.item,
entity.data)
: StoryId()) {
setContext(StoriesContext{ entity.item->from()->asUser(), storyId });
} else
if (const auto item = entity.item) { if (const auto item = entity.item) {
setContext(ItemContext{ item, entity.topicRootId }); setContext(ItemContext{ item, entity.topicRootId });
} else if (_peer) { } else if (_peer) {
@ -5765,7 +5733,7 @@ void OverlayWidget::clearBeforeHide() {
_collage = nullptr; _collage = nullptr;
_collageData = std::nullopt; _collageData = std::nullopt;
clearStreaming(); clearStreaming();
setStoriesUser(nullptr); setStoriesPeer(nullptr);
_layerBg->hideAll(anim::type::instant); _layerBg->hideAll(anim::type::instant);
assignMediaPointer(nullptr); assignMediaPointer(nullptr);
_preloadPhotos.clear(); _preloadPhotos.clear();

View file

@ -303,7 +303,7 @@ private:
MsgId topicRootId = 0; MsgId topicRootId = 0;
}; };
struct StoriesContext { struct StoriesContext {
not_null<UserData*> user; not_null<PeerData*> peer;
StoryId id = 0; StoryId id = 0;
}; };
void setContext(std::variant< void setContext(std::variant<
@ -311,7 +311,7 @@ private:
ItemContext, ItemContext,
not_null<PeerData*>, not_null<PeerData*>,
StoriesContext> context); StoriesContext> context);
void setStoriesUser(UserData *user); void setStoriesPeer(PeerData *peer);
void refreshLang(); void refreshLang();
void showSaveMsgFile(); void showSaveMsgFile();

View file

@ -45,6 +45,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_chat_filters.h" #include "data/data_chat_filters.h"
#include "data/data_replies_list.h" #include "data/data_replies_list.h"
#include "data/data_peer_values.h" #include "data/data_peer_values.h"
#include "data/data_stories.h"
#include "passport/passport_form_controller.h" #include "passport/passport_form_controller.h"
#include "chat_helpers/tabbed_selector.h" #include "chat_helpers/tabbed_selector.h"
#include "chat_helpers/emoji_interactions.h" #include "chat_helpers/emoji_interactions.h"
@ -2463,6 +2464,22 @@ Ui::ChatThemeBackgroundData SessionController::backgroundData(
}; };
} }
void SessionController::openPeerStories(PeerId peerId) {
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()) {
if (const auto from = stories.lookup({ peerId, i->ids.front() })) {
window().openInMediaView(OpenRequest(this, *from));
}
}
}
HistoryView::PaintContext SessionController::preparePaintContext( HistoryView::PaintContext SessionController::preparePaintContext(
PaintContextArgs &&args) { PaintContextArgs &&args) {
const auto visibleAreaTopLocal = content()->mapFromGlobal( const auto visibleAreaTopLocal = content()->mapFromGlobal(

View file

@ -564,6 +564,8 @@ public:
return _peerThemeOverride.value(); return _peerThemeOverride.value();
} }
void openPeerStories(PeerId peerId);
struct PaintContextArgs { struct PaintContextArgs {
not_null<Ui::ChatTheme*> theme; not_null<Ui::ChatTheme*> theme;
int visibleAreaTop = 0; int visibleAreaTop = 0;