mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-16 14:17:12 +02:00
Support displaying of photo spoilers.
This commit is contained in:
parent
ae819eb1a6
commit
25746d195c
26 changed files with 315 additions and 109 deletions
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -431,6 +431,9 @@ void Element::hideSpoilers() {
|
|||
if (_text.hasSpoilers()) {
|
||||
_text.setSpoilerRevealed(false, anim::type::instant);
|
||||
}
|
||||
if (_media) {
|
||||
_media->hideSpoilers();
|
||||
}
|
||||
}
|
||||
|
||||
void Element::customEmojiRepaint() {
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -54,6 +54,7 @@ public:
|
|||
}
|
||||
|
||||
TextWithEntities getCaption() const override;
|
||||
void hideSpoilers() override;
|
||||
bool needsBubble() const override {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -67,6 +67,7 @@ public:
|
|||
bool pressed) override;
|
||||
|
||||
TextWithEntities getCaption() const override;
|
||||
void hideSpoilers() override;
|
||||
Storage::SharedMediaTypesMask sharedMediaTypes() const override;
|
||||
|
||||
bool overrideEditedDate() const override {
|
||||
|
|
|
@ -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"
|
||||
|
|
@ -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
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
Loading…
Add table
Reference in a new issue