Allow navigating to stories of sibling users.

This commit is contained in:
John Preston 2023-05-09 14:31:48 +04:00
parent 7717de19ab
commit ae94cd2d42
20 changed files with 699 additions and 93 deletions

View File

@ -970,6 +970,8 @@ PRIVATE
media/stories/media_stories_header.h media/stories/media_stories_header.h
media/stories/media_stories_reply.cpp media/stories/media_stories_reply.cpp
media/stories/media_stories_reply.h media/stories/media_stories_reply.h
media/stories/media_stories_sibling.cpp
media/stories/media_stories_sibling.h
media/stories/media_stories_slider.cpp media/stories/media_stories_slider.cpp
media/stories/media_stories_slider.h media/stories/media_stories_slider.h
media/stories/media_stories_view.cpp media/stories/media_stories_view.cpp

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 806 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -173,7 +173,7 @@ StoryId Stories::generate(
const auto itemId = item->id; const auto itemId = item->id;
const auto peer = item->history()->peer; const auto peer = item->history()->peer;
const auto session = &peer->session(); const auto session = &peer->session();
auto stories = StoriesList{ .user = item->from()->asUser() }; auto full = std::vector<StoriesList>();
const auto lifetime = session->storage().query(SharedMediaQuery( const auto lifetime = session->storage().query(SharedMediaQuery(
SharedMediaKey(peer->id, MsgId(0), listType, itemId), SharedMediaKey(peer->id, MsgId(0), listType, itemId),
32, 32,
@ -182,21 +182,33 @@ StoryId Stories::generate(
if (!result.messageIds.contains(itemId)) { if (!result.messageIds.contains(itemId)) {
result.messageIds.emplace(itemId); result.messageIds.emplace(itemId);
} }
stories.items.reserve(result.messageIds.size());
auto index = StoryId(); auto index = StoryId();
const auto owner = &peer->owner(); const auto owner = &peer->owner();
for (const auto id : result.messageIds) { for (const auto id : result.messageIds) {
if (const auto item = owner->message(peer, id)) { 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) { if (id == itemId) {
resultId = ++index; resultId = ++index;
stories.items.push_back({ stories.items.push_back({
.id = resultId, .id = resultId,
.media = (document .media = (document
? StoryMedia{ not_null(document) } ? StoryMedia{ not_null(document) }
: StoryMedia{ v::get<not_null<PhotoData*>>(media) }), : StoryMedia{
v::get<not_null<PhotoData*>>(media) }),
.caption = item->originalText(), .caption = item->originalText(),
.date = item->date(), .date = item->date(),
}); });
++stories.total;
} else if (const auto media = item->media()) { } else if (const auto media = item->media()) {
const auto photo = media->photo(); const auto photo = media->photo();
const auto document = media->document(); const auto document = media->document();
@ -209,18 +221,21 @@ StoryId Stories::generate(
.caption = item->originalText(), .caption = item->originalText(),
.date = item->date(), .date = item->date(),
}); });
++stories.total;
} }
} }
} }
} }
stories.total = std::max( for (auto &stories : full) {
result.count.value_or(1), const auto i = ranges::find(
int(result.messageIds.size())); _all,
const auto i = ranges::find(_all, stories.user, &StoriesList::user); stories.user,
if (i != end(_all)) { &StoriesList::user);
*i = std::move(stories); if (i != end(_all)) {
} else { *i = std::move(stories);
_all.push_back(std::move(stories)); } else {
_all.push_back(std::move(stories));
}
} }
}); });
return resultId; return resultId;

View File

@ -46,9 +46,12 @@ struct FullStoryId {
UserData *user = nullptr; UserData *user = nullptr;
StoryId id = 0; StoryId id = 0;
explicit operator bool() const { [[nodiscard]] bool valid() const {
return user != nullptr && id != 0; return user != nullptr && id != 0;
} }
explicit operator bool() const {
return valid();
}
friend inline auto operator<=>(FullStoryId, FullStoryId) = default; friend inline auto operator<=>(FullStoryId, FullStoryId) = default;
friend inline bool operator==(FullStoryId, FullStoryId) = default; friend inline bool operator==(FullStoryId, FullStoryId) = default;
}; };

View File

