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
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) {
@ -41,6 +112,7 @@ Session &Stories::owner() const {
void Stories::apply(const MTPDupdateStories &data) {
pushToFront(parse(data.vstories()));
_allChanged.fire({});
}
StoriesList Stories::parse(const MTPUserStories &stories) {
@ -54,25 +126,36 @@ StoriesList Stories::parse(const MTPUserStories &stories) {
.total = count,
};
const auto &list = data.vstories().v;
result.items.reserve(list.size());
result.ids.reserve(list.size());
for (const auto &story : list) {
story.match([&](const MTPDstoryItem &data) {
if (auto entry = parse(data)) {
result.items.push_back(std::move(*entry));
if (const auto story = parse(result.user, data)) {
result.ids.push_back(story->id());
} else {
--result.total;
}
}, [&](const MTPDstoryItemSkipped &) {
}, [&](const MTPDstoryItemDeleted &) {
}, [&](const MTPDstoryItemSkipped &data) {
result.ids.push_back(data.vid().v);
}, [&](const MTPDstoryItemDeleted &data) {
_deleted.emplace(FullStoryId{
.peer = peerFromUser(userId),
.story = data.vid().v,
});
--result.total;
});
}
result.total = std::min(result.total, int(result.items.size()));
result.total = std::max(result.total, int(result.ids.size()));
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;
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<
std::variant<not_null<PhotoData*>, not_null<DocumentData*>>>;
const auto media = data.vmedia().match([&](
@ -95,24 +178,15 @@ std::optional<StoryItem> Stories::parse(const MTPDstoryItem &data) {
return {};
}, [](const auto &) { return MaybeMedia(); });
if (!media) {
return {};
return nullptr;
}
auto caption = TextWithEntities{
data.vcaption().value_or_empty(),
Api::EntitiesFromMTP(
&_owner->session(),
data.ventities().value_or_empty()),
};
auto privacy = StoryPrivacy();
const auto date = data.vdate().v;
return StoryItem{
.id = data.vid().v,
.media = { *media },
.caption = std::move(caption),
.date = date,
.privacy = privacy,
};
const auto result = stories.emplace(id, std::make_unique<Story>(
id,
peer,
StoryMedia{ *media },
data.vdate().v)).first->second.get();
result->apply(data);
return result;
}
void Stories::loadMore() {
@ -134,6 +208,7 @@ void Stories::loadMore() {
for (const auto &single : data.vuser_stories().v) {
pushToBack(parse(single));
}
_allChanged.fire({});
}, [](const MTPDstories_allStoriesNotModified &) {
});
}).fail([=] {
@ -153,96 +228,20 @@ rpl::producer<> Stories::allChanged() const {
return _allChanged.events();
}
// #TODO stories testing
StoryId Stories::generate(
not_null<HistoryItem*> item,
std::variant<
v::null_t,
not_null<PhotoData*>,
not_null<DocumentData*>> media) {
if (v::is_null(media)
|| !item->from()->isUser()
|| !item->isRegular()) {
return {};
base::expected<not_null<Story*>, NoStory> Stories::lookup(
FullStoryId id) const {
const auto i = _stories.find(id.peer);
if (i != end(_stories)) {
const auto j = i->second.find(id.story);
if (j != end(i->second)) {
return j->second.get();
}
}
const auto document = v::is<not_null<DocumentData*>>(media)
? v::get<not_null<DocumentData*>>(media).get()
: nullptr;
if (document && !document->isVideoFile()) {
return {};
}
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;
return base::make_unexpected(
_deleted.contains(id) ? NoStory::Deleted : NoStory::Unknown);
}
void Stories::resolve(FullStoryId id, Fn<void()> done) {
}
void Stories::pushToBack(StoriesList &&list) {
@ -255,7 +254,6 @@ void Stories::pushToBack(StoriesList &&list) {
} else {
_all.push_back(std::move(list));
}
_allChanged.fire({});
}
void Stories::pushToFront(StoriesList &&list) {

View File

@ -7,37 +7,64 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "base/expected.h"
class PhotoData;
class DocumentData;
namespace Main {
class Session;
} // namespace Main
namespace Data {
class Session;
struct StoryPrivacy {
friend inline bool operator==(StoryPrivacy, StoryPrivacy) = default;
};
struct StoryMedia {
std::variant<not_null<PhotoData*>, not_null<DocumentData*>> data;
friend inline bool operator==(StoryMedia, StoryMedia) = default;
};
struct StoryItem {
StoryId id = 0;
StoryMedia media;
TextWithEntities caption;
TimeId date = 0;
StoryPrivacy privacy;
bool pinned = false;
class Story {
public:
Story(
StoryId id,
not_null<PeerData*> peer,
StoryMedia media,
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 {
not_null<UserData*> user;
std::vector<StoryItem> items;
std::vector<StoryId> ids;
StoryId readTill = 0;
int total = 0;
@ -46,6 +73,11 @@ struct StoriesList {
friend inline bool operator==(StoriesList, StoriesList) = default;
};
enum class NoStory : uchar {
Unknown,
Deleted,
};
class Stories final {
public:
explicit Stories(not_null<Session*> owner);
@ -60,22 +92,24 @@ public:
[[nodiscard]] bool allLoaded() const;
[[nodiscard]] rpl::producer<> allChanged() const;
// #TODO stories testing
[[nodiscard]] StoryId generate(
not_null<HistoryItem*> item,
std::variant<
v::null_t,
not_null<PhotoData*>,
not_null<DocumentData*>> media);
[[nodiscard]] base::expected<not_null<Story*>, NoStory> lookup(
FullStoryId id) const;
void resolve(FullStoryId id, Fn<void()> done);
private:
[[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 pushToFront(StoriesList &&list);
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;
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_cloud_file.h"
#include "data/data_changes.h"
#include "data/data_stories.h"
#include "data/stickers/data_stickers.h"
#include "data/data_send_action.h"
#include "base/unixtime.h"
@ -335,6 +336,11 @@ InnerWidget::InnerWidget(
clearSelection();
}, lifetime());
_stories->clicks(
) | rpl::start_with_next([=](uint64 id) {
_controller->openPeerStories(PeerId(int64(id)));
}, lifetime());
handleChatListEntryRefreshes();
refreshWithCollapsedRows(true);
@ -590,6 +596,11 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
if (_controller->contentOverlapped(this, e)) {
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 videoPaused = _controller->isGifPausedAtLeastFor(
Window::GifPauseReason::Any);

View File

@ -128,13 +128,13 @@ State::State(not_null<Data::Stories*> data)
Content State::next() {
auto result = Content();
#if 0 // #TODO stories testing
#if 1 // #TODO stories testing
const auto &all = _data->all();
result.users.reserve(all.size());
for (const auto &list : all) {
auto userpic = std::shared_ptr<Userpic>();
const auto user = list.user;
#endif
#else
const auto list = _data->owner().chatsList();
const auto &all = list->indexed()->all();
result.users.reserve(all.size());
@ -142,6 +142,7 @@ Content State::next() {
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)) {
userpic = i->second;
} else {
@ -152,10 +153,14 @@ Content State::next() {
.id = uint64(user->id.value),
.name = user->shortName(),
.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;
}
@ -170,15 +175,16 @@ rpl::producer<Content> ContentForSession(not_null<Main::Session*> session) {
rpl::single(
rpl::empty
) | rpl::then(
#if 0 // #TODO stories testing
#if 1 // #TODO stories testing
stories->allChanged()
#endif
#else
rpl::merge(
session->data().chatsListChanges(
) | rpl::filter(
rpl::mappers::_1 == nullptr
) | rpl::to_empty,
session->data().unreadBadgeChanges())
#endif
) | rpl::start_with_next([=] {
consumer.put_next(state->next());
}, result);

View File

@ -29,6 +29,20 @@ constexpr auto kSummaryExpandLeft = 1.5;
} // 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(
not_null<QWidget*> parent,
rpl::producer<Content> content,
@ -42,6 +56,7 @@ List::List(
}, lifetime());
_shownAnimation.stop();
setMouseTracking(true);
resize(0, _data.empty() ? 0 : st::dialogsStoriesFull.height);
}
@ -214,30 +229,29 @@ void List::resizeEvent(QResizeEvent *e) {
updateScrollMax();
}
void List::paintEvent(QPaintEvent *e) {
List::Layout List::computeLayout() const {
const auto &st = st::dialogsStories;
const auto &full = st::dialogsStoriesFull;
const auto shownHeight = std::max(_shownHeight(), st.height);
const auto ratio = float64(shownHeight - 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;
};
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 single = lerp(singleSmall, singleFull);
const auto itemsCount = int(rendering.items.size());
const auto leftSmall = st.left;
const auto leftFull = full.left - _scrollLeft;
const auto narrowWidth = st::defaultDialogRow.padding.left()
+ 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 cellLeftFull = leftFull + (startIndexFull * singleFull);
const auto endIndexFull = std::min(
@ -250,18 +264,51 @@ void List::paintEvent(QPaintEvent *e) {
const auto userpicLeftSmall = cellLeftSmall + st.photoLeft;
const auto userpicLeft = lerp(userpicLeftSmall, userpicLeftFull);
const auto photoLeft = lerp(st.photoLeft, full.photoLeft);
const auto left = userpicLeft - photoLeft;
const auto nameScale = shownHeight / float64(full.height);
return Layout{
.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 nameWidth = nameScale * AvailableNameWidth();
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 readUserpicAppearingOpacity = lerp(kSmallReadOpacity, 0.);
auto p = QPainter(this);
p.fillRect(e->rect(), st::dialogsBg);
p.translate(0, height() - shownHeight);
p.translate(0, height() - layout.shownHeight);
const auto drawSmall = (ratio < 1.);
const auto drawFull = (ratio > 0.);
@ -270,8 +317,8 @@ void List::paintEvent(QPaintEvent *e) {
paintSummary(p, rendering, summaryTop, ratio);
const auto count = std::max(
endIndexFull - startIndexFull,
endIndexSmall - startIndexSmall);
layout.endIndexFull - layout.startIndexFull,
layout.endIndexSmall - layout.startIndexSmall);
struct Single {
float64 x = 0.;
@ -285,15 +332,15 @@ void List::paintEvent(QPaintEvent *e) {
}
};
const auto lookup = [&](int index) {
const auto indexSmall = startIndexSmall + index;
const auto indexFull = startIndexFull + index;
const auto small = (drawSmall && indexSmall < endIndexSmall)
const auto indexSmall = layout.startIndexSmall + index;
const auto indexFull = layout.startIndexFull + index;
const auto small = (drawSmall && indexSmall < layout.endIndexSmall)
? &rendering.items[indexSmall]
: nullptr;
const auto full = (drawFull && indexFull < endIndexFull)
const auto full = (drawFull && indexFull < layout.endIndexFull)
? &rendering.items[indexFull]
: nullptr;
const auto x = left + single * index;
const auto x = layout.left + single * index;
return Single{ x, indexSmall, small, indexFull, full };
};
const auto hasUnread = [&](const Single &single) {
@ -334,7 +381,7 @@ void List::paintEvent(QPaintEvent *e) {
// Unread gradient.
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 itemFull = single.itemFull;
const auto smallUnread = small && small->user.unread;
@ -367,7 +414,7 @@ void List::paintEvent(QPaintEvent *e) {
Expects(single.itemSmall || single.itemFull);
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 itemFull = single.itemFull;
const auto smallUnread = small && small->user.unread;
@ -549,7 +596,7 @@ void List::wheelEvent(QWheelEvent *e) {
if (next != now) {
_expandRequests.fire({});
_scrollLeft = next;
//updateSelected();
updateSelected();
update();
}
e->accept();
@ -559,13 +606,16 @@ void List::mousePressEvent(QMouseEvent *e) {
if (e->button() != Qt::LeftButton) {
return;
}
_mouseDownPosition = _lastMousePosition = e->globalPos();
//updateSelected();
_lastMousePosition = e->globalPos();
updateSelected();
_mouseDownPosition = _lastMousePosition;
_pressed = _selected;
}
void List::mouseMoveEvent(QMouseEvent *e) {
_lastMousePosition = e->globalPos();
//updateSelected();
updateSelected();
if (!_dragging && _mouseDownPosition) {
if ((_lastMousePosition - *_mouseDownPosition).manhattanLength()
@ -601,11 +651,18 @@ void List::mouseReleaseEvent(QMouseEvent *e) {
_mouseDownPosition = std::nullopt;
});
//const auto wasDown = std::exchange(_pressed, SpecialOver::None);
const auto pressed = std::exchange(_pressed, -1);
if (finishDragging()) {
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() {
@ -614,8 +671,62 @@ bool List::finishDragging() {
}
checkDragging();
_dragging = false;
//updateSelected();
updateSelected();
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

View File

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

View File

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

View File

@ -217,29 +217,47 @@ Sibling::Sibling(
not_null<Controller*> controller,
const Data::StoriesList &list)
: _controller(controller)
, _id{ list.user->id, list.items.front().id }
, _id{ list.user->id, list.ids.front() }
, _peer(list.user) {
const auto &item = list.items.front();
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();
checkStory();
_goodShown.stop();
}
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 {
return _id;
}
@ -249,9 +267,9 @@ not_null<PeerData*> Sibling::peer() 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) {

View File

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

View File

@ -14,6 +14,10 @@ class PeerData;
class PhotoData;
class HistoryItem;
namespace Data {
class Story;
} // namespace Data
namespace Window {
class SessionController;
} // namespace Window
@ -67,6 +71,17 @@ public:
, _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 {
return _peer;
}
@ -87,6 +102,10 @@ public:
return _document;
}
[[nodiscard]] Data::Story *story() const {
return _story;
}
[[nodiscard]] std::optional<Data::CloudTheme> cloudTheme() const {
return _cloudTheme;
}
@ -107,6 +126,7 @@ private:
Window::SessionController *_controller = nullptr;
DocumentData *_document = nullptr;
PhotoData *_photo = nullptr;
Data::Story *_story = nullptr;
PeerData *_peer = nullptr;
HistoryItem *_item = nullptr;
MsgId _topicRootId = 0;

View File

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

View File

@ -303,7 +303,7 @@ private:
MsgId topicRootId = 0;
};
struct StoriesContext {
not_null<UserData*> user;
not_null<PeerData*> peer;
StoryId id = 0;
};
void setContext(std::variant<
@ -311,7 +311,7 @@ private:
ItemContext,
not_null<PeerData*>,
StoriesContext> context);
void setStoriesUser(UserData *user);
void setStoriesPeer(PeerData *peer);
void refreshLang();
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_replies_list.h"
#include "data/data_peer_values.h"
#include "data/data_stories.h"
#include "passport/passport_form_controller.h"
#include "chat_helpers/tabbed_selector.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(
PaintContextArgs &&args) {
const auto visibleAreaTopLocal = content()->mapFromGlobal(

View File

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