Support displaying of photo spoilers.

This commit is contained in:
John Preston 2022-12-12 22:57:17 +04:00
parent ae819eb1a6
commit 25746d195c
26 changed files with 315 additions and 109 deletions

View file

@ -667,6 +667,8 @@ PRIVATE
history/view/media/history_view_media_common.h
history/view/media/history_view_media_grouped.cpp
history/view/media/history_view_media_grouped.h
history/view/media/history_view_media_spoiler.cpp
history/view/media/history_view_media_spoiler.h
history/view/media/history_view_media_unwrapped.cpp
history/view/media/history_view_media_unwrapped.h
history/view/media/history_view_photo.cpp

View file

@ -530,9 +530,11 @@ ItemPreview Media::toGroupPreview(
MediaPhoto::MediaPhoto(
not_null<HistoryItem*> parent,
not_null<PhotoData*> photo)
not_null<PhotoData*> photo,
bool spoiler)
: Media(parent)
, _photo(photo) {
, _photo(photo)
, _spoiler(spoiler) {
parent->history()->owner().registerPhotoItem(_photo, parent);
}
@ -556,7 +558,7 @@ MediaPhoto::~MediaPhoto() {
std::unique_ptr<Media> MediaPhoto::clone(not_null<HistoryItem*> parent) {
return _chat
? std::make_unique<MediaPhoto>(parent, _chat, _photo)
: std::make_unique<MediaPhoto>(parent, _photo);
: std::make_unique<MediaPhoto>(parent, _photo, _spoiler);
}
PhotoData *MediaPhoto::photo() const {
@ -722,17 +724,20 @@ std::unique_ptr<HistoryView::Media> MediaPhoto::createView(
return std::make_unique<HistoryView::Photo>(
message,
realParent,
_photo);
_photo,
_spoiler);
}
MediaFile::MediaFile(
not_null<HistoryItem*> parent,
not_null<DocumentData*> document,
bool skipPremiumEffect)
bool skipPremiumEffect,
bool spoiler)
: Media(parent)
, _document(document)
, _emoji(document->sticker() ? document->sticker()->alt : QString())
, _skipPremiumEffect(skipPremiumEffect) {
, _skipPremiumEffect(skipPremiumEffect)
, _spoiler(spoiler) {
parent->history()->owner().registerDocumentItem(_document, parent);
if (!_emoji.isEmpty()) {
@ -755,7 +760,8 @@ std::unique_ptr<Media> MediaFile::clone(not_null<HistoryItem*> parent) {
return std::make_unique<MediaFile>(
parent,
_document,
!_document->session().premium());
!_document->session().premium(),
_spoiler);
}
DocumentData *MediaFile::document() const {
@ -1096,13 +1102,15 @@ std::unique_ptr<HistoryView::Media> MediaFile::createView(
return std::make_unique<HistoryView::Gif>(
message,
realParent,
_document);
_document,
_spoiler);
}
} else if (_document->isAnimation() || _document->isVideoFile()) {
return std::make_unique<HistoryView::Gif>(
message,
realParent,
_document);
_document,
_spoiler);
} else if (_document->isTheme() && _document->hasThumbnail()) {
return std::make_unique<HistoryView::ThemeDocument>(
message,

View file

@ -165,7 +165,8 @@ class MediaPhoto final : public Media {
public:
MediaPhoto(
not_null<HistoryItem*> parent,
not_null<PhotoData*> photo);
not_null<PhotoData*> photo,
bool spoiler);
MediaPhoto(
not_null<HistoryItem*> parent,
not_null<PeerData*> chat,
@ -200,6 +201,7 @@ public:
private:
not_null<PhotoData*> _photo;
PeerData *_chat = nullptr;
bool _spoiler = false;
};
@ -208,7 +210,8 @@ public:
MediaFile(
not_null<HistoryItem*> parent,
not_null<DocumentData*> document,
bool skipPremiumEffect);
bool skipPremiumEffect,
bool spoiler);
~MediaFile();
std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override;
@ -242,6 +245,7 @@ private:
not_null<DocumentData*> _document;
QString _emoji;
bool _skipPremiumEffect = false;
bool _spoiler = false;
};

View file

@ -184,7 +184,8 @@ std::unique_ptr<Data::Media> HistoryItem::CreateMedia(
return photo->match([&](const MTPDphoto &photo) -> Result {
return std::make_unique<Data::MediaPhoto>(
item,
item->history()->owner().processPhoto(photo));
item->history()->owner().processPhoto(photo),
media.is_spoiler());
}, [](const MTPDphotoEmpty &) -> Result {
return nullptr;
});
@ -205,7 +206,8 @@ std::unique_ptr<Data::Media> HistoryItem::CreateMedia(
return std::make_unique<Data::MediaFile>(
item,
item->history()->owner().processDocument(document),
media.is_nopremium());
media.is_nopremium(),
media.is_spoiler());
}, [](const MTPDdocumentEmpty &) -> Result {
return nullptr;
});
@ -513,10 +515,12 @@ HistoryItem::HistoryItem(
std::move(markup));
const auto skipPremiumEffect = !history->session().premium();
const auto spoiler = false;
_media = std::make_unique<Data::MediaFile>(
this,
document,
skipPremiumEffect);
skipPremiumEffect,
spoiler);
setText(caption);
}
@ -545,7 +549,8 @@ HistoryItem::HistoryItem(
postAuthor,
std::move(markup));
_media = std::make_unique<Data::MediaPhoto>(this, photo);
const auto spoiler = false;
_media = std::make_unique<Data::MediaPhoto>(this, photo, spoiler);
setText(caption);
}