@ -12,8 +12,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_stories.h" #include "data/data_stories.h"
#include "media/stories/media_stories_delegate.h" #include "media/stories/media_stories_delegate.h"
#include "media/stories/media_stories_header.h" #include "media/stories/media_stories_header.h"
#include "media/stories/media_stories_sibling.h"
#include "media/stories/media_stories_slider.h" #include "media/stories/media_stories_slider.h"
#include "media/stories/media_stories_reply.h" #include "media/stories/media_stories_reply.h"
#include "media/stories/media_stories_view.h"
#include "media/audio/media_audio.h" #include "media/audio/media_audio.h"
#include "ui/rp_widget.h" #include "ui/rp_widget.h"
#include "styles/style_media_view.h" #include "styles/style_media_view.h"
@ -25,6 +27,7 @@ namespace {
constexpr auto kPhotoProgressInterval = crl::time(100); constexpr auto kPhotoProgressInterval = crl::time(100);
constexpr auto kPhotoDuration = 5 * crl::time(1000); constexpr auto kPhotoDuration = 5 * crl::time(1000);
constexpr auto kSiblingMultiplier = 0.448;
} // namespace } // namespace
@ -115,12 +118,15 @@ void Controller::initLayout() {
const auto sliderHeight = st::storiesSliderMargin.top() const auto sliderHeight = st::storiesSliderMargin.top()
+ st::storiesSliderWidth + st::storiesSliderWidth
+ st::storiesSliderMargin.bottom(); + st::storiesSliderMargin.bottom();
const auto outsideHeaderHeight = headerHeight + sliderHeight; const auto outsideHeaderHeight = headerHeight
+ sliderHeight
+ st::storiesSliderOutsideSkip;
const auto fieldMinHeight = st::storiesFieldMargin.top() const auto fieldMinHeight = st::storiesFieldMargin.top()
+ st::storiesAttach.height + st::storiesAttach.height
+ st::storiesFieldMargin.bottom(); + st::storiesFieldMargin.bottom();
const auto minHeightForOutsideHeader = st::storiesMaxSize.height() const auto minHeightForOutsideHeader = st::storiesFieldMargin.bottom()
+ outsideHeaderHeight + outsideHeaderHeight
+ st::storiesMaxSize.height()
+ fieldMinHeight; + fieldMinHeight;
_layout = _wrap->sizeValue( _layout = _wrap->sizeValue(
@ -134,9 +140,10 @@ void Controller::initLayout() {
? HeaderLayout::Outside ? HeaderLayout::Outside
: HeaderLayout::Normal; : HeaderLayout::Normal;
const auto topSkip = (layout.headerLayout == HeaderLayout::Outside) const auto topSkip = st::storiesFieldMargin.bottom()
? outsideHeaderHeight + (layout.headerLayout == HeaderLayout::Outside
: st::storiesFieldMargin.bottom(); ? outsideHeaderHeight
: 0);
const auto bottomSkip = fieldMinHeight; const auto bottomSkip = fieldMinHeight;
const auto maxWidth = size.width() - 2 * st::storiesSideSkip; const auto maxWidth = size.width() - 2 * st::storiesSideSkip;
const auto availableHeight = size.height() - topSkip - bottomSkip; const auto availableHeight = size.height() - topSkip - bottomSkip;
@ -187,6 +194,16 @@ void Controller::initLayout() {
layout.controlsWidth, layout.controlsWidth,
layout.controlsBottomPosition.y()); layout.controlsBottomPosition.y());
const auto siblingSize = layout.content.size() * kSiblingMultiplier;
const auto siblingTop = layout.content.y()
+ (layout.content.height() - siblingSize.height()) / 2;
layout.siblingLeft = QRect(
{ -siblingSize.width() / 3, siblingTop },
siblingSize);
layout.siblingRight = QRect(
{ size.width() - (2 * siblingSize.width() / 3), siblingTop },
siblingSize);
return layout; return layout;
}); });
} }
@ -214,11 +231,19 @@ auto Controller::stickerOrEmojiChosen() const
return _delegate->storiesStickerOrEmojiChosen(); return _delegate->storiesStickerOrEmojiChosen();
} }
void Controller::show(const Data::StoriesList &list, int index) { void Controller::show(
Expects(index < list.items.size()); const std::vector<Data::StoriesList> &lists,
int index,
int subindex) {
Expects(index >= 0 && index < lists.size());
Expects(subindex >= 0 && subindex < lists[index].items.size());
const auto &item = list.items[index]; showSiblings(lists, index);
const auto &list = lists[index];
const auto &item = list.items[subindex];
const auto guard = gsl::finally([&] { const auto guard = gsl::finally([&] {
_started = false;
if (v::is<not_null<PhotoData*>>(item.media.data)) { if (v::is<not_null<PhotoData*>>(item.media.data)) {
_photoPlayback = std::make_unique<PhotoPlayback>(this); _photoPlayback = std::make_unique<PhotoPlayback>(this);
} else { } else {
@ -228,7 +253,7 @@ void Controller::show(const Data::StoriesList &list, int index) {
if (_list != list) { if (_list != list) {
_list = list; _list = list;
} }
_index = index; _index = subindex;
const auto id = Data::FullStoryId{ const auto id = Data::FullStoryId{
.user = list.user, .user = list.user,
@ -240,11 +265,34 @@ void Controller::show(const Data::StoriesList &list, int index) {
_shown = id; _shown = id;
_header->show({ .user = list.user, .date = item.date }); _header->show({ .user = list.user, .date = item.date });
_slider->show({ .index = index, .total = int(list.items.size()) }); _slider->show({ .index = _index, .total = list.total });
_replyArea->show({ .user = list.user }); _replyArea->show({ .user = list.user });
} }
void Controller::showSiblings(
const std::vector<Data::StoriesList> &lists,
int index) {
showSibling(_siblingLeft, (index > 0) ? &lists[index - 1] : nullptr);
showSibling(
_siblingRight,
(index + 1 < lists.size()) ? &lists[index + 1] : nullptr);
}
void Controller::showSibling(
std::unique_ptr<Sibling> &sibling,
const Data::StoriesList *list) {
if (!list || list->items.empty()) {
sibling = nullptr;
} else if (!sibling || !sibling->shows(*list)) {
sibling = std::make_unique<Sibling>(this, *list);
}
}
void Controller::ready() { void Controller::ready() {
if (_started) {
return;
}
_started = true;
if (_photoPlayback) { if (_photoPlayback) {
_photoPlayback->togglePaused(false); _photoPlayback->togglePaused(false);
} }
@ -262,25 +310,28 @@ void Controller::updatePlayback(const Player::TrackState &state) {
_slider->updatePlayback(state); _slider->updatePlayback(state);
updatePowerSaveBlocker(state); updatePowerSaveBlocker(state);
if (Player::IsStoppedAtEnd(state.state)) { if (Player::IsStoppedAtEnd(state.state)) {
if (!jumpFor(1)) { if (!subjumpFor(1)) {
_delegate->storiesJumpTo({}); _delegate->storiesJumpTo({});
} }
} }
} }
bool Controller::jumpAvailable(int delta) const { bool Controller::subjumpAvailable(int delta) const {
if (delta == -1) {
// Always allow to jump back for one.
// In case of the first story just jump to the beginning.
return _list && !_list->items.empty();
}
const auto index = _index + delta; const auto index = _index + delta;
if (index < 0) {
return _siblingLeft && _siblingLeft->shownId().valid();
} else if (index >= _list->total) {
return _siblingRight && _siblingRight->shownId().valid();
}
return index >= 0 && index < _list->total; return index >= 0 && index < _list->total;
} }
bool Controller::jumpFor(int delta) { bool Controller::subjumpFor(int delta) {
if (!_index && delta == -1) { const auto index = _index + delta;
if (!_list || _list->items.empty()) { if (index < 0) {
if (_siblingLeft->shownId().valid()) {
return jumpFor(-1);
} else if (!_list || _list->items.empty()) {
return false; return false;
} }
_delegate->storiesJumpTo({ _delegate->storiesJumpTo({
@ -288,10 +339,8 @@ bool Controller::jumpFor(int delta) {
.id = _list->items.front().id .id = _list->items.front().id
}); });
return true; return true;
} } else if (index >= _list->total) {
const auto index = _index + delta; return _siblingRight->shownId().valid() && jumpFor(1);
if (index < 0 || index >= _list->total) {
return false;
} else if (index < _list->items.size()) { } else if (index < _list->items.size()) {
// #TODO stories load more // #TODO stories load more
_delegate->storiesJumpTo({ _delegate->storiesJumpTo({
@ -302,6 +351,22 @@ bool Controller::jumpFor(int delta) {
return true; return true;
} }
bool Controller::jumpFor(int delta) {
if (delta == -1) {
if (const auto left = _siblingLeft.get()) {
_delegate->storiesJumpTo(left->shownId());
return true;
}
} else if (delta == 1) {
if (const auto right = _siblingRight.get()) {
_delegate->storiesJumpTo(right->shownId());
return true;
}
}
return false;
}
bool Controller::paused() const { bool Controller::paused() const {
return _photoPlayback return _photoPlayback
? _photoPlayback->paused() ? _photoPlayback->paused()
@ -316,6 +381,30 @@ void Controller::togglePaused(bool paused) {
} }
} }
void Controller::repaintSibling(not_null<Sibling*> sibling) {
if (sibling == _siblingLeft.get() || sibling == _siblingRight.get()) {
_delegate->storiesRepaint();
}
}
SiblingView Controller::siblingLeft() const {
if (const auto value = _siblingLeft.get()) {
return { value->image(), _layout.current()->siblingLeft };
}
return {};
}
SiblingView Controller::siblingRight() const {
if (const auto value = _siblingRight.get()) {
return { value->image(), _layout.current()->siblingRight };
}
return {};
}
rpl::lifetime &Controller::lifetime() {
return _lifetime;
}
void Controller::updatePowerSaveBlocker(const Player::TrackState &state) { void Controller::updatePowerSaveBlocker(const Player::TrackState &state) {
const auto block = !Player::IsPausedOrPausing(state.state) const auto block = !Player::IsPausedOrPausing(state.state)
&& !Player::IsStoppedOrStopping(state.state); && !Player::IsStoppedOrStopping(state.state);

View File

@ -35,7 +35,9 @@ namespace Media::Stories {
class Header; class Header;
class Slider; class Slider;
class ReplyArea; class ReplyArea;
class Sibling;
class Delegate; class Delegate;
struct SiblingView;
enum class HeaderLayout { enum class HeaderLayout {
Normal, Normal,
@ -50,6 +52,8 @@ struct Layout {
QPoint controlsBottomPosition; QPoint controlsBottomPosition;
QRect autocompleteRect; QRect autocompleteRect;
HeaderLayout headerLayout = HeaderLayout::Normal; HeaderLayout headerLayout = HeaderLayout::Normal;
QRect siblingLeft;
QRect siblingRight;
friend inline auto operator<=>(Layout, Layout) = default; friend inline auto operator<=>(Layout, Layout) = default;
friend inline bool operator==(Layout, Layout) = default; friend inline bool operator==(Layout, Layout) = default;
@ -68,16 +72,26 @@ public:
[[nodiscard]] auto stickerOrEmojiChosen() const [[nodiscard]] auto stickerOrEmojiChosen() const
-> rpl::producer<ChatHelpers::FileChosen>; -> rpl::producer<ChatHelpers::FileChosen>;
void show(const Data::StoriesList &list, int index); void show(
const std::vector<Data::StoriesList> &lists,
int index,
int subindex);
void ready(); void ready();
void updateVideoPlayback(const Player::TrackState &state); void updateVideoPlayback(const Player::TrackState &state);
[[nodiscard]] bool jumpAvailable(int delta) const; [[nodiscard]] bool subjumpAvailable(int delta) const;
[[nodiscard]] bool subjumpFor(int delta);
[[nodiscard]] bool jumpFor(int delta); [[nodiscard]] bool jumpFor(int delta);
[[nodiscard]] bool paused() const; [[nodiscard]] bool paused() const;
void togglePaused(bool paused); void togglePaused(bool paused);
void repaintSibling(not_null<Sibling*> sibling);
[[nodiscard]] SiblingView siblingLeft() const;
[[nodiscard]] SiblingView siblingRight() const;
[[nodiscard]] rpl::lifetime &lifetime();
private: private:
class PhotoPlayback; class PhotoPlayback;
@ -86,6 +100,13 @@ private:
void updatePlayback(const Player::TrackState &state); void updatePlayback(const Player::TrackState &state);
void updatePowerSaveBlocker(const Player::TrackState &state); void updatePowerSaveBlocker(const Player::TrackState &state);
void showSiblings(
const std::vector<Data::StoriesList> &lists,
int index);
void showSibling(
std::unique_ptr<Sibling> &sibling,
const Data::StoriesList *list);
const not_null<Delegate*> _delegate; const not_null<Delegate*> _delegate;
rpl::variable<std::optional<Layout>> _layout; rpl::variable<std::optional<Layout>> _layout;
@ -94,11 +115,15 @@ private:
const std::unique_ptr<Header> _header; const std::unique_ptr<Header> _header;
const std::unique_ptr<Slider> _slider; const std::unique_ptr<Slider> _slider;
const std::unique_ptr<ReplyArea> _replyArea; const std::unique_ptr<ReplyArea> _replyArea;
std::unique_ptr<PhotoPlayback> _photoPlayback;
Data::FullStoryId _shown; Data::FullStoryId _shown;
std::optional<Data::StoriesList> _list; std::optional<Data::StoriesList> _list;
int _index = 0; int _index = 0;
std::unique_ptr<PhotoPlayback> _photoPlayback; bool _started = false;
std::unique_ptr<Sibling> _siblingLeft;
std::unique_ptr<Sibling> _siblingRight;
std::unique_ptr<base::PowerSaveBlocker> _powerSaveBlocker; std::unique_ptr<base::PowerSaveBlocker> _powerSaveBlocker;

View File

@ -37,6 +37,7 @@ public:
virtual void storiesJumpTo(Data::FullStoryId id) = 0; virtual void storiesJumpTo(Data::FullStoryId id) = 0;
[[nodiscard]] virtual bool storiesPaused() = 0; [[nodiscard]] virtual bool storiesPaused() = 0;
virtual void storiesTogglePaused(bool paused) = 0; virtual void storiesTogglePaused(bool paused) = 0;
virtual void storiesRepaint() = 0;
}; };
} // namespace Media::Stories } // namespace Media::Stories

View File

@ -0,0 +1,262 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "media/stories/media_stories_sibling.h"
#include "base/weak_ptr.h"
#include "data/data_document.h"
#include "data/data_document_media.h"
#include "data/data_file_origin.h"
#include "data/data_photo.h"
#include "data/data_photo_media.h"
#include "data/data_session.h"
#include "main/main_session.h"
#include "media/stories/media_stories_controller.h"
#include "media/streaming/media_streaming_instance.h"
#include "media/streaming/media_streaming_player.h"
namespace Media::Stories {
namespace {
constexpr auto kGoodFadeDuration = crl::time(200);
} // namespace
class Sibling::Loader {
public:
virtual ~Loader() = default;
virtual QImage blurred() = 0;
virtual QImage good() = 0;
};
class Sibling::LoaderPhoto final : public Sibling::Loader {
public:
LoaderPhoto(
not_null<PhotoData*> photo,
Data::FileOrigin origin,
Fn<void()> update);
QImage blurred() override;
QImage good() override;
private:
const not_null<PhotoData*> _photo;
const Fn<void()> _update;
std::shared_ptr<Data::PhotoMedia> _media;
rpl::lifetime _waitingLoading;
};
class Sibling::LoaderVideo final
: public Sibling::Loader
, public base::has_weak_ptr {
public:
LoaderVideo(
not_null<DocumentData*> video,
Data::FileOrigin origin,
Fn<void()> update);
QImage blurred() override;
QImage good() override;
private:
void waitForGoodThumbnail();
bool updateAfterGoodCheck();
void streamedFailed();
const not_null<DocumentData*> _video;
const Data::FileOrigin _origin;
const Fn<void()> _update;
std::shared_ptr<Data::DocumentMedia> _media;
std::unique_ptr<Streaming::Instance> _streamed;
rpl::lifetime _waitingGoodGeneration;
bool _checkingGoodInCache = false;
bool _failed = false;
};
Sibling::LoaderPhoto::LoaderPhoto(
not_null<PhotoData*> photo,
Data::FileOrigin origin,
Fn<void()> update)
: _photo(photo)
, _update(std::move(update))
, _media(_photo->createMediaView()) {
_photo->load(origin, LoadFromCloudOrLocal, true);
}
QImage Sibling::LoaderPhoto::blurred() {
if (const auto image = _media->thumbnailInline()) {
return image->original();
}
const auto ratio = style::DevicePixelRatio();
auto result = QImage(ratio, ratio, QImage::Format_ARGB32_Premultiplied);
result.fill(Qt::black);
result.setDevicePixelRatio(ratio);
return result;
}
QImage Sibling::LoaderPhoto::good() {
if (const auto image = _media->image(Data::PhotoSize::Large)) {
return image->original();
} else if (!_waitingLoading) {
_photo->session().downloaderTaskFinished(
) | rpl::start_with_next([=] {
if (_media->loaded()) {
_update();
}
}, _waitingLoading);
}
return QImage();
}
Sibling::LoaderVideo::LoaderVideo(
not_null<DocumentData*> video,
Data::FileOrigin origin,
Fn<void()> update)
: _video(video)
, _origin(origin)
, _update(std::move( update))
, _media(_video->createMediaView()) {
_media->goodThumbnailWanted();
}
QImage Sibling::LoaderVideo::blurred() {
if (const auto image = _media->thumbnailInline()) {
return image->original();
}
const auto ratio = style::DevicePixelRatio();
auto result = QImage(ratio, ratio, QImage::Format_ARGB32_Premultiplied);
result.fill(Qt::black);
result.setDevicePixelRatio(ratio);
return result;
}
QImage Sibling::LoaderVideo::good() {
if (const auto image = _media->goodThumbnail()) {
return image->original();
} else if (!_video->goodThumbnailChecked()) {
if (!_checkingGoodInCache) {
waitForGoodThumbnail();
}
} else if (_failed) {
return QImage();
} else if (!_streamed) {
_streamed = std::make_unique<Streaming::Instance>(
_video,
_origin,
[] {}); // waitingCallback
_streamed->lockPlayer();
_streamed->player().updates(
) | rpl::start_with_next_error([=](Streaming::Update &&update) {
v::match(update.data, [&](Streaming::Information &update) {
_update();
}, [](const auto &update) {
});
}, [=](Streaming::Error &&error) {
streamedFailed();
}, _streamed->lifetime());
if (_streamed->ready()) {
_update();
} else if (!_streamed->valid()) {
streamedFailed();
}
} else if (_streamed->ready()) {
return _streamed->info().video.cover;
}
return QImage();
}
void Sibling::LoaderVideo::streamedFailed() {
_failed = true;
_streamed = nullptr;
_update();
}
void Sibling::LoaderVideo::waitForGoodThumbnail() {
_checkingGoodInCache = true;
const auto weak = make_weak(this);
_video->owner().cache().get({}, [=](const auto &) {
crl::on_main([=] {
if (const auto strong = weak.get()) {
if (!strong->updateAfterGoodCheck()) {
strong->_video->session().downloaderTaskFinished(
) | rpl::start_with_next([=] {
strong->updateAfterGoodCheck();
}, strong->_waitingGoodGeneration);
}
}
});
});
}
bool Sibling::LoaderVideo::updateAfterGoodCheck() {
if (!_video->goodThumbnailChecked()) {
return false;
}
_checkingGoodInCache = false;
_waitingGoodGeneration.destroy();
_update();
return true;
}
Sibling::Sibling(
not_null<Controller*> controller,
const Data::StoriesList &list)
: _controller(controller)
, _id{ list.user, list.items.front().id } {
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();
_goodShown.stop();
}
Sibling::~Sibling() = default;
Data::FullStoryId Sibling::shownId() const {
return _id;
}
bool Sibling::shows(const Data::StoriesList &list) const {
Expects(!list.items.empty());
return _id == Data::FullStoryId{ list.user, list.items.front().id };
}
QImage Sibling::image() const {
return _good.isNull() ? _blurred : _good;
}
void Sibling::check() {
Expects(_loader != nullptr);
auto good = _loader->good();
if (good.isNull()) {
return;
}
_loader = nullptr;
_good = std::move(good);
_goodShown.start([=] {
_controller->repaintSibling(this);
}, 0., 1., kGoodFadeDuration, anim::linear);
}
} // namespace Media::Stories

View File

@ -0,0 +1,48 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "data/data_stories.h"
#include "ui/effects/animations.h"
namespace Media::Stories {
class Controller;
class Sibling final {
public:
Sibling(
not_null<Controller*> controller,
const Data::StoriesList &list);
~Sibling();
[[nodiscard]] Data::FullStoryId shownId() const;
[[nodiscard]] bool shows(const Data::StoriesList &list) const;
[[nodiscard]] QImage image() const;
private:
class Loader;
class LoaderPhoto;
class LoaderVideo;
void check();
const not_null<Controller*> _controller;
Data::FullStoryId _id;
QImage _blurred;
QImage _good;
Ui::Animations::Simple _goodShown;
std::unique_ptr<Loader> _loader;
};
} // namespace Media::Stories

View File

@ -136,7 +136,9 @@ void Slider::paint(QRectF clip) {
radius, radius,
radius); radius);
} else { } else {
p.setOpacity(kOpacityInactive); p.setOpacity((i < _data.index)
? kOpacityActive
: kOpacityInactive);
p.drawRoundedRect(_rects[i], radius, radius); p.drawRoundedRect(_rects[i], radius, radius);
} }
} }

View File

@ -21,8 +21,11 @@ View::View(not_null<Delegate*> delegate)
View::~View() = default; View::~View() = default;
void View::show(const Data::StoriesList &list, int index) { void View::show(
_controller->show(list, index); const std::vector<Data::StoriesList> &lists,
int index,
int subindex) {
_controller->show(lists, index, subindex);
} }
void View::ready() { void View::ready() {
@ -33,12 +36,23 @@ QRect View::contentGeometry() const {
return _controller->layout().content; return _controller->layout().content;
} }
rpl::producer<QRect> View::contentGeometryValue() const {
return _controller->layoutValue(
) | rpl::map([=](const Layout &layout) {
return layout.content;
}) | rpl::distinct_until_changed();
}
void View::updatePlayback(const Player::TrackState &state) { void View::updatePlayback(const Player::TrackState &state) {
_controller->updateVideoPlayback(state); _controller->updateVideoPlayback(state);
} }
bool View::jumpAvailable(int delta) const { bool View::subjumpAvailable(int delta) const {
return _controller->jumpAvailable(delta); return _controller->subjumpAvailable(delta);
}
bool View::subjumpFor(int delta) const {
return _controller->subjumpFor(delta);
} }
bool View::jumpFor(int delta) const { bool View::jumpFor(int delta) const {
@ -53,4 +67,16 @@ void View::togglePaused(bool paused) {
_controller->togglePaused(paused); _controller->togglePaused(paused);
} }
SiblingView View::siblingLeft() const {
return _controller->siblingLeft();
}
SiblingView View::siblingRight() const {
return _controller->siblingRight();
}
rpl::lifetime &View::lifetime() {
return _controller->lifetime();
}
} // namespace Media::Stories } // namespace Media::Stories

View File

@ -20,24 +20,45 @@ namespace Media::Stories {
class Delegate; class Delegate;
class Controller; class Controller;
struct SiblingView {
QImage image;
QRect geometry;
[[nodiscard]] bool valid() const {
return !image.isNull();
}
explicit operator bool() const {
return valid();
}
};
class View final { class View final {
public: public:
explicit View(not_null<Delegate*> delegate); explicit View(not_null<Delegate*> delegate);
~View(); ~View();
void show(const Data::StoriesList &list, int index); void show(
const std::vector<Data::StoriesList> &lists,
int index,
int subindex);
void ready(); void ready();
[[nodiscard]] QRect contentGeometry() const; [[nodiscard]] QRect contentGeometry() const;
[[nodiscard]] rpl::producer<QRect> contentGeometryValue() const;
[[nodiscard]] SiblingView siblingLeft() const;
[[nodiscard]] SiblingView siblingRight() const;
void updatePlayback(const Player::TrackState &state); void updatePlayback(const Player::TrackState &state);
[[nodiscard]] bool jumpAvailable(int delta) const; [[nodiscard]] bool subjumpAvailable(int delta) const;
[[nodiscard]] bool subjumpFor(int delta) const;
[[nodiscard]] bool jumpFor(int delta) const; [[nodiscard]] bool jumpFor(int delta) const;
[[nodiscard]] bool paused() const; [[nodiscard]] bool paused() const;
void togglePaused(bool paused); void togglePaused(bool paused);
[[nodiscard]] rpl::lifetime &lifetime();
private: private:
const std::unique_ptr<Controller> _controller; const std::unique_ptr<Controller> _controller;

View File

@ -406,10 +406,14 @@ pipVolumeIcon2Over: icon {{ "player/player_volume_on", mediaviewPipControlsFgOve
speedSliderDividerSize: size(2px, 8px); speedSliderDividerSize: size(2px, 8px);
storiesMaxSize: size(405px, 720px); storiesMaxSize: size(405px, 720px);
storiesControlSize: 64px;
storiesLeft: icon {{ "mediaview/stories_next-flip_horizontal", mediaviewControlFg }};
storiesRight: icon {{ "mediaview/stories_next", mediaviewControlFg }};
storiesSliderWidth: 2px; storiesSliderWidth: 2px;
storiesSliderMargin: margins(8px, 7px, 8px, 11px); storiesSliderMargin: margins(8px, 7px, 8px, 6px);
storiesSliderSkip: 4px; storiesSliderSkip: 4px;
storiesHeaderMargin: margins(12px, 3px, 12px, 8px); storiesSliderOutsideSkip: 4px;
storiesHeaderMargin: margins(12px, 4px, 12px, 8px);
storiesHeaderPhoto: UserpicButton(defaultUserpicButton) { storiesHeaderPhoto: UserpicButton(defaultUserpicButton) {
size: size(28px, 28px); size: size(28px, 28px);
photoSize: 28px; photoSize: 28px;
@ -422,7 +426,7 @@ storiesHeaderNamePosition: point(50px, 0px);
storiesHeaderDate: FlatLabel(defaultFlatLabel) { storiesHeaderDate: FlatLabel(defaultFlatLabel) {
textFg: mediaviewControlFg; textFg: mediaviewControlFg;
} }
storiesHeaderDatePosition: point(50px, 16px); storiesHeaderDatePosition: point(50px, 17px);
storiesControlsMinWidth: 200px; storiesControlsMinWidth: 200px;
storiesFieldMargin: margins(0px, 14px, 0px, 16px); storiesFieldMargin: margins(0px, 14px, 0px, 16px);
storiesAttach: IconButton(defaultIconButton) { storiesAttach: IconButton(defaultIconButton) {

View File

@ -112,6 +112,11 @@ OverlayWidget::RendererGL::RendererGL(not_null<OverlayWidget*> owner)
_captionImage.invalidate(); _captionImage.invalidate();
invalidateControls(); invalidateControls();
}, _lifetime); }, _lifetime);
_owner->_storiesChanged.events(
) | rpl::start_with_next([=] {
invalidateControls();
}, _lifetime);
} }
void OverlayWidget::RendererGL::init( void OverlayWidget::RendererGL::init(
@ -568,7 +573,8 @@ void OverlayWidget::RendererGL::paintControl(
QRect inner, QRect inner,
float64 innerOpacity, float64 innerOpacity,
const style::icon &icon) { const style::icon &icon) {
const auto meta = ControlMeta(control); const auto stories = (_owner->_stories != nullptr);
const auto meta = ControlMeta(control, stories);
Assert(meta.icon == &icon); Assert(meta.icon == &icon);
const auto overAlpha = overOpacity * kOverBackgroundOpacity; const auto overAlpha = overOpacity * kOverBackgroundOpacity;
@ -626,11 +632,17 @@ void OverlayWidget::RendererGL::paintControl(
FillTexturedRectangle(*_f, &*_controlsProgram, fgOffset); FillTexturedRectangle(*_f, &*_controlsProgram, fgOffset);
} }
auto OverlayWidget::RendererGL::ControlMeta(OverState control) auto OverlayWidget::RendererGL::ControlMeta(OverState control, bool stories)
-> Control { -> Control {
switch (control) { switch (control) {
case OverLeftNav: return { 0, &st::mediaviewLeft }; case OverLeftNav: return {
case OverRightNav: return { 1, &st::mediaviewRight }; 0,
stories ? &st::storiesLeft : &st::mediaviewLeft
};
case OverRightNav: return {
1,
stories ? &st::storiesRight : &st::mediaviewRight
};
case OverSave: return { 2, &st::mediaviewSave }; case OverSave: return { 2, &st::mediaviewSave };
case OverRotate: return { 3, &st::mediaviewRotate }; case OverRotate: return { 3, &st::mediaviewRotate };
case OverMore: return { 4, &st::mediaviewMore }; case OverMore: return { 4, &st::mediaviewMore };
@ -642,12 +654,13 @@ void OverlayWidget::RendererGL::validateControls() {
if (!_controlsImage.image().isNull()) { if (!_controlsImage.image().isNull()) {
return; return;
} }
const auto stories = (_owner->_stories != nullptr);
const auto metas = { const auto metas = {
ControlMeta(OverLeftNav), ControlMeta(OverLeftNav, stories),
ControlMeta(OverRightNav), ControlMeta(OverRightNav, stories),
ControlMeta(OverSave), ControlMeta(OverSave, stories),
ControlMeta(OverRotate), ControlMeta(OverRotate, stories),
ControlMeta(OverMore), ControlMeta(OverMore, stories),
}; };
auto maxWidth = 0; auto maxWidth = 0;
auto fullHeight = 0; auto fullHeight = 0;

View File

@ -134,7 +134,9 @@ private:
Ui::GL::Image _controlsImage; Ui::GL::Image _controlsImage;
static constexpr auto kControlsCount = 5; static constexpr auto kControlsCount = 5;
[[nodiscard]] static Control ControlMeta(OverState control); [[nodiscard]] static Control ControlMeta(
OverState control,
bool stories);
// Last one is for the over circle image. // Last one is for the over circle image.
std::array<QRect, kControlsCount + 1> _controlsTextures; std::array<QRect, kControlsCount + 1> _controlsTextures;

View File

@ -813,16 +813,7 @@ void OverlayWidget::updateGeometryToScreen(bool inMove) {
} }
void OverlayWidget::updateControlsGeometry() { void OverlayWidget::updateControlsGeometry() {
const auto overRect = QRect( updateNavigationControlsGeometry();
QPoint(),
QSize(st::mediaviewIconOver, st::mediaviewIconOver));
const auto navSkip = st::mediaviewHeaderTop;
_leftNav = QRect(0, navSkip, st::mediaviewControlSize, height() - 2 * navSkip);
_leftNavOver = style::centerrect(_leftNav, overRect);
_leftNavIcon = style::centerrect(_leftNav, st::mediaviewLeft);
_rightNav = QRect(width() - st::mediaviewControlSize, navSkip, st::mediaviewControlSize, height() - 2 * navSkip);
_rightNavOver = style::centerrect(_rightNav, overRect);
_rightNavIcon = style::centerrect(_rightNav, st::mediaviewRight);
_saveMsg.moveTo((width() - _saveMsg.width()) / 2, (height() - _saveMsg.height()) / 2); _saveMsg.moveTo((width() - _saveMsg.width()) / 2, (height() - _saveMsg.height()) / 2);
_photoRadialRect = QRect(QPoint((width() - st::radialSize.width()) / 2, (height() - st::radialSize.height()) / 2), st::radialSize); _photoRadialRect = QRect(QPoint((width() - st::radialSize.width()) / 2, (height() - st::radialSize.height()) / 2), st::radialSize);
@ -843,6 +834,32 @@ void OverlayWidget::updateControlsGeometry() {
update(); update();
} }
void OverlayWidget::updateNavigationControlsGeometry() {
const auto overRect = QRect(
QPoint(),
QSize(st::mediaviewIconOver, st::mediaviewIconOver));
const auto navSize = _stories
? st::storiesControlSize
: st::mediaviewControlSize;
const auto navSkip = st::mediaviewHeaderTop;
const auto xLeft = _stories ? (_x - navSize) : 0;
const auto xRight = _stories ? (_x + _w) : (width() - navSize);
_leftNav = QRect(xLeft, navSkip, navSize, height() - 2 * navSkip);
_leftNavOver = _stories
? QRect()
: style::centerrect(_leftNav, overRect);
_leftNavIcon = style::centerrect(
_leftNav,
_stories ? st::storiesLeft : st::mediaviewLeft);
_rightNav = QRect(xRight, navSkip, navSize, height() - 2 * navSkip);
_rightNavOver = _stories
? QRect()
: style::centerrect(_rightNav, overRect);
_rightNavIcon = style::centerrect(
_rightNav,
_stories ? st::storiesRight : st::mediaviewRight);
}
bool OverlayWidget::topShadowOnTheRight() const { bool OverlayWidget::topShadowOnTheRight() const {
return _topShadowRight.current(); return _topShadowRight.current();
} }
@ -1009,8 +1026,8 @@ void OverlayWidget::updateDocSize() {
void OverlayWidget::refreshNavVisibility() { void OverlayWidget::refreshNavVisibility() {
if (_stories) { if (_stories) {
_leftNavVisible = _stories->jumpAvailable(-1); _leftNavVisible = _stories->subjumpAvailable(-1);
_rightNavVisible = _stories->jumpAvailable(1); _rightNavVisible = _stories->subjumpAvailable(1);
} else if (_sharedMediaData) { } else if (_sharedMediaData) {
_leftNavVisible = _index && (*_index > 0); _leftNavVisible = _index && (*_index > 0);
_rightNavVisible = _index && (*_index + 1 < _sharedMediaData->size()); _rightNavVisible = _index && (*_index + 1 < _sharedMediaData->size());
@ -1432,6 +1449,12 @@ bool OverlayWidget::updateControlsAnimation(crl::time now) {
+ (_over == OverSave ? _saveNavOver : _saveNavIcon) + (_over == OverSave ? _saveNavOver : _saveNavIcon)
+ (_over == OverRotate ? _rotateNavOver : _rotateNavIcon) + (_over == OverRotate ? _rotateNavOver : _rotateNavIcon)
+ (_over == OverMore ? _moreNavOver : _moreNavIcon) + (_over == OverMore ? _moreNavOver : _moreNavIcon)
+ ((_stories && _over == OverLeftStories)
? _stories->siblingLeft().geometry
: QRect())
+ ((_stories && _over == OverRightStories)
? _stories->siblingRight().geometry
: QRect())
+ _headerNav + _headerNav
+ _nameNav + _nameNav
+ _dateNav + _dateNav
@ -1547,6 +1570,7 @@ void OverlayWidget::resizeContentByScreenSize() {
_y = content.y(); _y = content.y();
_w = content.width(); _w = content.width();
_h = content.height(); _h = content.height();
updateNavigationControlsGeometry();
return; return;
} }
recountSkipTop(); recountSkipTop();
@ -3861,14 +3885,16 @@ std::shared_ptr<ChatHelpers::Show> OverlayWidget::storiesShow() {
return _widget->_body; return _widget->_body;
} }
bool valid() const override { bool valid() const override {
return _widget->_storiesUser != nullptr; return _widget->_storiesSession != nullptr;
} }
operator bool() const override { operator bool() const override {
return valid(); return valid();
} }
Main::Session &session() const override { Main::Session &session() const override {
return _widget->_storiesUser->session(); Expects(_widget->_storiesSession != nullptr);
return *_widget->_storiesSession;
} }
bool paused(ChatHelpers::PauseReason reason) const override { bool paused(ChatHelpers::PauseReason reason) const override {
if (_widget->isHidden() if (_widget->isHidden()
@ -3976,6 +4002,10 @@ void OverlayWidget::storiesTogglePaused(bool paused) {
} }
} }
void OverlayWidget::storiesRepaint() {
update();
}
void OverlayWidget::playbackToggleFullScreen() { void OverlayWidget::playbackToggleFullScreen() {
Expects(_streamed != nullptr); Expects(_streamed != nullptr);
@ -4131,6 +4161,22 @@ void OverlayWidget::paint(not_null<Renderer*> renderer) {
fillTransparentBackground); fillTransparentBackground);
} }
paintRadialLoading(renderer); paintRadialLoading(renderer);
if (_stories) {
if (const auto left = _stories->siblingLeft()) {
renderer->paintTransformedStaticContent(
left.image,
{ .rect = left.geometry },
false, // semi-transparent
false); // fill transparent background
}
if (const auto right = _stories->siblingRight()) {
renderer->paintTransformedStaticContent(
right.image,
{ .rect = right.geometry },
false, // semi-transparent
false); // fill transparent background
}
}
} else { } else {
if (_themePreviewShown) { if (_themePreviewShown) {
renderer->paintThemePreview(_themePreviewRect); renderer->paintThemePreview(_themePreviewRect);
@ -4420,14 +4466,14 @@ void OverlayWidget::paintControls(
_leftNavVisible, _leftNavVisible,
_leftNavOver, _leftNavOver,
_leftNavIcon, _leftNavIcon,
st::mediaviewLeft, _stories ? st::storiesLeft : st::mediaviewLeft,
true }, true },
{ {
OverRightNav, OverRightNav,
_rightNavVisible, _rightNavVisible,
_rightNavOver, _rightNavOver,
_rightNavIcon, _rightNavIcon,
st::mediaviewRight, _stories ? st::storiesRight : st::mediaviewRight,
true }, true },
{ {
OverSave, OverSave,
@ -4470,6 +4516,10 @@ void OverlayWidget::paintControls(
float64 OverlayWidget::controlOpacity( float64 OverlayWidget::controlOpacity(
float64 progress, float64 progress,
bool nonbright) const { bool nonbright) const {
if (nonbright && _stories) {
return progress * kStoriesNavOverOpacity
+ (1. - progress) * kStoriesNavOpacity;
}
const auto normal = _windowed const auto normal = _windowed
? kNormalIconOpacity ? kNormalIconOpacity
: kMaximizedIconOpacity; : kMaximizedIconOpacity;
@ -4813,15 +4863,13 @@ 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();
_stories = nullptr; setStoriesUser(nullptr);
_storiesUser = 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();
_stories = nullptr; setStoriesUser(nullptr);
_storiesUser = 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();
@ -4837,18 +4885,14 @@ void OverlayWidget::setContext(
i->items, i->items,
story->id, story->id,
&Data::StoryItem::id); &Data::StoryItem::id);
_storiesUser = story->user; setStoriesUser(story->user);
if (!_stories) { _stories->show(all, (i - begin(all)), j - begin(i->items));
_stories = std::make_unique<Stories::View>(
static_cast<Stories::Delegate*>(this));
}
_stories->show(*i, j - begin(i->items));
} else { } else {
_message = nullptr; _message = nullptr;
_topicRootId = MsgId(); _topicRootId = MsgId();
_history = nullptr; _history = nullptr;
_peer = nullptr; _peer = nullptr;
_stories = nullptr; setStoriesUser(nullptr);
} }
_migrated = nullptr; _migrated = nullptr;
if (_history) { if (_history) {
@ -4863,6 +4907,27 @@ void OverlayWidget::setContext(
_user = _peer ? _peer->asUser() : nullptr; _user = _peer ? _peer->asUser() : nullptr;
} }
void OverlayWidget::setStoriesUser(UserData *user) {
const auto session = user ? &user->session() : nullptr;
if (!session && !_storiesSession) {
Assert(!_stories);
} else if (!user) {
_stories = nullptr;
_storiesSession = nullptr;
_storiesChanged.fire({});
} else if (_storiesSession != session) {
_stories = nullptr;
_storiesSession = session;
const auto delegate = static_cast<Stories::Delegate*>(this);
_stories = std::make_unique<Stories::View>(delegate);
_stories->contentGeometryValue(
) | rpl::skip(1) | rpl::start_with_next([=] {
updateControlsGeometry();
}, _stories->lifetime());
_storiesChanged.fire({});
}
}
void OverlayWidget::setSession(not_null<Main::Session*> session) { void OverlayWidget::setSession(not_null<Main::Session*> session) {
if (_session == session) { if (_session == session) {
return; return;
@ -4908,7 +4973,7 @@ void OverlayWidget::setSession(not_null<Main::Session*> session) {
bool OverlayWidget::moveToNext(int delta) { bool OverlayWidget::moveToNext(int delta) {
if (_stories) { if (_stories) {
return _stories->jumpFor(delta); return _stories->subjumpFor(delta);
} else if (!_index) { } else if (!_index) {
return false; return false;
} }
@ -4991,9 +5056,14 @@ void OverlayWidget::handleMousePress(
if (button == Qt::LeftButton) { if (button == Qt::LeftButton) {
_down = OverNone; _down = OverNone;
if (!ClickHandler::getPressed()) { if (!ClickHandler::getPressed()) {
if (_over == OverLeftNav && moveToNext(-1)) { if ((_over == OverLeftNav && moveToNext(-1))
_lastAction = position; || (_over == OverRightNav && moveToNext(1))
} else if (_over == OverRightNav && moveToNext(1)) { || (_stories
&& _over == OverLeftStories
&& _stories->jumpFor(-1))
|| (_stories
&& _over == OverRightStories
&& _stories->jumpFor(1))) {
_lastAction = position; _lastAction = position;
} else if (_over == OverName } else if (_over == OverName
|| _over == OverDate || _over == OverDate
@ -5082,8 +5152,18 @@ void OverlayWidget::handleMouseMove(QPoint position) {
void OverlayWidget::updateOverRect(OverState state) { void OverlayWidget::updateOverRect(OverState state) {
switch (state) { switch (state) {
case OverLeftNav: update(_leftNavOver); break; case OverLeftNav:
case OverRightNav: update(_rightNavOver); break; update(_stories ? _leftNavIcon : _leftNavOver);
break;
case OverRightNav:
update(_stories ? _rightNavIcon : _rightNavOver);
break;
case OverLeftStories:
update(_stories ? _stories->siblingLeft().geometry : QRect());
break;
case OverRightStories:
update(_stories ? _stories->siblingRight().geometry : QRect());
break;
case OverName: update(_nameNav); break; case OverName: update(_nameNav); break;
case OverDate: update(_dateNav); break; case OverDate: update(_dateNav); break;
case OverSave: update(_saveNavOver); break; case OverSave: update(_saveNavOver); break;
@ -5170,6 +5250,10 @@ void OverlayWidget::updateOver(QPoint pos) {
updateOverState(OverVideo); updateOverState(OverVideo);
} else if (_leftNavVisible && _leftNav.contains(pos)) { } else if (_leftNavVisible && _leftNav.contains(pos)) {
updateOverState(OverLeftNav); updateOverState(OverLeftNav);
} else if (_stories && _stories->siblingLeft().geometry.contains(pos)) {
updateOverState(OverLeftStories);
} else if (_stories && _stories->siblingRight().geometry.contains(pos)) {
updateOverState(OverRightStories);
} else if (_rightNavVisible && _rightNav.contains(pos)) { } else if (_rightNavVisible && _rightNav.contains(pos)) {
updateOverState(OverRightNav); updateOverState(OverRightNav);
} else if (!_stories && _from && _nameNav.contains(pos)) { } else if (!_stories && _from && _nameNav.contains(pos)) {
@ -5527,6 +5611,7 @@ void OverlayWidget::clearBeforeHide() {
_collage = nullptr; _collage = nullptr;
_collageData = std::nullopt; _collageData = std::nullopt;
clearStreaming(); clearStreaming();
setStoriesUser(nullptr);
assignMediaPointer(nullptr); assignMediaPointer(nullptr);
_preloadPhotos.clear(); _preloadPhotos.clear();
_preloadDocuments.clear(); _preloadDocuments.clear();

View File

@ -137,6 +137,8 @@ private:
OverNone, OverNone,
OverLeftNav, OverLeftNav,
OverRightNav, OverRightNav,
OverLeftStories,
OverRightStories,
OverHeader, OverHeader,
OverName, OverName,
OverDate, OverDate,
@ -231,6 +233,7 @@ private:
void storiesJumpTo(Data::FullStoryId id) override; void storiesJumpTo(Data::FullStoryId id) override;
bool storiesPaused() override; bool storiesPaused() override;
void storiesTogglePaused(bool paused) override; void storiesTogglePaused(bool paused) override;
void storiesRepaint() override;
void hideControls(bool force = false); void hideControls(bool force = false);
void subscribeToScreenGeometry(); void subscribeToScreenGeometry();
@ -292,6 +295,7 @@ private:
ItemContext, ItemContext,
not_null<PeerData*>, not_null<PeerData*>,
StoriesContext> context); StoriesContext> context);
void setStoriesUser(UserData *user);
void refreshLang(); void refreshLang();
void showSaveMsgFile(); void showSaveMsgFile();
@ -332,6 +336,7 @@ private:
void updateDocSize(); void updateDocSize();
void updateControls(); void updateControls();
void updateControlsGeometry(); void updateControlsGeometry();
void updateNavigationControlsGeometry();
using MenuCallback = Fn<void( using MenuCallback = Fn<void(
const QString &, const QString &,
@ -572,7 +577,8 @@ private:
bool _showAsPip = false; bool _showAsPip = false;
std::unique_ptr<Stories::View> _stories; std::unique_ptr<Stories::View> _stories;
UserData *_storiesUser = nullptr; rpl::event_stream<> _storiesChanged;
Main::Session *_storiesSession = nullptr;
rpl::event_stream<ChatHelpers::FileChosen> _storiesStickerOrEmojiChosen; rpl::event_stream<ChatHelpers::FileChosen> _storiesStickerOrEmojiChosen;
std::unique_ptr<Ui::LayerManager> _layerBg; std::unique_ptr<Ui::LayerManager> _layerBg;

View File

@ -20,6 +20,8 @@ namespace Media::View {
inline constexpr auto kMaximizedIconOpacity = 0.6; inline constexpr auto kMaximizedIconOpacity = 0.6;
inline constexpr auto kNormalIconOpacity = 0.9; inline constexpr auto kNormalIconOpacity = 0.9;
inline constexpr auto kOverBackgroundOpacity = 0.2775; inline constexpr auto kOverBackgroundOpacity = 0.2775;
inline constexpr auto kStoriesNavOpacity = 0.3;
inline constexpr auto kStoriesNavOverOpacity = 0.7;
[[nodiscard]] QColor OverBackgroundColor(); [[nodiscard]] QColor OverBackgroundColor();
} // namespace Media::View } // namespace Media::View