mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-18 23:27:09 +02:00
Allow navigating to stories of sibling users.
This commit is contained in:
parent
7717de19ab
commit
ae94cd2d42
20 changed files with 699 additions and 93 deletions
|
@ -970,6 +970,8 @@ PRIVATE
|
|||
media/stories/media_stories_header.h
|
||||
media/stories/media_stories_reply.cpp
|
||||
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.h
|
||||
media/stories/media_stories_view.cpp
|
||||
|
|
BIN
Telegram/Resources/icons/mediaview/stories_next.png
Normal file
BIN
Telegram/Resources/icons/mediaview/stories_next.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 438 B |
BIN
Telegram/Resources/icons/mediaview/stories_next@2x.png
Normal file
BIN
Telegram/Resources/icons/mediaview/stories_next@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 806 B |
BIN
Telegram/Resources/icons/mediaview/stories_next@3x.png
Normal file
BIN
Telegram/Resources/icons/mediaview/stories_next@3x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
|
@ -173,7 +173,7 @@ StoryId Stories::generate(
|
|||
const auto itemId = item->id;
|
||||
const auto peer = item->history()->peer;
|
||||
const auto session = &peer->session();
|
||||
auto stories = StoriesList{ .user = item->from()->asUser() };
|
||||
auto full = std::vector<StoriesList>();
|
||||
const auto lifetime = session->storage().query(SharedMediaQuery(
|
||||
SharedMediaKey(peer->id, MsgId(0), listType, itemId),
|
||||
32,
|
||||
|
@ -182,21 +182,33 @@ StoryId Stories::generate(
|
|||
if (!result.messageIds.contains(itemId)) {
|
||||
result.messageIds.emplace(itemId);
|
||||
}
|
||||
stories.items.reserve(result.messageIds.size());
|
||||
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) }),
|
||||
: 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();
|
||||
|
@ -209,18 +221,21 @@ StoryId Stories::generate(
|
|||
.caption = item->originalText(),
|
||||
.date = item->date(),
|
||||
});
|
||||
++stories.total;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stories.total = std::max(
|
||||
result.count.value_or(1),
|
||||
int(result.messageIds.size()));
|
||||
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));
|
||||
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;
|
||||
|
|
|
@ -46,9 +46,12 @@ struct FullStoryId {
|
|||
UserData *user = nullptr;
|
||||
StoryId id = 0;
|
||||
|
||||
explicit operator bool() const {
|
||||
[[nodiscard]] bool valid() const {
|
||||
return user != nullptr && id != 0;
|
||||
}
|
||||
explicit operator bool() const {
|
||||
return valid();
|
||||
}
|
||||
friend inline auto operator<=>(FullStoryId, FullStoryId) = default;
|
||||
friend inline bool operator==(FullStoryId, FullStoryId) = default;
|
||||
};
|
||||
|
|
|
@ -12,8 +12,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_stories.h"
|
||||
#include "media/stories/media_stories_delegate.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_reply.h"
|
||||
#include "media/stories/media_stories_view.h"
|
||||
#include "media/audio/media_audio.h"
|
||||
#include "ui/rp_widget.h"
|
||||
#include "styles/style_media_view.h"
|
||||
|
@ -25,6 +27,7 @@ namespace {
|
|||
|
||||
constexpr auto kPhotoProgressInterval = crl::time(100);
|
||||
constexpr auto kPhotoDuration = 5 * crl::time(1000);
|
||||
constexpr auto kSiblingMultiplier = 0.448;
|
||||
|
||||
} // namespace
|
||||
|
||||
|
@ -115,12 +118,15 @@ void Controller::initLayout() {
|
|||
const auto sliderHeight = st::storiesSliderMargin.top()
|
||||
+ st::storiesSliderWidth
|
||||
+ st::storiesSliderMargin.bottom();
|
||||
const auto outsideHeaderHeight = headerHeight + sliderHeight;
|
||||
const auto outsideHeaderHeight = headerHeight
|
||||
+ sliderHeight
|
||||
+ st::storiesSliderOutsideSkip;
|
||||
const auto fieldMinHeight = st::storiesFieldMargin.top()
|
||||
+ st::storiesAttach.height
|
||||
+ st::storiesFieldMargin.bottom();
|
||||
const auto minHeightForOutsideHeader = st::storiesMaxSize.height()
|
||||
const auto minHeightForOutsideHeader = st::storiesFieldMargin.bottom()
|
||||
+ outsideHeaderHeight
|
||||
+ st::storiesMaxSize.height()
|
||||
+ fieldMinHeight;
|
||||
|
||||
_layout = _wrap->sizeValue(
|
||||
|
@ -134,9 +140,10 @@ void Controller::initLayout() {
|
|||
? HeaderLayout::Outside
|
||||
: HeaderLayout::Normal;
|
||||
|
||||
const auto topSkip = (layout.headerLayout == HeaderLayout::Outside)
|
||||
? outsideHeaderHeight
|
||||
: st::storiesFieldMargin.bottom();
|
||||
const auto topSkip = st::storiesFieldMargin.bottom()
|
||||
+ (layout.headerLayout == HeaderLayout::Outside
|
||||
? outsideHeaderHeight
|
||||
: 0);
|
||||
const auto bottomSkip = fieldMinHeight;
|
||||
const auto maxWidth = size.width() - 2 * st::storiesSideSkip;
|
||||
const auto availableHeight = size.height() - topSkip - bottomSkip;
|
||||
|
@ -187,6 +194,16 @@ void Controller::initLayout() {
|
|||
layout.controlsWidth,
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
@ -214,11 +231,19 @@ auto Controller::stickerOrEmojiChosen() const
|
|||
return _delegate->storiesStickerOrEmojiChosen();
|
||||
}
|
||||
|
||||
void Controller::show(const Data::StoriesList &list, int index) {
|
||||
Expects(index < list.items.size());
|
||||
void Controller::show(
|
||||
const std::vector<Data::StoriesList> &lists,
|
||||
int index,
|
||||
int subindex) {
|
||||
Expects(index >= 0 && index < lists.size());
|
||||
Expects(subindex >= 0 && subindex < lists[index].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([&] {
|
||||
_started = false;
|
||||
if (v::is<not_null<PhotoData*>>(item.media.data)) {
|
||||
_photoPlayback = std::make_unique<PhotoPlayback>(this);
|
||||
} else {
|
||||
|
@ -228,7 +253,7 @@ void Controller::show(const Data::StoriesList &list, int index) {
|
|||
if (_list != list) {
|
||||
_list = list;
|
||||
}
|
||||
_index = index;
|
||||
_index = subindex;
|
||||
|
||||
const auto id = Data::FullStoryId{
|
||||
.user = list.user,
|
||||
|
@ -240,11 +265,34 @@ void Controller::show(const Data::StoriesList &list, int index) {
|
|||
_shown = id;
|
||||
|
||||
_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 });
|
||||
}
|
||||
|
||||
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() {
|
||||
if (_started) {
|
||||
return;
|
||||
}
|
||||
_started = true;
|
||||
if (_photoPlayback) {
|
||||
_photoPlayback->togglePaused(false);
|
||||
}
|
||||
|
@ -262,25 +310,28 @@ void Controller::updatePlayback(const Player::TrackState &state) {
|
|||
_slider->updatePlayback(state);
|
||||
updatePowerSaveBlocker(state);
|
||||
if (Player::IsStoppedAtEnd(state.state)) {
|
||||
if (!jumpFor(1)) {
|
||||
if (!subjumpFor(1)) {
|
||||
_delegate->storiesJumpTo({});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Controller::jumpAvailable(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();
|
||||
}
|
||||
bool Controller::subjumpAvailable(int delta) const {
|
||||
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;
|
||||
}
|
||||
|
||||
bool Controller::jumpFor(int delta) {
|
||||
if (!_index && delta == -1) {
|
||||
if (!_list || _list->items.empty()) {
|
||||
bool Controller::subjumpFor(int delta) {
|
||||
const auto index = _index + delta;
|
||||
if (index < 0) {
|
||||
if (_siblingLeft->shownId().valid()) {
|
||||
return jumpFor(-1);
|
||||
} else if (!_list || _list->items.empty()) {
|
||||
return false;
|
||||
}
|
||||
_delegate->storiesJumpTo({
|
||||
|
@ -288,10 +339,8 @@ bool Controller::jumpFor(int delta) {
|
|||
.id = _list->items.front().id
|
||||
});
|
||||
return true;
|
||||
}
|
||||
const auto index = _index + delta;
|
||||
if (index < 0 || index >= _list->total) {
|
||||
return false;
|
||||
} else if (index >= _list->total) {
|
||||
return _siblingRight->shownId().valid() && jumpFor(1);
|
||||
} else if (index < _list->items.size()) {
|
||||
// #TODO stories load more
|
||||
_delegate->storiesJumpTo({
|
||||
|
@ -302,6 +351,22 @@ bool Controller::jumpFor(int delta) {
|
|||
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 {
|
||||
return _photoPlayback
|
||||
? _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) {
|
||||
const auto block = !Player::IsPausedOrPausing(state.state)
|
||||
&& !Player::IsStoppedOrStopping(state.state);
|
||||
|
|
|
@ -35,7 +35,9 @@ namespace Media::Stories {
|
|||
class Header;
|
||||
class Slider;
|
||||
class ReplyArea;
|
||||
class Sibling;
|
||||
class Delegate;
|
||||
struct SiblingView;
|
||||
|
||||
enum class HeaderLayout {
|
||||
Normal,
|
||||
|
@ -50,6 +52,8 @@ struct Layout {
|
|||
QPoint controlsBottomPosition;
|
||||
QRect autocompleteRect;
|
||||
HeaderLayout headerLayout = HeaderLayout::Normal;
|
||||
QRect siblingLeft;
|
||||
QRect siblingRight;
|
||||
|
||||
friend inline auto operator<=>(Layout, Layout) = default;
|
||||
friend inline bool operator==(Layout, Layout) = default;
|
||||
|
@ -68,16 +72,26 @@ public:
|
|||
[[nodiscard]] auto stickerOrEmojiChosen() const
|
||||
-> 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 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 paused() const;
|
||||
void togglePaused(bool paused);
|
||||
|
||||
void repaintSibling(not_null<Sibling*> sibling);
|
||||
[[nodiscard]] SiblingView siblingLeft() const;
|
||||
[[nodiscard]] SiblingView siblingRight() const;
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime();
|
||||
|
||||
private:
|
||||
class PhotoPlayback;
|
||||
|
||||
|
@ -86,6 +100,13 @@ private:
|
|||
void updatePlayback(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;
|
||||
|
||||
rpl::variable<std::optional<Layout>> _layout;
|
||||
|
@ -94,11 +115,15 @@ private:
|
|||
const std::unique_ptr<Header> _header;
|
||||
const std::unique_ptr<Slider> _slider;
|
||||
const std::unique_ptr<ReplyArea> _replyArea;
|
||||
std::unique_ptr<PhotoPlayback> _photoPlayback;
|
||||
|
||||
Data::FullStoryId _shown;
|
||||
std::optional<Data::StoriesList> _list;
|
||||
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;
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ public:
|
|||
virtual void storiesJumpTo(Data::FullStoryId id) = 0;
|
||||
[[nodiscard]] virtual bool storiesPaused() = 0;
|
||||
virtual void storiesTogglePaused(bool paused) = 0;
|
||||
virtual void storiesRepaint() = 0;
|
||||
};
|
||||
|
||||
} // namespace Media::Stories
|
||||
|
|
262
Telegram/SourceFiles/media/stories/media_stories_sibling.cpp
Normal file
262
Telegram/SourceFiles/media/stories/media_stories_sibling.cpp
Normal 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
|
48
Telegram/SourceFiles/media/stories/media_stories_sibling.h
Normal file
48
Telegram/SourceFiles/media/stories/media_stories_sibling.h
Normal 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
|
|
@ -136,7 +136,9 @@ void Slider::paint(QRectF clip) {
|
|||
radius,
|
||||
radius);
|
||||
} else {
|
||||
p.setOpacity(kOpacityInactive);
|
||||
p.setOpacity((i < _data.index)
|
||||
? kOpacityActive
|
||||
: kOpacityInactive);
|
||||
p.drawRoundedRect(_rects[i], radius, radius);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,8 +21,11 @@ View::View(not_null<Delegate*> delegate)
|
|||
|
||||
View::~View() = default;
|
||||
|
||||
void View::show(const Data::StoriesList &list, int index) {
|
||||
_controller->show(list, index);
|
||||
void View::show(
|
||||
const std::vector<Data::StoriesList> &lists,
|
||||
int index,
|
||||
int subindex) {
|
||||
_controller->show(lists, index, subindex);
|
||||
}
|
||||
|
||||
void View::ready() {
|
||||
|
@ -33,12 +36,23 @@ QRect View::contentGeometry() const {
|
|||
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) {
|
||||
_controller->updateVideoPlayback(state);
|
||||
}
|
||||
|
||||
bool View::jumpAvailable(int delta) const {
|
||||
return _controller->jumpAvailable(delta);
|
||||
bool View::subjumpAvailable(int delta) const {
|
||||
return _controller->subjumpAvailable(delta);
|
||||
}
|
||||
|
||||
bool View::subjumpFor(int delta) const {
|
||||
return _controller->subjumpFor(delta);
|
||||
}
|
||||
|
||||
bool View::jumpFor(int delta) const {
|
||||
|
@ -53,4 +67,16 @@ void View::togglePaused(bool 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
|
||||
|
|
|
@ -20,24 +20,45 @@ namespace Media::Stories {
|
|||
class Delegate;
|
||||
class Controller;
|
||||
|
||||
struct SiblingView {
|
||||
QImage image;
|
||||
QRect geometry;
|
||||
|
||||
[[nodiscard]] bool valid() const {
|
||||
return !image.isNull();
|
||||
}
|
||||
explicit operator bool() const {
|
||||
return valid();
|
||||
}
|
||||
};
|
||||
|
||||
class View final {
|
||||
public:
|
||||
explicit View(not_null<Delegate*> delegate);
|
||||
~View();
|
||||
|
||||
void show(const Data::StoriesList &list, int index);
|
||||
void show(
|
||||
const std::vector<Data::StoriesList> &lists,
|
||||
int index,
|
||||
int subindex);
|
||||
void ready();
|
||||
|
||||
[[nodiscard]] QRect contentGeometry() const;
|
||||
[[nodiscard]] rpl::producer<QRect> contentGeometryValue() const;
|
||||
[[nodiscard]] SiblingView siblingLeft() const;
|
||||
[[nodiscard]] SiblingView siblingRight() const;
|
||||
|
||||
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 paused() const;
|
||||
void togglePaused(bool paused);
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime();
|
||||
|
||||
private:
|
||||
const std::unique_ptr<Controller> _controller;
|
||||
|
||||
|
|
|
@ -406,10 +406,14 @@ pipVolumeIcon2Over: icon {{ "player/player_volume_on", mediaviewPipControlsFgOve
|
|||
speedSliderDividerSize: size(2px, 8px);
|
||||
|
||||
storiesMaxSize: size(405px, 720px);
|
||||
storiesControlSize: 64px;
|
||||
storiesLeft: icon {{ "mediaview/stories_next-flip_horizontal", mediaviewControlFg }};
|
||||
storiesRight: icon {{ "mediaview/stories_next", mediaviewControlFg }};
|
||||
storiesSliderWidth: 2px;
|
||||
storiesSliderMargin: margins(8px, 7px, 8px, 11px);
|
||||
storiesSliderMargin: margins(8px, 7px, 8px, 6px);
|
||||
storiesSliderSkip: 4px;
|
||||
storiesHeaderMargin: margins(12px, 3px, 12px, 8px);
|
||||
storiesSliderOutsideSkip: 4px;
|
||||
storiesHeaderMargin: margins(12px, 4px, 12px, 8px);
|
||||
storiesHeaderPhoto: UserpicButton(defaultUserpicButton) {
|
||||
size: size(28px, 28px);
|
||||
photoSize: 28px;
|
||||
|
@ -422,7 +426,7 @@ storiesHeaderNamePosition: point(50px, 0px);
|
|||
storiesHeaderDate: FlatLabel(defaultFlatLabel) {
|
||||
textFg: mediaviewControlFg;
|
||||
}
|
||||
storiesHeaderDatePosition: point(50px, 16px);
|
||||
storiesHeaderDatePosition: point(50px, 17px);
|
||||
storiesControlsMinWidth: 200px;
|
||||
storiesFieldMargin: margins(0px, 14px, 0px, 16px);
|
||||
storiesAttach: IconButton(defaultIconButton) {
|
||||
|
|
|
@ -112,6 +112,11 @@ OverlayWidget::RendererGL::RendererGL(not_null<OverlayWidget*> owner)
|
|||
_captionImage.invalidate();
|
||||
invalidateControls();
|
||||
}, _lifetime);
|
||||
|
||||
_owner->_storiesChanged.events(
|
||||
) | rpl::start_with_next([=] {
|
||||
invalidateControls();
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
void OverlayWidget::RendererGL::init(
|
||||
|
@ -568,7 +573,8 @@ void OverlayWidget::RendererGL::paintControl(
|
|||
QRect inner,
|
||||
float64 innerOpacity,
|
||||
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);
|
||||
|
||||
const auto overAlpha = overOpacity * kOverBackgroundOpacity;
|
||||
|
@ -626,11 +632,17 @@ void OverlayWidget::RendererGL::paintControl(
|
|||
FillTexturedRectangle(*_f, &*_controlsProgram, fgOffset);
|
||||
}
|
||||
|
||||
auto OverlayWidget::RendererGL::ControlMeta(OverState control)
|
||||
auto OverlayWidget::RendererGL::ControlMeta(OverState control, bool stories)
|
||||
-> Control {
|
||||
switch (control) {
|
||||
case OverLeftNav: return { 0, &st::mediaviewLeft };
|
||||
case OverRightNav: return { 1, &st::mediaviewRight };
|
||||
case OverLeftNav: return {
|
||||
0,
|
||||
stories ? &st::storiesLeft : &st::mediaviewLeft
|
||||
};
|
||||
case OverRightNav: return {
|
||||
1,
|
||||
stories ? &st::storiesRight : &st::mediaviewRight
|
||||
};
|
||||
case OverSave: return { 2, &st::mediaviewSave };
|
||||
case OverRotate: return { 3, &st::mediaviewRotate };
|
||||
case OverMore: return { 4, &st::mediaviewMore };
|
||||
|
@ -642,12 +654,13 @@ void OverlayWidget::RendererGL::validateControls() {
|
|||
if (!_controlsImage.image().isNull()) {
|
||||
return;
|
||||
}
|
||||
const auto stories = (_owner->_stories != nullptr);
|
||||
const auto metas = {
|
||||
ControlMeta(OverLeftNav),
|
||||
ControlMeta(OverRightNav),
|
||||
ControlMeta(OverSave),
|
||||
ControlMeta(OverRotate),
|
||||
ControlMeta(OverMore),
|
||||
ControlMeta(OverLeftNav, stories),
|
||||
ControlMeta(OverRightNav, stories),
|
||||
ControlMeta(OverSave, stories),
|
||||
ControlMeta(OverRotate, stories),
|
||||
ControlMeta(OverMore, stories),
|
||||
};
|
||||
auto maxWidth = 0;
|
||||
auto fullHeight = 0;
|
||||
|
|
|
@ -134,7 +134,9 @@ private:
|
|||
Ui::GL::Image _controlsImage;
|
||||
|
||||
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.
|
||||
std::array<QRect, kControlsCount + 1> _controlsTextures;
|
||||
|
|
|
@ -813,16 +813,7 @@ void OverlayWidget::updateGeometryToScreen(bool inMove) {
|
|||
}
|
||||
|
||||
void OverlayWidget::updateControlsGeometry() {
|
||||
const auto overRect = QRect(
|
||||
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);
|
||||
updateNavigationControlsGeometry();
|
||||
|
||||
_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);
|
||||
|
@ -843,6 +834,32 @@ void OverlayWidget::updateControlsGeometry() {
|
|||
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 {
|
||||
return _topShadowRight.current();
|
||||
}
|
||||
|
@ -1009,8 +1026,8 @@ void OverlayWidget::updateDocSize() {
|
|||
|
||||
void OverlayWidget::refreshNavVisibility() {
|
||||
if (_stories) {
|
||||
_leftNavVisible = _stories->jumpAvailable(-1);
|
||||
_rightNavVisible = _stories->jumpAvailable(1);
|
||||
_leftNavVisible = _stories->subjumpAvailable(-1);
|
||||
_rightNavVisible = _stories->subjumpAvailable(1);
|
||||
} else if (_sharedMediaData) {
|
||||
_leftNavVisible = _index && (*_index > 0);
|
||||
_rightNavVisible = _index && (*_index + 1 < _sharedMediaData->size());
|
||||
|
@ -1432,6 +1449,12 @@ bool OverlayWidget::updateControlsAnimation(crl::time now) {
|
|||
+ (_over == OverSave ? _saveNavOver : _saveNavIcon)
|
||||
+ (_over == OverRotate ? _rotateNavOver : _rotateNavIcon)
|
||||
+ (_over == OverMore ? _moreNavOver : _moreNavIcon)
|
||||
+ ((_stories && _over == OverLeftStories)
|
||||
? _stories->siblingLeft().geometry
|
||||
: QRect())
|
||||
+ ((_stories && _over == OverRightStories)
|
||||
? _stories->siblingRight().geometry
|
||||
: QRect())
|
||||
+ _headerNav
|
||||
+ _nameNav
|
||||
+ _dateNav
|
||||
|
@ -1547,6 +1570,7 @@ void OverlayWidget::resizeContentByScreenSize() {
|
|||
_y = content.y();
|
||||
_w = content.width();
|
||||
_h = content.height();
|
||||
updateNavigationControlsGeometry();
|
||||
return;
|
||||
}
|
||||
recountSkipTop();
|
||||
|
@ -3861,14 +3885,16 @@ std::shared_ptr<ChatHelpers::Show> OverlayWidget::storiesShow() {
|
|||
return _widget->_body;
|
||||
}
|
||||
bool valid() const override {
|
||||
return _widget->_storiesUser != nullptr;
|
||||
return _widget->_storiesSession != nullptr;
|
||||
}
|
||||
operator bool() const override {
|
||||
return valid();
|
||||
}
|
||||
|
||||
Main::Session &session() const override {
|
||||
return _widget->_storiesUser->session();
|
||||
Expects(_widget->_storiesSession != nullptr);
|
||||
|
||||
return *_widget->_storiesSession;
|
||||
}
|
||||
bool paused(ChatHelpers::PauseReason reason) const override {
|
||||
if (_widget->isHidden()
|
||||
|
@ -3976,6 +4002,10 @@ void OverlayWidget::storiesTogglePaused(bool paused) {
|
|||
}
|
||||
}
|
||||
|
||||
void OverlayWidget::storiesRepaint() {
|
||||
update();
|
||||
}
|
||||
|
||||
void OverlayWidget::playbackToggleFullScreen() {
|
||||
Expects(_streamed != nullptr);
|
||||
|
||||
|
@ -4131,6 +4161,22 @@ void OverlayWidget::paint(not_null<Renderer*> renderer) {
|
|||
fillTransparentBackground);
|
||||
}
|
||||
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 {
|
||||
if (_themePreviewShown) {
|
||||
renderer->paintThemePreview(_themePreviewRect);
|
||||
|
@ -4420,14 +4466,14 @@ void OverlayWidget::paintControls(
|
|||
_leftNavVisible,
|
||||
_leftNavOver,
|
||||
_leftNavIcon,
|
||||
st::mediaviewLeft,
|
||||
_stories ? st::storiesLeft : st::mediaviewLeft,
|
||||
true },
|
||||
{
|
||||
OverRightNav,
|
||||
_rightNavVisible,
|
||||
_rightNavOver,
|
||||
_rightNavIcon,
|
||||
st::mediaviewRight,
|
||||
_stories ? st::storiesRight : st::mediaviewRight,
|
||||
true },
|
||||
{
|
||||
OverSave,
|
||||
|
@ -4470,6 +4516,10 @@ void OverlayWidget::paintControls(
|
|||
float64 OverlayWidget::controlOpacity(
|
||||
float64 progress,
|
||||
bool nonbright) const {
|
||||
if (nonbright && _stories) {
|
||||
return progress * kStoriesNavOverOpacity
|
||||
+ (1. - progress) * kStoriesNavOpacity;
|
||||
}
|
||||
const auto normal = _windowed
|
||||
? kNormalIconOpacity
|
||||
: kMaximizedIconOpacity;
|
||||
|
@ -4813,15 +4863,13 @@ void OverlayWidget::setContext(
|
|||
_history = _message->history();
|
||||
_peer = _history->peer;
|
||||
_topicRootId = _peer->isForum() ? item->topicRootId : MsgId();
|
||||
_stories = nullptr;
|
||||
_storiesUser = nullptr;
|
||||
setStoriesUser(nullptr);
|
||||
} else if (const auto peer = std::get_if<not_null<PeerData*>>(&context)) {
|
||||
_peer = *peer;
|
||||
_history = _peer->owner().history(_peer);
|
||||
_message = nullptr;
|
||||
_topicRootId = MsgId();
|
||||
_stories = nullptr;
|
||||
_storiesUser = nullptr;
|
||||
setStoriesUser(nullptr);
|
||||
} else if (const auto story = std::get_if<StoriesContext>(&context)) {
|
||||
_message = nullptr;
|
||||
_topicRootId = MsgId();
|
||||
|
@ -4837,18 +4885,14 @@ void OverlayWidget::setContext(
|
|||
i->items,
|
||||
story->id,
|
||||
&Data::StoryItem::id);
|
||||
_storiesUser = story->user;
|
||||
if (!_stories) {
|
||||
_stories = std::make_unique<Stories::View>(
|
||||
static_cast<Stories::Delegate*>(this));
|
||||
}
|
||||
_stories->show(*i, j - begin(i->items));
|
||||
setStoriesUser(story->user);
|
||||
_stories->show(all, (i - begin(all)), j - begin(i->items));
|
||||
} else {
|
||||
_message = nullptr;
|
||||
_topicRootId = MsgId();
|
||||
_history = nullptr;
|
||||
_peer = nullptr;
|
||||
_stories = nullptr;
|
||||
setStoriesUser(nullptr);
|
||||
}
|
||||
_migrated = nullptr;
|
||||
if (_history) {
|
||||
|
@ -4863,6 +4907,27 @@ void OverlayWidget::setContext(
|
|||
_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) {
|
||||
if (_session == session) {
|
||||
return;
|
||||
|
@ -4908,7 +4973,7 @@ void OverlayWidget::setSession(not_null<Main::Session*> session) {
|
|||
|
||||
bool OverlayWidget::moveToNext(int delta) {
|
||||
if (_stories) {
|
||||
return _stories->jumpFor(delta);
|
||||
return _stories->subjumpFor(delta);
|
||||
} else if (!_index) {
|
||||
return false;
|
||||
}
|
||||
|
@ -4991,9 +5056,14 @@ void OverlayWidget::handleMousePress(
|
|||
if (button == Qt::LeftButton) {
|
||||
_down = OverNone;
|
||||
if (!ClickHandler::getPressed()) {
|
||||
if (_over == OverLeftNav && moveToNext(-1)) {
|
||||
_lastAction = position;
|
||||
} else if (_over == OverRightNav && moveToNext(1)) {
|
||||
if ((_over == OverLeftNav && moveToNext(-1))
|
||||
|| (_over == OverRightNav && moveToNext(1))
|
||||
|| (_stories
|
||||
&& _over == OverLeftStories
|
||||
&& _stories->jumpFor(-1))
|
||||
|| (_stories
|
||||
&& _over == OverRightStories
|
||||
&& _stories->jumpFor(1))) {
|
||||
_lastAction = position;
|
||||
} else if (_over == OverName
|
||||
|| _over == OverDate
|
||||
|
@ -5082,8 +5152,18 @@ void OverlayWidget::handleMouseMove(QPoint position) {
|
|||
|
||||
void OverlayWidget::updateOverRect(OverState state) {
|
||||
switch (state) {
|
||||
case OverLeftNav: update(_leftNavOver); break;
|
||||
case OverRightNav: update(_rightNavOver); break;
|
||||
case OverLeftNav:
|
||||
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 OverDate: update(_dateNav); break;
|
||||
case OverSave: update(_saveNavOver); break;
|
||||
|
@ -5170,6 +5250,10 @@ void OverlayWidget::updateOver(QPoint pos) {
|
|||
updateOverState(OverVideo);
|
||||
} else if (_leftNavVisible && _leftNav.contains(pos)) {
|
||||
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)) {
|
||||
updateOverState(OverRightNav);
|
||||
} else if (!_stories && _from && _nameNav.contains(pos)) {
|
||||
|
@ -5527,6 +5611,7 @@ void OverlayWidget::clearBeforeHide() {
|
|||
_collage = nullptr;
|
||||
_collageData = std::nullopt;
|
||||
clearStreaming();
|
||||
setStoriesUser(nullptr);
|
||||
assignMediaPointer(nullptr);
|
||||
_preloadPhotos.clear();
|
||||
_preloadDocuments.clear();
|
||||
|
|
|
@ -137,6 +137,8 @@ private:
|
|||
OverNone,
|
||||
OverLeftNav,
|
||||
OverRightNav,
|
||||
OverLeftStories,
|
||||
OverRightStories,
|
||||
OverHeader,
|
||||
OverName,
|
||||
OverDate,
|
||||
|
@ -231,6 +233,7 @@ private:
|
|||
void storiesJumpTo(Data::FullStoryId id) override;
|
||||
bool storiesPaused() override;
|
||||
void storiesTogglePaused(bool paused) override;
|
||||
void storiesRepaint() override;
|
||||
|
||||
void hideControls(bool force = false);
|
||||
void subscribeToScreenGeometry();
|
||||
|
@ -292,6 +295,7 @@ private:
|
|||
ItemContext,
|
||||
not_null<PeerData*>,
|
||||
StoriesContext> context);
|
||||
void setStoriesUser(UserData *user);
|
||||
|
||||
void refreshLang();
|
||||
void showSaveMsgFile();
|
||||
|
@ -332,6 +336,7 @@ private:
|
|||
void updateDocSize();
|
||||
void updateControls();
|
||||
void updateControlsGeometry();
|
||||
void updateNavigationControlsGeometry();
|
||||
|
||||
using MenuCallback = Fn<void(
|
||||
const QString &,
|
||||
|
@ -572,7 +577,8 @@ private:
|
|||
bool _showAsPip = false;
|
||||
|
||||
std::unique_ptr<Stories::View> _stories;
|
||||
UserData *_storiesUser = nullptr;
|
||||
rpl::event_stream<> _storiesChanged;
|
||||
Main::Session *_storiesSession = nullptr;
|
||||
rpl::event_stream<ChatHelpers::FileChosen> _storiesStickerOrEmojiChosen;
|
||||
std::unique_ptr<Ui::LayerManager> _layerBg;
|
||||
|
||||
|
|
|
@ -20,6 +20,8 @@ namespace Media::View {
|
|||
inline constexpr auto kMaximizedIconOpacity = 0.6;
|
||||
inline constexpr auto kNormalIconOpacity = 0.9;
|
||||
inline constexpr auto kOverBackgroundOpacity = 0.2775;
|
||||
inline constexpr auto kStoriesNavOpacity = 0.3;
|
||||
inline constexpr auto kStoriesNavOverOpacity = 0.7;
|
||||
[[nodiscard]] QColor OverBackgroundColor();
|
||||
|
||||
} // namespace Media::View
|
||||
|
|
Loading…
Add table
Reference in a new issue