Support spoilers in chats list media previews.

This commit is contained in:
John Preston 2022-12-28 14:07:38 +04:00
parent 17f40d6a1f
commit b334a1f4fd
4 changed files with 72 additions and 24 deletions

View file

@ -101,7 +101,8 @@ using ItemPreviewImage = HistoryView::ItemPreviewImage;
[[nodiscard]] QImage PreparePreviewImage( [[nodiscard]] QImage PreparePreviewImage(
not_null<const Image*> image, not_null<const Image*> image,
ImageRoundRadius radius = ImageRoundRadius::Small) { ImageRoundRadius radius,
bool spoiler) {
const auto original = image->original(); const auto original = image->original();
if (original.width() * 10 < original.height() if (original.width() * 10 < original.height()
|| original.height() * 10 < original.width()) { || original.height() * 10 < original.width()) {
@ -119,6 +120,11 @@ using ItemPreviewImage = HistoryView::ItemPreviewImage;
size, size,
size size
).convertToFormat(QImage::Format_ARGB32_Premultiplied); ).convertToFormat(QImage::Format_ARGB32_Premultiplied);
if (spoiler) {
square = Images::BlurLargeImage(
std::move(square),
style::ConvertScale(3) * factor);
}
if (radius == ImageRoundRadius::Small) { if (radius == ImageRoundRadius::Small) {
struct Cache { struct Cache {
base::flat_map<int, std::array<QImage, 4>> all; base::flat_map<int, std::array<QImage, 4>> all;
@ -146,27 +152,33 @@ using ItemPreviewImage = HistoryView::ItemPreviewImage;
return square; return square;
} }
template <typename MediaType>
[[nodiscard]] uint64 CountCacheKey(not_null<MediaType*> data, bool spoiler) {
return (reinterpret_cast<uint64>(data.get()) & ~1) | (spoiler ? 1 : 0);
}
[[nodiscard]] ItemPreviewImage PreparePhotoPreview( [[nodiscard]] ItemPreviewImage PreparePhotoPreview(
not_null<const HistoryItem*> item, not_null<const HistoryItem*> item,
const std::shared_ptr<PhotoMedia> &media, const std::shared_ptr<PhotoMedia> &media,
ImageRoundRadius radius) { ImageRoundRadius radius,
bool spoiler) {
const auto photo = media->owner(); const auto photo = media->owner();
const auto readyCacheKey = reinterpret_cast<uint64>(photo.get()); const auto counted = CountCacheKey(photo, spoiler);
if (const auto small = media->image(PhotoSize::Small)) { if (const auto small = media->image(PhotoSize::Small)) {
return { PreparePreviewImage(small, radius), readyCacheKey }; return { PreparePreviewImage(small, radius, spoiler), counted };
} else if (const auto thumbnail = media->image(PhotoSize::Thumbnail)) { } else if (const auto thumbnail = media->image(PhotoSize::Thumbnail)) {
return { PreparePreviewImage(thumbnail, radius), readyCacheKey }; return { PreparePreviewImage(thumbnail, radius, spoiler), counted };
} else if (const auto large = media->image(PhotoSize::Large)) { } else if (const auto large = media->image(PhotoSize::Large)) {
return { PreparePreviewImage(large, radius), readyCacheKey }; return { PreparePreviewImage(large, radius, spoiler), counted };
} }
const auto allowedToDownload = media->autoLoadThumbnailAllowed( const auto allowedToDownload = media->autoLoadThumbnailAllowed(
item->history()->peer); item->history()->peer);
const auto cacheKey = allowedToDownload ? 0 : readyCacheKey; const auto cacheKey = allowedToDownload ? 0 : counted;
if (allowedToDownload) { if (allowedToDownload) {
media->owner()->load(PhotoSize::Small, item->fullId()); media->owner()->load(PhotoSize::Small, item->fullId());
} }
if (const auto blurred = media->thumbnailInline()) { if (const auto blurred = media->thumbnailInline()) {
return { PreparePreviewImage(blurred, radius), cacheKey }; return { PreparePreviewImage(blurred, radius, spoiler), cacheKey };
} }
return { QImage(), allowedToDownload ? 0 : cacheKey }; return { QImage(), allowedToDownload ? 0 : cacheKey };
} }
@ -174,17 +186,21 @@ using ItemPreviewImage = HistoryView::ItemPreviewImage;
[[nodiscard]] ItemPreviewImage PrepareFilePreviewImage( [[nodiscard]] ItemPreviewImage PrepareFilePreviewImage(
not_null<const HistoryItem*> item, not_null<const HistoryItem*> item,
const std::shared_ptr<DocumentMedia> &media, const std::shared_ptr<DocumentMedia> &media,
ImageRoundRadius radius) { ImageRoundRadius radius,
bool spoiler) {
Expects(media->owner()->hasThumbnail()); Expects(media->owner()->hasThumbnail());
const auto document = media->owner(); const auto document = media->owner();
const auto readyCacheKey = reinterpret_cast<uint64>(document.get()); const auto readyCacheKey = CountCacheKey(document, spoiler);
if (const auto thumbnail = media->thumbnail()) { if (const auto thumbnail = media->thumbnail()) {
return { PreparePreviewImage(thumbnail, radius), readyCacheKey }; return {
PreparePreviewImage(thumbnail, radius, spoiler),
readyCacheKey,
};
} }
document->loadThumbnail(item->fullId()); document->loadThumbnail(item->fullId());
if (const auto blurred = media->thumbnailInline()) { if (const auto blurred = media->thumbnailInline()) {
return { PreparePreviewImage(blurred, radius), 0 }; return { PreparePreviewImage(blurred, radius, spoiler), 0 };
} }
return { QImage(), 0 }; return { QImage(), 0 };
} }
@ -204,8 +220,9 @@ using ItemPreviewImage = HistoryView::ItemPreviewImage;
[[nodiscard]] ItemPreviewImage PrepareFilePreview( [[nodiscard]] ItemPreviewImage PrepareFilePreview(
not_null<const HistoryItem*> item, not_null<const HistoryItem*> item,
const std::shared_ptr<DocumentMedia> &media, const std::shared_ptr<DocumentMedia> &media,
ImageRoundRadius radius) { ImageRoundRadius radius,
auto result = PrepareFilePreviewImage(item, media, radius); bool spoiler) {
auto result = PrepareFilePreviewImage(item, media, radius, spoiler);
const auto document = media->owner(); const auto document = media->owner();
if (!result.data.isNull() if (!result.data.isNull()
&& (document->isVideoFile() || document->isVideoMessage())) { && (document->isVideoFile() || document->isVideoMessage())) {
@ -223,13 +240,14 @@ using ItemPreviewImage = HistoryView::ItemPreviewImage;
template <typename MediaType> template <typename MediaType>
[[nodiscard]] ItemPreviewImage FindCachedPreview( [[nodiscard]] ItemPreviewImage FindCachedPreview(
const std::vector<ItemPreviewImage> *existing, const std::vector<ItemPreviewImage> *existing,
not_null<MediaType*> data) { not_null<MediaType*> data,
bool spoiler) {
if (!existing) { if (!existing) {
return {}; return {};
} }
const auto i = ranges::find( const auto i = ranges::find(
*existing, *existing,
reinterpret_cast<uint64>(data.get()), CountCacheKey(data, spoiler),
&ItemPreviewImage::cacheKey); &ItemPreviewImage::cacheKey);
return (i != end(*existing)) ? *i : ItemPreviewImage(); return (i != end(*existing)) ? *i : ItemPreviewImage();
} }
@ -619,14 +637,18 @@ ItemPreview MediaPhoto::toPreview(ToPreviewOptions options) const {
} }
auto images = std::vector<ItemPreviewImage>(); auto images = std::vector<ItemPreviewImage>();
auto context = std::any(); auto context = std::any();
if (auto cached = FindCachedPreview(options.existing, _photo)) { if (auto found = FindCachedPreview(options.existing, _photo, _spoiler)) {
images.push_back(std::move(cached)); images.push_back(std::move(found));
} else { } else {
const auto media = _photo->createMediaView(); const auto media = _photo->createMediaView();
const auto radius = _chat const auto radius = _chat
? ImageRoundRadius::Ellipse ? ImageRoundRadius::Ellipse
: ImageRoundRadius::Small; : ImageRoundRadius::Small;
if (auto prepared = PreparePhotoPreview(parent(), media, radius) if (auto prepared = PreparePhotoPreview(
parent(),
media,
radius,
_spoiler)
; prepared || !prepared.cacheKey) { ; prepared || !prepared.cacheKey) {
images.push_back(std::move(prepared)); images.push_back(std::move(prepared));
if (!prepared.cacheKey) { if (!prepared.cacheKey) {
@ -848,14 +870,19 @@ ItemPreview MediaFile::toPreview(ToPreviewOptions options) const {
} }
auto images = std::vector<ItemPreviewImage>(); auto images = std::vector<ItemPreviewImage>();
auto context = std::any(); auto context = std::any();
if (auto cached = FindCachedPreview(options.existing, _document)) { const auto existing = options.existing;
images.push_back(std::move(cached)); if (auto found = FindCachedPreview(existing, _document, _spoiler)) {
images.push_back(std::move(found));
} else if (TryFilePreview(_document)) { } else if (TryFilePreview(_document)) {
const auto media = _document->createMediaView(); const auto media = _document->createMediaView();
const auto radius = _document->isVideoMessage() const auto radius = _document->isVideoMessage()
? ImageRoundRadius::Ellipse ? ImageRoundRadius::Ellipse
: ImageRoundRadius::Small; : ImageRoundRadius::Small;
if (auto prepared = PrepareFilePreview(parent(), media, radius) if (auto prepared = PrepareFilePreview(
parent(),
media,
radius,
_spoiler)
; prepared || !prepared.cacheKey) { ; prepared || !prepared.cacheKey) {
images.push_back(std::move(prepared)); images.push_back(std::move(prepared));
if (!prepared.cacheKey) { if (!prepared.cacheKey) {

View file

@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h" #include "main/main_session.h"
#include "dialogs/ui/dialogs_layout.h" #include "dialogs/ui/dialogs_layout.h"
#include "dialogs/ui/dialogs_topics_view.h" #include "dialogs/ui/dialogs_topics_view.h"
#include "ui/effects/spoiler_mess.h"
#include "ui/text/text_options.h" #include "ui/text/text_options.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/image/image.h" #include "ui/image/image.h"
@ -185,6 +186,11 @@ void MessageView::prepare(
context); context);
_textCachedFor = item; _textCachedFor = item;
_imagesCache = std::move(preview.images); _imagesCache = std::move(preview.images);
if (!ranges::any_of(_imagesCache, &ItemPreviewImage::hasSpoiler)) {
_spoiler = nullptr;
} else if (!_spoiler) {
_spoiler = std::make_unique<SpoilerAnimation>(customEmojiRepaint);
}
if (preview.loadingContext.has_value()) { if (preview.loadingContext.has_value()) {
if (!_loadingContext) { if (!_loadingContext) {
_loadingContext = std::make_unique<LoadingContext>(); _loadingContext = std::make_unique<LoadingContext>();
@ -302,10 +308,19 @@ void MessageView::paint(
if (rect.width() < st::dialogsMiniPreview) { if (rect.width() < st::dialogsMiniPreview) {
break; break;
} }
p.drawImage( const auto mini = QRect(
rect.x(), rect.x(),
rect.y() + st::dialogsMiniPreviewTop, rect.y() + st::dialogsMiniPreviewTop,
image.data); st::dialogsMiniPreview,
st::dialogsMiniPreview);
if (!image.data.isNull()) {
p.drawImage(mini, image.data);
if (image.hasSpoiler()) {
const auto frame = DefaultImageSpoiler().frame(
_spoiler->index(context.now, context.paused));
FillSpoilerRect(p, mini, frame);
}
}
rect.setLeft(rect.x() rect.setLeft(rect.x()
+ st::dialogsMiniPreview + st::dialogsMiniPreview
+ st::dialogsMiniPreviewSkip); + st::dialogsMiniPreviewSkip);

View file

@ -18,6 +18,7 @@ struct DialogRow;
} // namespace style } // namespace style
namespace Ui { namespace Ui {
class SpoilerAnimation;
} // namespace Ui } // namespace Ui
namespace Data { namespace Data {
@ -88,6 +89,7 @@ private:
mutable std::unique_ptr<TopicsView> _topics; mutable std::unique_ptr<TopicsView> _topics;
mutable Text::String _textCache; mutable Text::String _textCache;
mutable std::vector<ItemPreviewImage> _imagesCache; mutable std::vector<ItemPreviewImage> _imagesCache;
mutable std::unique_ptr<SpoilerAnimation> _spoiler;
mutable std::unique_ptr<LoadingContext> _loadingContext; mutable std::unique_ptr<LoadingContext> _loadingContext;
}; };

View file

@ -13,6 +13,10 @@ struct ItemPreviewImage {
QImage data; QImage data;
uint64 cacheKey = 0; uint64 cacheKey = 0;
[[nodiscard]] bool hasSpoiler() const {
return (cacheKey & 1);
}
explicit operator bool() const { explicit operator bool() const {
return !data.isNull(); return !data.isNull();
} }