View file

@ -431,6 +431,9 @@ void Element::hideSpoilers() {
if (_text.hasSpoilers()) {
_text.setSpoilerRevealed(false, anim::type::instant);
}
if (_media) {
_media->hideSpoilers();
}
}
void Element::customEmojiRepaint() {

View file

@ -7,11 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "history/view/history_view_spoiler_click_handler.h"
#include "core/click_handler_types.h" // ClickHandlerContext
#include "data/data_session.h"
#include "history/view/history_view_element.h"
#include "history/history.h"
#include "main/main_session.h"
#include "window/window_session_controller.h"
#include "base/weak_ptr.h"
namespace HistoryView {
@ -22,18 +21,13 @@ void FillTextWithAnimatedSpoilers(
if (text.hasSpoilers()) {
text.setSpoilerLinkFilter([weak = base::make_weak(view)](
const ClickContext &context) {
const auto my = context.other.value<ClickHandlerContext>();
const auto button = context.button;
const auto view = weak.get();
if (button != Qt::LeftButton || !view || !my.elementDelegate) {
if (button != Qt::LeftButton || !view) {
return false;
} else if (const auto d = my.elementDelegate()) {
if (const auto controller = my.sessionWindow.get()) {
controller->session().data().registerShownSpoiler(view);
}
return true;
}
return false;
view->history()->owner().registerShownSpoiler(view);
return true;
});
}
}

View file

@ -1454,6 +1454,12 @@ TextWithEntities Document::getCaption() const {
return TextWithEntities();
}
void Document::hideSpoilers() {
if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
captioned->caption.setSpoilerRevealed(false, anim::type::instant);
}
}
Ui::Text::String Document::createCaption() {
return File::createCaption(_realParent);
}

View file

@ -54,6 +54,7 @@ public:
}
TextWithEntities getCaption() const override;
void hideSpoilers() override;
bool needsBubble() const override {
return true;
}

View file

@ -54,7 +54,7 @@ ExtendedPreview::ExtendedPreview(
, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) {
const auto item = parent->data();
_caption = createCaption(item);
_link = MakeInvoiceLink(item);
_spoiler.link = MakeInvoiceLink(item);
resolveButtonText();
}
@ -97,15 +97,15 @@ void ExtendedPreview::ensureThumbnailRead() const {
}
bool ExtendedPreview::hasHeavyPart() const {
return _animation || !_inlineThumbnail.isNull();
return _spoiler.animation || !_inlineThumbnail.isNull();
}
void ExtendedPreview::unloadHeavyPart() {
_inlineThumbnail
= _imageCache
= _cornerCache
= _spoiler.background
= _spoiler.cornerCache
= _buttonBackground = QImage();
_animation = nullptr;
_spoiler.animation = nullptr;
_caption.unloadPersistentAnimation();
}
@ -217,8 +217,8 @@ void ExtendedPreview::draw(Painter &p, const PaintContext &context) const {
fillImageShadow(p, rthumb, *rounding, context);
}
validateImageCache(rthumb.size(), rounding);
p.drawImage(rthumb.topLeft(), _imageCache);
fillSpoilerMess(p, rthumb, rounding, context);
p.drawImage(rthumb.topLeft(), _spoiler.background);
fillImageSpoiler(p, &_spoiler, rthumb, context);
paintButton(p, rthumb, context);
if (context.selected()) {
fillImageOverlay(p, rthumb, rounding, context);
@ -263,14 +263,14 @@ void ExtendedPreview::validateImageCache(
QSize outer,
std::optional<Ui::BubbleRounding> rounding) const {
const auto ratio = style::DevicePixelRatio();
if (_imageCache.size() == (outer * ratio)
&& _imageCacheRounding == rounding) {
if (_spoiler.background.size() == (outer * ratio)
&& _spoiler.backgroundRounding == rounding) {
return;
}
_imageCache = Images::Round(
_spoiler.background = Images::Round(
prepareImageCache(outer),
MediaRoundingMask(rounding));
_imageCacheRounding = rounding;
_spoiler.backgroundRounding = rounding;
}
QImage ExtendedPreview::prepareImageCache(QSize outer) const {
@ -278,28 +278,6 @@ QImage ExtendedPreview::prepareImageCache(QSize outer) const {
return PrepareWithBlurredBackground(outer, {}, {}, _inlineThumbnail);
}
void ExtendedPreview::fillSpoilerMess(
QPainter &p,
QRect rect,
std::optional<Ui::BubbleRounding> rounding,
const PaintContext &context) const {
if (!_animation) {
_animation = std::make_unique<Ui::SpoilerAnimation>([=] {
_parent->customEmojiRepaint();
});
history()->owner().registerHeavyViewPart(_parent);
}
_parent->clearCustomEmojiRepaint();
const auto &spoiler = Ui::DefaultImageSpoiler();
const auto index = _animation->index(context.now, context.paused);
Ui::FillSpoilerRect(
p,
rect,
MediaRoundingMask(rounding),
spoiler.frame(index),
_cornerCache);
}
void ExtendedPreview::paintButton(
Painter &p,
QRect outer,
@ -318,13 +296,14 @@ void ExtendedPreview::paintButton(
const auto size = QSize(width, height);
if (_buttonBackground.size() != size * ratio
|| _buttonBackgroundOverlay != overlay) {
if (_imageCache.width() < width * ratio
|| _imageCache.height() < height * ratio) {
auto &background = _spoiler.background;
if (background.width() < width * ratio
|| background.height() < height * ratio) {
return;
}
_buttonBackground = _imageCache.copy(QRect(
(_imageCache.width() - width * ratio) / 2,
(_imageCache.height() - height * ratio) / 2,
_buttonBackground = background.copy(QRect(
(background.width() - width * ratio) / 2,
(background.height() - height * ratio) / 2,
width * ratio,
height * ratio));
_buttonBackground.setDevicePixelRatio(ratio);
@ -374,7 +353,7 @@ TextState ExtendedPreview::textState(QPoint point, StateRequest request) const {
painth -= st::mediaCaptionSkip;
}
if (QRect(paintx, painty, paintw, painth).contains(point)) {
result.link = _link;
result.link = _spoiler.link;
}
if (_caption.isEmpty() && _parent->media() == this) {
auto fullRight = paintx + paintw;
@ -402,11 +381,11 @@ TextState ExtendedPreview::textState(QPoint point, StateRequest request) const {
}
bool ExtendedPreview::toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const {
return p == _link;
return p == _spoiler.link;
}
bool ExtendedPreview::dragItemByHandler(const ClickHandlerPtr &p) const {
return p == _link;
return p == _spoiler.link;
}
bool ExtendedPreview::needInfoDisplay() const {
@ -420,6 +399,10 @@ TextForMimeData ExtendedPreview::selectedText(TextSelection selection) const {
return _caption.toTextForMimeData(selection);
}
void ExtendedPreview::hideSpoilers() {
_caption.setSpoilerRevealed(false, anim::type::instant);
}
bool ExtendedPreview::needsBubble() const {
if (!_caption.isEmpty()) {
return true;

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "history/view/media/history_view_media.h"
#include "history/view/media/history_view_media_spoiler.h"
enum class ImageRoundRadius;
@ -55,6 +56,7 @@ public:
TextWithEntities getCaption() const override {
return _caption.toTextWithEntities();
}
void hideSpoilers() override;
bool needsBubble() const override;
bool customInfoLayout() const override {
return _caption.isEmpty();
@ -87,23 +89,13 @@ private:
QRect outer,
const PaintContext &context) const;
void fillSpoilerMess(
QPainter &p,
QRect rect,
std::optional<Ui::BubbleRounding> rounding,
const PaintContext &context) const;
const not_null<Data::Invoice*> _invoice;
ClickHandlerPtr _link;
Ui::Text::String _caption;
mutable std::unique_ptr<Ui::SpoilerAnimation> _animation;
mutable MediaSpoiler _spoiler;
mutable QImage _inlineThumbnail;
mutable QImage _imageCache;
mutable QImage _cornerCache;
mutable QImage _buttonBackground;
mutable QColor _buttonBackgroundOverlay;
mutable Ui::Text::String _buttonText;
mutable std::optional<Ui::BubbleRounding> _imageCacheRounding;
mutable bool _imageCacheInvalid = false;
};

View file

@ -15,7 +15,7 @@ class FileClickHandler;
namespace HistoryView {
class File : public Media, public base::has_weak_ptr {
class File : public Media {
public:
File(
not_null<Element*> parent,

View file

@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_cursor_state.h"
#include "history/view/history_view_transcribe_button.h"
#include "history/view/media/history_view_media_common.h"
#include "history/view/media/history_view_media_spoiler.h"
#include "window/window_session_controller.h"
#include "core/application.h" // Application::showDocument.
#include "ui/chat/attach/attach_prepare.h"
@ -38,6 +39,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/grouped_layout.h"
#include "ui/cached_round_corners.h"
#include "ui/effects/path_shift_gradient.h"
#include "ui/effects/spoiler_mess.h"
#include "data/data_session.h"
#include "data/data_streaming.h"
#include "data/data_document.h"
@ -80,10 +82,12 @@ Gif::Streamed::Streamed(
Gif::Gif(
not_null<Element*> parent,
not_null<HistoryItem*> realParent,
not_null<DocumentData*> document)
not_null<DocumentData*> document,
bool spoiler)
: File(parent, realParent)
, _data(document)
, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right())
, _spoiler(spoiler ? std::make_unique<MediaSpoiler>() : nullptr)
, _downloadSize(Ui::FormatSizeText(_data->size)) {
setDocumentLinks(_data, realParent);
setStatusSize(Ui::FileStatusSizeReady);
@ -476,6 +480,13 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
p.drawImage(rthumb, _thumbCache);
}
if (!isRound && _spoiler) {
fillImageSpoiler(
p,
_spoiler.get(),
rthumb,
context);
}
if (context.selected()) {
if (isRound) {
Ui::FillComplexEllipse(p, st, rthumb);
@ -1312,6 +1323,13 @@ bool Gif::uploading() const {
return _data->uploading();
}
void Gif::hideSpoilers() {
_caption.setSpoilerRevealed(false, anim::type::instant);
if (_spoiler) {
_spoiler->revealed = false;
}
}
bool Gif::needsBubble() const {
if (_data->isVideoMessage()) {
return false;
@ -1538,12 +1556,16 @@ void Gif::parentTextUpdated() {
}
bool Gif::hasHeavyPart() const {
return _streamed || _dataMedia;
return (_spoiler && _spoiler->animation) || _streamed || _dataMedia;
}
void Gif::unloadHeavyPart() {
stopAnimation();
_dataMedia = nullptr;
if (_spoiler) {
_spoiler->background = _spoiler->cornerCache = QImage();
_spoiler->animation = nullptr;
}
_thumbCache = QImage();
_videoThumbnailFrame = nullptr;
_caption.unloadPersistentAnimation();

View file

@ -44,7 +44,8 @@ public:
Gif(
not_null<Element*> parent,
not_null<HistoryItem*> realParent,
not_null<DocumentData*> document);
not_null<DocumentData*> document,
bool spoiler);
~Gif();
void draw(Painter &p, const PaintContext &context) const override;
@ -98,6 +99,7 @@ public:
TextWithEntities getCaption() const override {
return _caption.toTextWithEntities();
}
void hideSpoilers() override;
bool needsBubble() const override;
bool unwrapped() const override;
bool customInfoLayout() const override {
@ -206,6 +208,7 @@ private:
const not_null<DocumentData*> _data;
Ui::Text::String _caption;
std::unique_ptr<Streamed> _streamed;
const std::unique_ptr<MediaSpoiler> _spoiler;
mutable std::unique_ptr<TranscribeButton> _transcribe;
mutable std::shared_ptr<Data::DocumentMedia> _dataMedia;
mutable std::unique_ptr<Image> _videoThumbnailFrame;

View file

@ -34,10 +34,12 @@ Invoice::Invoice(
void Invoice::fillFromData(not_null<Data::Invoice*> invoice) {
if (invoice->photo) {
const auto spoiler = false;
_attach = std::make_unique<Photo>(
_parent,
_parent->data(),
invoice->photo);
invoice->photo,
spoiler);
} else {
_attach = nullptr;
}

View file

@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_cursor_state.h"
#include "history/view/history_view_spoiler_click_handler.h"
#include "history/view/media/history_view_sticker.h"
#include "history/view/media/history_view_media_spoiler.h"
#include "storage/storage_shared_media.h"
#include "data/data_document.h"
#include "data/data_session.h"
@ -246,6 +247,46 @@ void Media::fillImageOverlay(
Ui::FillComplexOverlayRect(p, rect, st->msgSelectOverlay(), corners);
}
void Media::fillImageSpoiler(
QPainter &p,
not_null<MediaSpoiler*> spoiler,
QRect rect,
const PaintContext &context) const {
if (!spoiler->animation) {
spoiler->animation = std::make_unique<Ui::SpoilerAnimation>([=] {
_parent->customEmojiRepaint();
});
history()->owner().registerHeavyViewPart(_parent);
}
_parent->clearCustomEmojiRepaint();
Ui::FillSpoilerRect(
p,
rect,
MediaRoundingMask(spoiler->backgroundRounding),
Ui::DefaultImageSpoiler().frame(
spoiler->animation->index(context.now, context.paused)),
spoiler->cornerCache);
}
void Media::createSpoilerLink(not_null<MediaSpoiler*> spoiler) {
const auto weak = base::make_weak(this);
spoiler->link = std::make_shared<LambdaClickHandler>([=](
const ClickContext &context) {
const auto button = context.button;
const auto media = weak.get();
if (button != Qt::LeftButton || !media || spoiler->revealed) {
return;
}
const auto view = media->parent();
spoiler->revealed = true;
spoiler->revealAnimation.start([=] {
media->history()->owner().requestViewRepaint(view);
}, 0., 1., st::fadeWrapDuration);
media->history()->owner().requestViewRepaint(view);
media->history()->owner().registerShownSpoiler(view);
});
}
void Media::repaint() const {
history()->owner().requestViewRepaint(_parent);
}

View file

@ -32,6 +32,7 @@ struct ColorReplacements;
namespace Ui {
struct BubbleSelectionInterval;
struct ChatPaintContext;
class SpoilerAnimation;
} // namespace Ui
namespace Images {
@ -45,6 +46,7 @@ enum class CursorState : char;
enum class InfoDisplayType : char;
struct TextState;
struct StateRequest;
struct MediaSpoiler;
class StickerPlayer;
class Element;
@ -74,7 +76,7 @@ enum class MediaInBubbleState : uchar {
TimeId duration,
const QString &base);
class Media : public Object {
class Media : public Object, public base::has_weak_ptr {
public:
explicit Media(not_null<Element*> parent) : _parent(parent) {
}
@ -209,6 +211,8 @@ public:
[[nodiscard]] virtual TextWithEntities getCaption() const {
return TextWithEntities();
}
virtual void hideSpoilers() {
}
[[nodiscard]] virtual bool needsBubble() const = 0;
[[nodiscard]] virtual bool unwrapped() const {
return false;
@ -343,6 +347,12 @@ protected:
QRect rect,
std::optional<Ui::BubbleRounding> rounding, // nullopt if in WebPage.
const PaintContext &context) const;
void fillImageSpoiler(
QPainter &p,
not_null<MediaSpoiler*> spoiler,
QRect rect,
const PaintContext &context) const;
void createSpoilerLink(not_null<MediaSpoiler*> spoiler);
void repaint() const;

View file

@ -62,6 +62,7 @@ std::unique_ptr<Media> CreateAttach(
if (!collage.empty()) {
return std::make_unique<GroupedMedia>(parent, collage);
} else if (document) {
const auto spoiler = false;
if (document->sticker()) {
const auto skipPremiumEffect = true;
return std::make_unique<UnwrappedMedia>(
@ -71,7 +72,11 @@ std::unique_ptr<Media> CreateAttach(
document,
skipPremiumEffect));
} else if (document->isAnimation() || document->isVideoFile()) {
return std::make_unique<Gif>(parent, parent->data(), document);
return std::make_unique<Gif>(
parent,
parent->data(),
document,
spoiler);
} else if (document->isWallPaper() || document->isTheme()) {
return std::make_unique<ThemeDocument>(
parent,
@ -80,10 +85,12 @@ std::unique_ptr<Media> CreateAttach(
}
return std::make_unique<Document>(parent, parent->data(), document);
} else if (photo) {
const auto spoiler = false;
return std::make_unique<Photo>(
parent,
parent->data(),
photo);
photo,
spoiler);
} else if (const auto params = ThemeDocument::ParamsFromUrl(webpageUrl)) {
return std::make_unique<ThemeDocument>(parent, nullptr, params);
}

View file

@ -694,6 +694,13 @@ TextWithEntities GroupedMedia::getCaption() const {
return main()->getCaption();
}
void GroupedMedia::hideSpoilers() {
_caption.setSpoilerRevealed(false, anim::type::instant);
for (const auto &part : _parts) {
part.content->hideSpoilers();
}
}
Storage::SharedMediaTypesMask GroupedMedia::sharedMediaTypes() const {
return main()->sharedMediaTypes();
}

View file

@ -67,6 +67,7 @@ public:
bool pressed) override;
TextWithEntities getCaption() const override;
void hideSpoilers() override;
Storage::SharedMediaTypesMask sharedMediaTypes() const override;
bool overrideEditedDate() const override {

View file

@ -0,0 +1,9 @@
/*
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 "history/view/media/history_view_media_spoiler.h"

View file

@ -0,0 +1,25 @@
/*
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 "ui/chat/message_bubble.h"
#include "ui/effects/animations.h"
namespace HistoryView {
struct MediaSpoiler {
ClickHandlerPtr link;
std::unique_ptr<Ui::SpoilerAnimation> animation;
QImage cornerCache;
QImage background;
std::optional<Ui::BubbleRounding> backgroundRounding;
Ui::Animations::Simple revealAnimation;
bool revealed = false;
};
} // namespace HistoryView

View file

@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_element.h"
#include "history/view/history_view_cursor_state.h"
#include "history/view/media/history_view_media_common.h"
#include "history/view/media/history_view_media_spoiler.h"
#include "media/streaming/media_streaming_instance.h"
#include "media/streaming/media_streaming_player.h"
#include "media/streaming/media_streaming_document.h"
@ -20,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h"
#include "main/main_session_settings.h"
#include "ui/image/image.h"
#include "ui/effects/spoiler_mess.h"
#include "ui/chat/chat_style.h"
#include "ui/grouped_layout.h"
#include "ui/cached_round_corners.h"
@ -58,10 +60,12 @@ Photo::Streamed::Streamed(
Photo::Photo(
not_null<Element*> parent,
not_null<HistoryItem*> realParent,
not_null<PhotoData*> photo)
not_null<PhotoData*> photo,
bool spoiler)
: File(parent, realParent)
, _data(photo)
, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) {
, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right())
, _spoiler(spoiler ? std::make_unique<MediaSpoiler>() : nullptr) {
_caption = createCaption(realParent);
create(realParent->fullId());
}
@ -110,6 +114,9 @@ void Photo::create(FullMsgId contextId, PeerData *chat) {
|| _data->hasExact(PhotoSize::Thumbnail))) {
_data->load(PhotoSize::Small, contextId);
}
if (_spoiler) {
createSpoilerLink(_spoiler.get());
}
}
void Photo::ensureDataMediaCreated() const {
@ -132,12 +139,16 @@ void Photo::dataMediaCreated() const {
}
bool Photo::hasHeavyPart() const {
return _streamed || _dataMedia;
return (_spoiler && _spoiler->animation) || _streamed || _dataMedia;
}
void Photo::unloadHeavyPart() {
stopAnimation();
_dataMedia = nullptr;
if (_spoiler) {
_spoiler->background = _spoiler->cornerCache = QImage();
_spoiler->animation = nullptr;
}
_imageCache = QImage();
_caption.unloadPersistentAnimation();
}
@ -276,8 +287,22 @@ void Photo::draw(Painter &p, const PaintContext &context) const {
Assert(rounding.has_value());
fillImageShadow(p, rthumb, *rounding, context);
}
validateImageCache(rthumb.size(), rounding);
p.drawImage(rthumb.topLeft(), _imageCache);
const auto revealed = _spoiler
? _spoiler->revealAnimation.value(_spoiler->revealed ? 1. : 0.)
: 1.;
if (revealed < 1.) {
validateSpoilerImageCache(rthumb.size(), rounding);
}
if (revealed > 0.) {
validateImageCache(rthumb.size(), rounding);
p.drawImage(rthumb.topLeft(), _imageCache);
}
if (revealed < 1.) {
p.setOpacity(1. - revealed);
p.drawImage(rthumb.topLeft(), _spoiler->background);
fillImageSpoiler(p, _spoiler.get(), rthumb, context);
p.setOpacity(1.);
}
if (context.selected()) {
fillImageOverlay(p, rthumb, rounding, context);
}
@ -414,9 +439,30 @@ void Photo::validateImageCache(
_imageCacheBlurred = blurredValue;
}
void Photo::validateSpoilerImageCache(
QSize outer,
std::optional<Ui::BubbleRounding> rounding) const {
Expects(_spoiler != nullptr);
const auto ratio = style::DevicePixelRatio();
if (_spoiler->background.size() == (outer * ratio)
&& _spoiler->backgroundRounding == rounding) {
return;
}
_spoiler->background = Images::Round(
prepareImageCacheWithLarge(outer, nullptr),
MediaRoundingMask(rounding));
_spoiler->backgroundRounding = rounding;
}
QImage Photo::prepareImageCache(QSize outer) const {
return prepareImageCacheWithLarge(
outer,
_dataMedia->image(PhotoSize::Large));
}
QImage Photo::prepareImageCacheWithLarge(QSize outer, Image *large) const {
using Size = PhotoSize;
const auto large = _dataMedia->image(Size::Large);
auto blurred = (Image*)nullptr;
if (const auto embedded = _dataMedia->thumbnailInline()) {
blurred = embedded;
@ -536,15 +582,15 @@ TextState Photo::textState(QPoint point, StateRequest request) const {
}
if (QRect(paintx, painty, paintw, painth).contains(point)) {
ensureDataMediaCreated();
if (_data->uploading()) {
result.link = _cancell;
} else if (_dataMedia->loaded()) {
result.link = _openl;
} else if (_data->loading()) {
result.link = _cancell;
} else {
result.link = _savel;
}
result.link = (_spoiler && !_spoiler->revealed)
? _spoiler->link
: _data->uploading()
? _cancell
: _dataMedia->loaded()
? _openl
: _data->loading()
? _cancell
: _savel;
}
if (_caption.isEmpty() && _parent->media() == this) {
auto fullRight = paintx + paintw;
@ -593,8 +639,6 @@ void Photo::drawGrouped(
ensureDataMediaCreated();
_dataMedia->automaticLoad(_realParent->fullId(), _parent->data());
validateGroupedCache(geometry, rounding, cacheKey, cache);
const auto st = context.st;
const auto sti = context.imageStyle();
const auto loaded = _dataMedia->loaded();
@ -608,7 +652,22 @@ void Photo::drawGrouped(
}
const auto radial = isRadialAnimation();
p.drawPixmap(geometry.topLeft(), *cache);
const auto revealed = _spoiler
? _spoiler->revealAnimation.value(_spoiler->revealed ? 1. : 0.)
: 1.;
if (revealed < 1.) {
validateSpoilerImageCache(geometry.size(), rounding);
}
if (revealed > 0.) {
validateGroupedCache(geometry, rounding, cacheKey, cache);
p.drawPixmap(geometry.topLeft(), *cache);
}
if (revealed < 1.) {
p.setOpacity(1. - revealed);
p.drawImage(geometry.topLeft(), _spoiler->background);
fillImageSpoiler(p, _spoiler.get(), geometry, context);
p.setOpacity(1.);
}
const auto overlayOpacity = context.selected()
? (1. - highlightOpacity)
@ -688,7 +747,9 @@ TextState Photo::getStateGrouped(
return {};
}
ensureDataMediaCreated();
return TextState(_parent, _data->uploading()
return TextState(_parent, (_spoiler && !_spoiler->revealed)
? _spoiler->link
: _data->uploading()
? _cancell
: _dataMedia->loaded()
? _openl
@ -904,6 +965,13 @@ TextForMimeData Photo::selectedText(TextSelection selection) const {
return _caption.toTextForMimeData(selection);
}
void Photo::hideSpoilers() {
_caption.setSpoilerRevealed(false, anim::type::instant);
if (_spoiler) {
_spoiler->revealed = false;
}
}
bool Photo::needsBubble() const {
if (!_caption.isEmpty()) {
return true;

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/media/history_view_file.h"
class Image;
enum class ImageRoundRadius;
namespace Data {
@ -31,7 +32,8 @@ public:
Photo(
not_null<Element*> parent,
not_null<HistoryItem*> realParent,
not_null<PhotoData*> photo);
not_null<PhotoData*> photo,
bool spoiler);
Photo(
not_null<Element*> parent,
not_null<PeerData*> chat,
@ -81,6 +83,7 @@ public:
TextWithEntities getCaption() const override {
return _caption.toTextWithEntities();
}
void hideSpoilers() override;
bool needsBubble() const override;
bool customInfoLayout() const override {
return _caption.isEmpty();
@ -130,6 +133,12 @@ private:
std::optional<Ui::BubbleRounding> rounding) const;
void validateUserpicImageCache(QSize size, bool forum) const;
[[nodiscard]] QImage prepareImageCache(QSize outer) const;
void validateSpoilerImageCache(
QSize outer,
std::optional<Ui::BubbleRounding> rounding) const;
[[nodiscard]] QImage prepareImageCacheWithLarge(
QSize outer,
Image *large) const;
bool videoAutoplayEnabled() const;
void setStreamed(std::unique_ptr<Streamed> value);
@ -148,6 +157,7 @@ private:
Ui::Text::String _caption;
mutable std::shared_ptr<Data::PhotoMedia> _dataMedia;
mutable std::unique_ptr<Streamed> _streamed;
const std::unique_ptr<MediaSpoiler> _spoiler;
mutable QImage _imageCache;
mutable std::optional<Ui::BubbleRounding> _imageCacheRounding;
int _serviceWidth : 30 = 0;

View file

@ -21,7 +21,7 @@ namespace HistoryView {
class Message;
class Poll : public Media, public base::has_weak_ptr {
class Poll final : public Media {
public:
Poll(
not_null<Element*> parent,

View file

@ -60,16 +60,19 @@ std::vector<std::unique_ptr<Data::Media>> PrepareCollageMedia(
auto result = std::vector<std::unique_ptr<Data::Media>>();
result.reserve(data.items.size());
for (const auto &item : data.items) {
const auto spoiler = false;
if (const auto document = std::get_if<DocumentData*>(&item)) {
const auto skipPremiumEffect = false;
result.push_back(std::make_unique<Data::MediaFile>(
parent,
*document,
skipPremiumEffect));
skipPremiumEffect,
spoiler));
} else if (const auto photo = std::get_if<PhotoData*>(&item)) {
result.push_back(std::make_unique<Data::MediaPhoto>(
parent,
*photo));
*photo,
spoiler));
} else {
return {};
}

@ -1 +1 @@
Subproject commit f339dd377100beaef79af8512c67a3c16f950c3f
Subproject commit 64d277891403ba3275b47d6ccab849322670cf99