From 4b85c9e2df4bdcf0f40534c2eddb3e6ad8a2133d Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 22 Jul 2025 12:06:00 +0400 Subject: [PATCH] Add spoilers to shared GIFs. --- .../info/media/info_media_provider.cpp | 11 ++ .../SourceFiles/overview/overview_layout.cpp | 154 ++++++++++++++++-- .../SourceFiles/overview/overview_layout.h | 18 +- 3 files changed, 164 insertions(+), 19 deletions(-) diff --git a/Telegram/SourceFiles/info/media/info_media_provider.cpp b/Telegram/SourceFiles/info/media/info_media_provider.cpp index 88f47bc755..14553f7958 100644 --- a/Telegram/SourceFiles/info/media/info_media_provider.cpp +++ b/Telegram/SourceFiles/info/media/info_media_provider.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/media/info_media_list_section.h" #include "info/info_controller.h" #include "layout/layout_selection.h" +#include "main/main_app_config.h" #include "main/main_session.h" #include "lang/lang_keys.h" #include "history/history.h" @@ -59,6 +60,16 @@ Provider::Provider(not_null controller) layout.second.item->invalidateCache(); } }, _lifetime); + + _controller->session().appConfig().ignoredRestrictionReasonsChanges( + ) | rpl::start_with_next([=](std::vector &&changed) { + const auto sensitive = Data::UnavailableReason::Sensitive(); + if (ranges::contains(changed, sensitive.reason)) { + for (auto &[id, layout] : _layouts) { + layout.item->maybeClearSensitiveSpoiler(); + } + } + }, _lifetime); } Type Provider::type() { diff --git a/Telegram/SourceFiles/overview/overview_layout.cpp b/Telegram/SourceFiles/overview/overview_layout.cpp index af3317ca02..c80ffec3e5 100644 --- a/Telegram/SourceFiles/overview/overview_layout.cpp +++ b/Telegram/SourceFiles/overview/overview_layout.cpp @@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_item_components.h" #include "history/history_item_helpers.h" #include "history/view/history_view_cursor_state.h" +#include "history/view/media/history_view_media_common.h" #include "history/view/media/history_view_document.h" // DrawThumbnailAsSongCover #include "base/unixtime.h" #include "ui/effects/round_checkbox.h" @@ -98,6 +99,27 @@ constexpr auto kStoryRatio = 1.46; } } +void PaintSensitiveTag(Painter &p, QRect r) { + auto text = Ui::Text::String(); + text.setText( + st::semiboldTextStyle, + tr::lng_sensitive_tag(tr::now)); + const auto width = text.maxWidth(); + const auto inner = QRect(0, 0, width, text.minHeight()); + const auto outer = style::centerrect(r, inner.marginsAdded(st::paidTagPadding)); + const auto size = outer.size(); + const auto radius = std::min(size.width(), size.height()) / 2; + auto hq = PainterHighQualityEnabler(p); + + p.setPen(Qt::NoPen); + p.setBrush(st::radialBg); + p.drawRoundedRect(outer, radius, radius); + p.setPen(st::radialFg); + text.draw(p, { + .position = outer.marginsRemoved(st::paidTagPadding).topLeft(), + }); +} + } // namespace class Checkbox { @@ -327,18 +349,21 @@ Photo::Photo( MediaOptions options) : ItemBase(delegate, parent) , _data(photo) -, _link(std::make_shared( - photo, - crl::guard(this, [=](FullMsgId id) { - clearSpoiler(); - delegate->openPhoto(photo, id); - }), - parent->fullId())) -, _spoiler(options.spoiler ? std::make_unique([=] { - delegate->repaintItem(this); -}) : nullptr) +, _spoiler((options.spoiler || parent->isMediaSensitive()) + ? std::make_unique([=] { + delegate->repaintItem(this); + }) + : nullptr) +, _sensitiveSpoiler(parent->isMediaSensitive() ? 1 : 0) , _pinned(options.pinned) -, _story(options.story) { +, _story(options.story) +, _link(_sensitiveSpoiler + ? HistoryView::MakeSensitiveMediaLink( + std::make_shared(crl::guard(this, [=] { + maybeClearSensitiveSpoiler(); + })), + parent) + : makeOpenPhotoHandler()) { if (_data->inlineThumbnailBytes().isEmpty() && (_data->hasExact(Data::PhotoSize::Small) || _data->hasExact(Data::PhotoSize::Thumbnail))) { @@ -348,6 +373,16 @@ Photo::Photo( Photo::~Photo() = default; +ClickHandlerPtr Photo::makeOpenPhotoHandler() { + return std::make_shared( + _data, + crl::guard(this, [=](FullMsgId id) { + clearSpoiler(); + delegate()->openPhoto(_data, id); + }), + parent()->fullId()); +} + void Photo::initDimensions() { _maxw = 2 * st::overviewPhotoMinSize; _minh = _story ? qRound(_maxw * kStoryRatio) : _maxw; @@ -401,6 +436,10 @@ void Photo::paint(Painter &p, const QRect &clip, TextSelection selection, const QRect(0, 0, _width, _height), Ui::DefaultImageSpoiler().frame( _spoiler->index(context->ms, paused))); + + if (_sensitiveSpoiler) { + PaintSensitiveTag(p, QRect(0, 0, _width, _height)); + } } if (selected) { @@ -453,11 +492,19 @@ void Photo::ensureDataMediaCreated() const { void Photo::clearSpoiler() { if (_spoiler) { _spoiler = nullptr; + _sensitiveSpoiler = false; _pix = QPixmap(); delegate()->repaintItem(this); } } +void Photo::maybeClearSensitiveSpoiler() { + if (_sensitiveSpoiler) { + clearSpoiler(); + _link = makeOpenPhotoHandler(); + } +} + void Photo::itemDataChanged() { const auto pinned = parent()->isPinned(); if (_pinned != pinned) { @@ -488,12 +535,23 @@ Video::Video( , _data(video) , _videoCover(LookupVideoCover(video, parent)) , _duration(Ui::FormatDurationText(_data->duration() / 1000)) -, _spoiler(options.spoiler ? std::make_unique([=] { - delegate->repaintItem(this); -}) : nullptr) +, _spoiler((options.spoiler || parent->isMediaSensitive()) + ? std::make_unique([=] { + delegate->repaintItem(this); + }) + : nullptr) +, _sensitiveSpoiler(parent->isMediaSensitive() ? 1 : 0) , _pinned(options.pinned) , _story(options.story) { setDocumentLinks(_data); + if (_sensitiveSpoiler) { + _openl = HistoryView::MakeSensitiveMediaLink( + std::make_shared(crl::guard(this, [=] { + clearSpoiler(); + setDocumentLinks(_data); + })), + parent); + } if (!_videoCover) { _data->loadThumbnail(parent->fullId()); } else if (_videoCover->inlineThumbnailBytes().isEmpty() @@ -574,6 +632,10 @@ void Video::paint(Painter &p, const QRect &clip, TextSelection selection, const QRect(0, 0, _width, _height), Ui::DefaultImageSpoiler().frame( _spoiler->index(context->ms, paused))); + + if (_sensitiveSpoiler) { + PaintSensitiveTag(p, QRect(0, 0, _width, _height)); + } } if (selected) { @@ -660,11 +722,19 @@ void Video::ensureDataMediaCreated() const { void Video::clearSpoiler() { if (_spoiler) { _spoiler = nullptr; + _sensitiveSpoiler = false; _pix = QPixmap(); delegate()->repaintItem(this); } } +void Video::maybeClearSensitiveSpoiler() { + if (_sensitiveSpoiler) { + clearSpoiler(); + setDocumentLinks(_data); + } +} + void Video::itemDataChanged() { const auto pinned = parent()->isPinned(); if (_pinned != pinned) { @@ -700,7 +770,9 @@ TextState Video::getState( StateRequest request) const { if (hasPoint(point)) { ensureDataMediaCreated(); - const auto link = (_data->loading() || _data->uploading()) + const auto link = _sensitiveSpoiler + ? _openl + : (_data->loading() || _data->uploading()) ? _cancell : (dataLoaded() || _dataMedia->canBePlayed(parent())) ? _openl @@ -2036,8 +2108,22 @@ Gif::Gif( not_null parent, not_null gif) : RadialProgressItem(delegate, parent) -, _data(gif) { +, _data(gif) +, _spoiler(parent->isMediaSensitive() + ? std::make_unique([=] { + delegate->repaintItem(this); + }) + : nullptr) +, _sensitiveSpoiler(parent->isMediaSensitive() ? 1 : 0) { setDocumentLinks(_data, true); + if (_sensitiveSpoiler) { + _openl = HistoryView::MakeSensitiveMediaLink( + std::make_shared(crl::guard(this, [=] { + clearSpoiler(); + setDocumentLinks(_data, true); + })), + parent); + } _data->loadThumbnail(parent->fullId()); } @@ -2140,6 +2226,23 @@ void Gif::clipCallback(Media::Clip::Notification notification) { } } +void Gif::clearSpoiler() { + if (_spoiler) { + _spoiler = nullptr; + _sensitiveSpoiler = false; + _thumb = QImage(); + _thumbGood = false; + delegate()->repaintItem(this); + } +} + +void Gif::maybeClearSensitiveSpoiler() { + if (_sensitiveSpoiler) { + clearSpoiler(); + setDocumentLinks(_data); + } +} + void Gif::validateThumbnail( Image *image, QSize size, @@ -2165,7 +2268,9 @@ void Gif::prepareThumbnail(QSize size, QSize frame) { Assert(document != nullptr); ensureDataMediaCreated(); - validateThumbnail(_dataMedia->thumbnail(), size, frame, true); + if (!_spoiler) { + validateThumbnail(_dataMedia->thumbnail(), size, frame, true); + } validateThumbnail(_dataMedia->thumbnailInline(), size, frame, false); } @@ -2194,7 +2299,7 @@ void Gif::paint( }); } - const auto animating = (_gif && _gif->started()); + const auto animating = !_spoiler && (_gif && _gif->started()); if (displayLoading) { ensureRadial(); if (!_radial->animating()) { @@ -2224,6 +2329,19 @@ void Gif::paint( } } + if (_spoiler) { + const auto paused = context->paused || On(PowerSaving::kChatSpoiler); + Ui::FillSpoilerRect( + p, + r, + Ui::DefaultImageSpoiler().frame( + _spoiler->index(context->ms, paused))); + + if (_sensitiveSpoiler) { + PaintSensitiveTag(p, r); + } + } + const auto selected = (selection == FullSelection); if (radial diff --git a/Telegram/SourceFiles/overview/overview_layout.h b/Telegram/SourceFiles/overview/overview_layout.h index ff0273d1d6..5edf2a6ac4 100644 --- a/Telegram/SourceFiles/overview/overview_layout.h +++ b/Telegram/SourceFiles/overview/overview_layout.h @@ -76,6 +76,9 @@ public: virtual void clearHeavyPart() { } + virtual void maybeClearSensitiveSpoiler() { + } + protected: [[nodiscard]] not_null parent() const { return _parent; @@ -210,21 +213,26 @@ public: void itemDataChanged() override; void clearHeavyPart() override; + void maybeClearSensitiveSpoiler() override; + private: void ensureDataMediaCreated() const; void setPixFrom(not_null image); + [[nodiscard]] ClickHandlerPtr makeOpenPhotoHandler(); void clearSpoiler(); const not_null _data; mutable std::shared_ptr _dataMedia; - ClickHandlerPtr _link; std::unique_ptr _spoiler; QPixmap _pix; bool _goodLoaded = false; + bool _sensitiveSpoiler = false; bool _pinned = false; bool _story = false; + ClickHandlerPtr _link; + }; class Gif final : public RadialProgressItem { @@ -249,6 +257,9 @@ public: void clearHeavyPart() override; void setPosition(int32 position) override; + void clearSpoiler() override; + void maybeClearSensitiveSpoiler() override; + protected: float64 dataProgress() const override; bool dataFinished() const override; @@ -279,9 +290,11 @@ private: const not_null _data; mutable std::shared_ptr _dataMedia; StatusText _status; + std::unique_ptr _spoiler; QImage _thumb; bool _thumbGood = false; + bool _sensitiveSpoiler = false; }; @@ -305,6 +318,8 @@ public: void clearHeavyPart() override; void clearSpoiler() override; + void maybeClearSensitiveSpoiler() override; + protected: float64 dataProgress() const override; bool dataFinished() const override; @@ -326,6 +341,7 @@ private: QPixmap _pix; bool _pixBlurred = true; + bool _sensitiveSpoiler = false; bool _pinned = false; bool _story = false;