diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 03d509811..a179f9d1f 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -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 diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 21d431f59..cc878a6c7 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -530,9 +530,11 @@ ItemPreview Media::toGroupPreview( MediaPhoto::MediaPhoto( not_null parent, - not_null photo) + not_null 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 MediaPhoto::clone(not_null parent) { return _chat ? std::make_unique(parent, _chat, _photo) - : std::make_unique(parent, _photo); + : std::make_unique(parent, _photo, _spoiler); } PhotoData *MediaPhoto::photo() const { @@ -722,17 +724,20 @@ std::unique_ptr MediaPhoto::createView( return std::make_unique( message, realParent, - _photo); + _photo, + _spoiler); } MediaFile::MediaFile( not_null parent, not_null 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 MediaFile::clone(not_null parent) { return std::make_unique( parent, _document, - !_document->session().premium()); + !_document->session().premium(), + _spoiler); } DocumentData *MediaFile::document() const { @@ -1096,13 +1102,15 @@ std::unique_ptr MediaFile::createView( return std::make_unique( message, realParent, - _document); + _document, + _spoiler); } } else if (_document->isAnimation() || _document->isVideoFile()) { return std::make_unique( message, realParent, - _document); + _document, + _spoiler); } else if (_document->isTheme() && _document->hasThumbnail()) { return std::make_unique( message, diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index afcc725c3..0b612a799 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -165,7 +165,8 @@ class MediaPhoto final : public Media { public: MediaPhoto( not_null parent, - not_null photo); + not_null photo, + bool spoiler); MediaPhoto( not_null parent, not_null chat, @@ -200,6 +201,7 @@ public: private: not_null _photo; PeerData *_chat = nullptr; + bool _spoiler = false; }; @@ -208,7 +210,8 @@ public: MediaFile( not_null parent, not_null document, - bool skipPremiumEffect); + bool skipPremiumEffect, + bool spoiler); ~MediaFile(); std::unique_ptr clone(not_null parent) override; @@ -242,6 +245,7 @@ private: not_null _document; QString _emoji; bool _skipPremiumEffect = false; + bool _spoiler = false; }; diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index d9d48dde4..0894b4423 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -184,7 +184,8 @@ std::unique_ptr HistoryItem::CreateMedia( return photo->match([&](const MTPDphoto &photo) -> Result { return std::make_unique( 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 HistoryItem::CreateMedia( return std::make_unique( 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( this, document, - skipPremiumEffect); + skipPremiumEffect, + spoiler); setText(caption); } @@ -545,7 +549,8 @@ HistoryItem::HistoryItem( postAuthor, std::move(markup)); - _media = std::make_unique(this, photo); + const auto spoiler = false; + _media = std::make_unique(this, photo, spoiler); setText(caption); } diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 73a6d572a..b7f60e954 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -431,6 +431,9 @@ void Element::hideSpoilers() { if (_text.hasSpoilers()) { _text.setSpoilerRevealed(false, anim::type::instant); } + if (_media) { + _media->hideSpoilers(); + } } void Element::customEmojiRepaint() { diff --git a/Telegram/SourceFiles/history/view/history_view_spoiler_click_handler.cpp b/Telegram/SourceFiles/history/view/history_view_spoiler_click_handler.cpp index 889bf0c64..eb72db0f2 100644 --- a/Telegram/SourceFiles/history/view/history_view_spoiler_click_handler.cpp +++ b/Telegram/SourceFiles/history/view/history_view_spoiler_click_handler.cpp @@ -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(); 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; }); } } diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_document.cpp index c2c7f622d..69aaf26fd 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_document.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_document.cpp @@ -1454,6 +1454,12 @@ TextWithEntities Document::getCaption() const { return TextWithEntities(); } +void Document::hideSpoilers() { + if (const auto captioned = Get()) { + captioned->caption.setSpoilerRevealed(false, anim::type::instant); + } +} + Ui::Text::String Document::createCaption() { return File::createCaption(_realParent); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.h b/Telegram/SourceFiles/history/view/media/history_view_document.h index 01bdabe30..08f0c3ada 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_document.h +++ b/Telegram/SourceFiles/history/view/media/history_view_document.h @@ -54,6 +54,7 @@ public: } TextWithEntities getCaption() const override; + void hideSpoilers() override; bool needsBubble() const override { return true; } diff --git a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp index 697573c51..7ede5a9d6 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp @@ -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 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 rounding, - const PaintContext &context) const { - if (!_animation) { - _animation = std::make_unique([=] { - _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; diff --git a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.h b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.h index 00e66e959..8bec02154 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.h +++ b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.h @@ -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 rounding, - const PaintContext &context) const; - const not_null _invoice; - ClickHandlerPtr _link; Ui::Text::String _caption; - mutable std::unique_ptr _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 _imageCacheRounding; mutable bool _imageCacheInvalid = false; }; diff --git a/Telegram/SourceFiles/history/view/media/history_view_file.h b/Telegram/SourceFiles/history/view/media/history_view_file.h index a5b7c6ca6..dcdf40063 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_file.h +++ b/Telegram/SourceFiles/history/view/media/history_view_file.h @@ -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 parent, diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index 597e3a1fc..632a9aa88 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -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 parent, not_null realParent, - not_null document) + not_null document, + bool spoiler) : File(parent, realParent) , _data(document) , _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) +, _spoiler(spoiler ? std::make_unique() : 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(); diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.h b/Telegram/SourceFiles/history/view/media/history_view_gif.h index ad3e47aac..54b30456a 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.h +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.h @@ -44,7 +44,8 @@ public: Gif( not_null parent, not_null realParent, - not_null document); + not_null 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 _data; Ui::Text::String _caption; std::unique_ptr _streamed; + const std::unique_ptr _spoiler; mutable std::unique_ptr _transcribe; mutable std::shared_ptr _dataMedia; mutable std::unique_ptr _videoThumbnailFrame; diff --git a/Telegram/SourceFiles/history/view/media/history_view_invoice.cpp b/Telegram/SourceFiles/history/view/media/history_view_invoice.cpp index a57bbf0f3..d1ddb7501 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_invoice.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_invoice.cpp @@ -34,10 +34,12 @@ Invoice::Invoice( void Invoice::fillFromData(not_null invoice) { if (invoice->photo) { + const auto spoiler = false; _attach = std::make_unique( _parent, _parent->data(), - invoice->photo); + invoice->photo, + spoiler); } else { _attach = nullptr; } diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.cpp b/Telegram/SourceFiles/history/view/media/history_view_media.cpp index a909d4112..e20b8e76e 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media.cpp @@ -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 spoiler, + QRect rect, + const PaintContext &context) const { + if (!spoiler->animation) { + spoiler->animation = std::make_unique([=] { + _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 spoiler) { + const auto weak = base::make_weak(this); + spoiler->link = std::make_shared([=]( + 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); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.h b/Telegram/SourceFiles/history/view/media/history_view_media.h index e06f0eb2d..346d1e685 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media.h @@ -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 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 rounding, // nullopt if in WebPage. const PaintContext &context) const; + void fillImageSpoiler( + QPainter &p, + not_null spoiler, + QRect rect, + const PaintContext &context) const; + void createSpoilerLink(not_null spoiler); void repaint() const; diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp index 094a8af58..273262e8d 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp @@ -62,6 +62,7 @@ std::unique_ptr CreateAttach( if (!collage.empty()) { return std::make_unique(parent, collage); } else if (document) { + const auto spoiler = false; if (document->sticker()) { const auto skipPremiumEffect = true; return std::make_unique( @@ -71,7 +72,11 @@ std::unique_ptr CreateAttach( document, skipPremiumEffect)); } else if (document->isAnimation() || document->isVideoFile()) { - return std::make_unique(parent, parent->data(), document); + return std::make_unique( + parent, + parent->data(), + document, + spoiler); } else if (document->isWallPaper() || document->isTheme()) { return std::make_unique( parent, @@ -80,10 +85,12 @@ std::unique_ptr CreateAttach( } return std::make_unique(parent, parent->data(), document); } else if (photo) { + const auto spoiler = false; return std::make_unique( parent, parent->data(), - photo); + photo, + spoiler); } else if (const auto params = ThemeDocument::ParamsFromUrl(webpageUrl)) { return std::make_unique(parent, nullptr, params); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp index a4310c817..d9212bd54 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp @@ -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(); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h index 3ad6974b6..c66864063 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h @@ -67,6 +67,7 @@ public: bool pressed) override; TextWithEntities getCaption() const override; + void hideSpoilers() override; Storage::SharedMediaTypesMask sharedMediaTypes() const override; bool overrideEditedDate() const override { diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_spoiler.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_spoiler.cpp new file mode 100644 index 000000000..cdf0ff3dc --- /dev/null +++ b/Telegram/SourceFiles/history/view/media/history_view_media_spoiler.cpp @@ -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" + diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_spoiler.h b/Telegram/SourceFiles/history/view/media/history_view_media_spoiler.h new file mode 100644 index 000000000..605b3d4c8 --- /dev/null +++ b/Telegram/SourceFiles/history/view/media/history_view_media_spoiler.h @@ -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 animation; + QImage cornerCache; + QImage background; + std::optional backgroundRounding; + Ui::Animations::Simple revealAnimation; + bool revealed = false; +}; + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index eeb21548b..b40c6bb13 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -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 parent, not_null realParent, - not_null photo) + not_null 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() : 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 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; diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.h b/Telegram/SourceFiles/history/view/media/history_view_photo.h index 0b7e47462..6ba555fa1 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.h +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.h @@ -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 parent, not_null realParent, - not_null photo); + not_null photo, + bool spoiler); Photo( not_null parent, not_null 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 rounding) const; void validateUserpicImageCache(QSize size, bool forum) const; [[nodiscard]] QImage prepareImageCache(QSize outer) const; + void validateSpoilerImageCache( + QSize outer, + std::optional rounding) const; + [[nodiscard]] QImage prepareImageCacheWithLarge( + QSize outer, + Image *large) const; bool videoAutoplayEnabled() const; void setStreamed(std::unique_ptr value); @@ -148,6 +157,7 @@ private: Ui::Text::String _caption; mutable std::shared_ptr _dataMedia; mutable std::unique_ptr _streamed; + const std::unique_ptr _spoiler; mutable QImage _imageCache; mutable std::optional _imageCacheRounding; int _serviceWidth : 30 = 0; diff --git a/Telegram/SourceFiles/history/view/media/history_view_poll.h b/Telegram/SourceFiles/history/view/media/history_view_poll.h index d32ae664b..ea919b539 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_poll.h +++ b/Telegram/SourceFiles/history/view/media/history_view_poll.h @@ -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 parent, diff --git a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp index d66e00c1c..b75cdd096 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp @@ -60,16 +60,19 @@ std::vector> PrepareCollageMedia( auto result = std::vector>(); result.reserve(data.items.size()); for (const auto &item : data.items) { + const auto spoiler = false; if (const auto document = std::get_if(&item)) { const auto skipPremiumEffect = false; result.push_back(std::make_unique( parent, *document, - skipPremiumEffect)); + skipPremiumEffect, + spoiler)); } else if (const auto photo = std::get_if(&item)) { result.push_back(std::make_unique( parent, - *photo)); + *photo, + spoiler)); } else { return {}; } diff --git a/Telegram/lib_ui b/Telegram/lib_ui index f339dd377..64d277891 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit f339dd377100beaef79af8512c67a3c16f950c3f +Subproject commit 64d277891403ba3275b47d6ccab849322670cf99