mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Generate mini-previews for photos and files.
This commit is contained in:
parent
8c21fad642
commit
792b9090a7
7 changed files with 226 additions and 14 deletions
|
@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/view/media/history_view_theme_document.h"
|
#include "history/view/media/history_view_theme_document.h"
|
||||||
#include "history/view/media/history_view_slot_machine.h"
|
#include "history/view/media/history_view_slot_machine.h"
|
||||||
#include "history/view/media/history_view_dice.h"
|
#include "history/view/media/history_view_dice.h"
|
||||||
|
#include "dialogs/ui/dialogs_message_view.h"
|
||||||
#include "ui/image/image.h"
|
#include "ui/image/image.h"
|
||||||
#include "ui/text/format_song_document_name.h"
|
#include "ui/text/format_song_document_name.h"
|
||||||
#include "ui/text/format_values.h"
|
#include "ui/text/format_values.h"
|
||||||
|
@ -37,18 +38,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "storage/localstorage.h"
|
#include "storage/localstorage.h"
|
||||||
#include "chat_helpers/stickers_dice_pack.h" // Stickers::DicePacks::IsSlot.
|
#include "chat_helpers/stickers_dice_pack.h" // Stickers::DicePacks::IsSlot.
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
|
#include "data/data_auto_download.h"
|
||||||
#include "data/data_photo.h"
|
#include "data/data_photo.h"
|
||||||
|
#include "data/data_photo_media.h"
|
||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
|
#include "data/data_document_media.h"
|
||||||
#include "data/data_game.h"
|
#include "data/data_game.h"
|
||||||
#include "data/data_web_page.h"
|
#include "data/data_web_page.h"
|
||||||
#include "data/data_poll.h"
|
#include "data/data_poll.h"
|
||||||
#include "data/data_channel.h"
|
#include "data/data_channel.h"
|
||||||
#include "data/data_file_origin.h"
|
#include "data/data_file_origin.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
|
#include "main/main_session_settings.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "storage/file_upload.h"
|
#include "storage/file_upload.h"
|
||||||
#include "app.h"
|
#include "app.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
|
#include "styles/style_dialogs.h"
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -134,6 +140,130 @@ using ItemPreview = HistoryView::ItemPreview;
|
||||||
caption);
|
caption);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] QImage PreparePreviewImage(
|
||||||
|
not_null<const Image*> image,
|
||||||
|
ImageRoundRadius radius = ImageRoundRadius::Small) {
|
||||||
|
const auto original = image->original();
|
||||||
|
if (original.width() * 10 < original.height()
|
||||||
|
|| original.height() * 10 < original.width()) {
|
||||||
|
return QImage();
|
||||||
|
}
|
||||||
|
const auto factor = style::DevicePixelRatio();
|
||||||
|
const auto size = st::dialogsMiniPreview * factor;
|
||||||
|
const auto scaled = original.scaled(
|
||||||
|
QSize(size, size),
|
||||||
|
Qt::KeepAspectRatioByExpanding,
|
||||||
|
Qt::SmoothTransformation);
|
||||||
|
auto square = scaled.copy(
|
||||||
|
(scaled.width() - size) / 2,
|
||||||
|
(scaled.height() - size) / 2,
|
||||||
|
size,
|
||||||
|
size
|
||||||
|
).convertToFormat(QImage::Format_ARGB32_Premultiplied);
|
||||||
|
Images::prepareRound(square, radius);
|
||||||
|
square.setDevicePixelRatio(factor);
|
||||||
|
return square;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PreparedPreview {
|
||||||
|
QImage preview;
|
||||||
|
bool loading = false;
|
||||||
|
|
||||||
|
explicit operator bool() const {
|
||||||
|
return !preview.isNull() || loading;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] PreparedPreview PreparePhotoPreview(
|
||||||
|
not_null<const HistoryItem*> item,
|
||||||
|
const std::shared_ptr<PhotoMedia> &media,
|
||||||
|
ImageRoundRadius radius) {
|
||||||
|
if (const auto small = media->image(PhotoSize::Small)) {
|
||||||
|
return { PreparePreviewImage(small, radius) };
|
||||||
|
} else if (const auto thumbnail = media->image(PhotoSize::Thumbnail)) {
|
||||||
|
return { PreparePreviewImage(thumbnail, radius) };
|
||||||
|
} else if (const auto large = media->image(PhotoSize::Large)) {
|
||||||
|
return { PreparePreviewImage(large, radius) };
|
||||||
|
}
|
||||||
|
const auto allowedToDownload = [&] {
|
||||||
|
const auto photo = media->owner();
|
||||||
|
if (media->loaded() || photo->cancelled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return photo->hasExact(PhotoSize::Small)
|
||||||
|
|| photo->hasExact(PhotoSize::Thumbnail)
|
||||||
|
|| AutoDownload::Should(
|
||||||
|
photo->session().settings().autoDownload(),
|
||||||
|
item->history()->peer,
|
||||||
|
photo);
|
||||||
|
}();
|
||||||
|
if (allowedToDownload) {
|
||||||
|
media->owner()->load(PhotoSize::Small, item->fullId());
|
||||||
|
}
|
||||||
|
if (const auto blurred = media->thumbnailInline()) {
|
||||||
|
return { PreparePreviewImage(blurred, radius), allowedToDownload };
|
||||||
|
}
|
||||||
|
return { QImage(), allowedToDownload };
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] PreparedPreview PrepareFilePreviewImage(
|
||||||
|
not_null<const HistoryItem*> item,
|
||||||
|
const std::shared_ptr<DocumentMedia> &media,
|
||||||
|
ImageRoundRadius radius) {
|
||||||
|
Expects(media->owner()->hasThumbnail());
|
||||||
|
|
||||||
|
if (const auto thumbnail = media->thumbnail()) {
|
||||||
|
return { PreparePreviewImage(thumbnail, radius) };
|
||||||
|
}
|
||||||
|
media->owner()->loadThumbnail(item->fullId());
|
||||||
|
if (const auto blurred = media->thumbnailInline()) {
|
||||||
|
return { PreparePreviewImage(blurred, radius), true };
|
||||||
|
}
|
||||||
|
return { QImage(), true };
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] QImage PutPlayIcon(QImage preview) {
|
||||||
|
Expects(!preview.isNull());
|
||||||
|
|
||||||
|
{
|
||||||
|
QPainter p(&preview);
|
||||||
|
//st::overviewVideoPlay.paintInCenter(
|
||||||
|
// p,
|
||||||
|
// QRect(QPoint(), preview.size() / preview.devicePixelRatio()));
|
||||||
|
}
|
||||||
|
return preview;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] PreparedPreview PrepareFilePreview(
|
||||||
|
not_null<const HistoryItem*> item,
|
||||||
|
const std::shared_ptr<DocumentMedia> &media,
|
||||||
|
ImageRoundRadius radius) {
|
||||||
|
if (auto result = PrepareFilePreviewImage(item, media, radius)) {
|
||||||
|
const auto document = media->owner();
|
||||||
|
if (!result.preview.isNull()
|
||||||
|
&& (document->isVideoFile() || document->isVideoMessage())) {
|
||||||
|
result.preview = PutPlayIcon(std::move(result.preview));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
Expects(media->owner()->hasThumbnail());
|
||||||
|
|
||||||
|
if (const auto thumbnail = media->thumbnail()) {
|
||||||
|
return { PreparePreviewImage(thumbnail, radius) };
|
||||||
|
}
|
||||||
|
media->owner()->loadThumbnail(item->fullId());
|
||||||
|
if (const auto blurred = media->thumbnailInline()) {
|
||||||
|
return { PreparePreviewImage(blurred, radius), true };
|
||||||
|
}
|
||||||
|
return { QImage(), true };
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool TryFilePreview(not_null<DocumentData*> document) {
|
||||||
|
return document->hasThumbnail()
|
||||||
|
&& !document->sticker()
|
||||||
|
&& !document->isAudioFile();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TextForMimeData WithCaptionClipboardText(
|
TextForMimeData WithCaptionClipboardText(
|
||||||
|
@ -348,11 +478,25 @@ ItemPreview MediaPhoto::toPreview(ToPreviewOptions options) const {
|
||||||
const auto caption = options.hideCaption
|
const auto caption = options.hideCaption
|
||||||
? QString()
|
? QString()
|
||||||
: parent()->originalText().text;
|
: parent()->originalText().text;
|
||||||
// #TODO minis generate images and support albums
|
auto images = std::vector<QImage>();
|
||||||
|
// #TODO minis support albums
|
||||||
|
const auto media = _photo->createMediaView();
|
||||||
|
const auto radius = _chat
|
||||||
|
? ImageRoundRadius::Ellipse
|
||||||
|
: ImageRoundRadius::Small;
|
||||||
|
auto context = std::any();
|
||||||
|
if (auto prepared = PreparePhotoPreview(parent(), media, radius)) {
|
||||||
|
images.push_back(std::move(prepared.preview));
|
||||||
|
if (prepared.loading) {
|
||||||
|
context = media;
|
||||||
|
}
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
.text = WithCaptionDialogsText(
|
.text = WithCaptionDialogsText(
|
||||||
tr::lng_in_dlg_photo(tr::now),
|
tr::lng_in_dlg_photo(tr::now),
|
||||||
caption)
|
caption),
|
||||||
|
.images = std::move(images),
|
||||||
|
.loadingContext = std::move(context),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -544,9 +688,27 @@ ItemPreview MediaFile::toPreview(ToPreviewOptions options) const {
|
||||||
const auto caption = options.hideCaption
|
const auto caption = options.hideCaption
|
||||||
? QString()
|
? QString()
|
||||||
: parent()->originalText().text;
|
: parent()->originalText().text;
|
||||||
// #TODO minis generate images and support albums
|
// #TODO minis support albums
|
||||||
|
auto images = std::vector<QImage>();
|
||||||
|
const auto media = TryFilePreview(_document)
|
||||||
|
? _document->createMediaView()
|
||||||
|
: nullptr;
|
||||||
|
const auto radius = _document->isVideoMessage()
|
||||||
|
? ImageRoundRadius::Ellipse
|
||||||
|
: ImageRoundRadius::Small;
|
||||||
|
auto context = std::any();
|
||||||
|
if (media) {
|
||||||
|
if (auto prepared = PrepareFilePreview(parent(), media, radius)) {
|
||||||
|
images.push_back(std::move(prepared.preview));
|
||||||
|
if (prepared.loading) {
|
||||||
|
context = media;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
.text = WithCaptionDialogsText(type, caption),
|
.text = WithCaptionDialogsText(type, caption),
|
||||||
|
.images = std::move(images),
|
||||||
|
.loadingContext = std::move(context),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -296,6 +296,6 @@ dialogsScamFont: font(9px semibold);
|
||||||
dialogsScamSkip: 4px;
|
dialogsScamSkip: 4px;
|
||||||
dialogsScamRadius: 2px;
|
dialogsScamRadius: 2px;
|
||||||
|
|
||||||
dialogsMiniPreview: 32px;
|
dialogsMiniPreview: 16px;
|
||||||
dialogsMiniPreviewSkip: 4px;
|
dialogsMiniPreviewSkip: 2px;
|
||||||
dialogsMiniPreviewRight: 6px;
|
dialogsMiniPreviewRight: 3px;
|
||||||
|
|
|
@ -7,8 +7,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "dialogs/ui/dialogs_message_view.h"
|
#include "dialogs/ui/dialogs_message_view.h"
|
||||||
|
|
||||||
|
#include "history/history.h"
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
|
#include "main/main_session.h"
|
||||||
#include "ui/text/text_options.h"
|
#include "ui/text/text_options.h"
|
||||||
|
#include "ui/image/image.h"
|
||||||
#include "styles/style_dialogs.h"
|
#include "styles/style_dialogs.h"
|
||||||
|
|
||||||
namespace Dialogs::Ui {
|
namespace Dialogs::Ui {
|
||||||
|
@ -16,6 +19,11 @@ namespace {
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
struct MessageView::LoadingContext {
|
||||||
|
std::any context;
|
||||||
|
rpl::lifetime lifetime;
|
||||||
|
};
|
||||||
|
|
||||||
MessageView::MessageView()
|
MessageView::MessageView()
|
||||||
: _textCache(st::dialogsTextWidthMin) {
|
: _textCache(st::dialogsTextWidthMin) {
|
||||||
}
|
}
|
||||||
|
@ -43,12 +51,41 @@ void MessageView::paint(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (_textCachedFor != item.get()) {
|
if (_textCachedFor != item.get()) {
|
||||||
const auto preview = item->toPreview(options);
|
auto preview = item->toPreview(options);
|
||||||
_textCache.setText(
|
_textCache.setText(
|
||||||
st::dialogsTextStyle,
|
st::dialogsTextStyle,
|
||||||
preview.text,
|
preview.text,
|
||||||
DialogTextOptions());
|
DialogTextOptions());
|
||||||
_textCachedFor = item;
|
_textCachedFor = item;
|
||||||
|
_imagesCache = std::move(preview.images);
|
||||||
|
if (preview.loadingContext.has_value()) {
|
||||||
|
if (!_loadingContext) {
|
||||||
|
_loadingContext = std::make_unique<LoadingContext>();
|
||||||
|
item->history()->session().downloaderTaskFinished(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
_textCachedFor = nullptr;
|
||||||
|
}, _loadingContext->lifetime);
|
||||||
|
}
|
||||||
|
_loadingContext->context = std::move(preview.loadingContext);
|
||||||
|
} else {
|
||||||
|
_loadingContext = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto rect = geometry;
|
||||||
|
for (const auto &image : _imagesCache) {
|
||||||
|
if (rect.width() < st::dialogsMiniPreview) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
p.drawImage(rect.topLeft(), image);
|
||||||
|
rect.setLeft(rect.x()
|
||||||
|
+ st::dialogsMiniPreview
|
||||||
|
+ st::dialogsMiniPreviewSkip);
|
||||||
|
}
|
||||||
|
if (!_imagesCache.empty()) {
|
||||||
|
rect.setLeft(rect.x() + st::dialogsMiniPreviewRight);
|
||||||
|
}
|
||||||
|
if (rect.isEmpty()) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
p.setTextPalette(active
|
p.setTextPalette(active
|
||||||
? st::dialogsTextPaletteActive
|
? st::dialogsTextPaletteActive
|
||||||
|
@ -59,10 +96,10 @@ void MessageView::paint(
|
||||||
p.setPen(active ? st::dialogsTextFgActive : (selected ? st::dialogsTextFgOver : st::dialogsTextFg));
|
p.setPen(active ? st::dialogsTextFgActive : (selected ? st::dialogsTextFgOver : st::dialogsTextFg));
|
||||||
_textCache.drawElided(
|
_textCache.drawElided(
|
||||||
p,
|
p,
|
||||||
geometry.left(),
|
rect.left(),
|
||||||
geometry.top(),
|
rect.top(),
|
||||||
geometry.width(),
|
rect.width(),
|
||||||
geometry.height() / st::dialogsTextFont->height);
|
rect.height() / st::dialogsTextFont->height);
|
||||||
p.restoreTextPalette();
|
p.restoreTextPalette();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,10 +7,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <any>
|
||||||
|
|
||||||
|
class Image;
|
||||||
class HistoryItem;
|
class HistoryItem;
|
||||||
|
enum class ImageRoundRadius;
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
|
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
|
@ -42,9 +45,12 @@ public:
|
||||||
ToPreviewOptions options) const;
|
ToPreviewOptions options) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
struct LoadingContext;
|
||||||
|
|
||||||
mutable const HistoryItem *_textCachedFor = nullptr;
|
mutable const HistoryItem *_textCachedFor = nullptr;
|
||||||
mutable Ui::Text::String _textCache;
|
mutable Ui::Text::String _textCache;
|
||||||
mutable std::vector<QImage> _imagesCache;
|
mutable std::vector<QImage> _imagesCache;
|
||||||
|
mutable std::unique_ptr<LoadingContext> _loadingContext;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -926,7 +926,7 @@ bool HistoryItem::isEmpty() const {
|
||||||
|
|
||||||
QString HistoryItem::notificationText() const {
|
QString HistoryItem::notificationText() const {
|
||||||
const auto result = [&] {
|
const auto result = [&] {
|
||||||
if (_media) {
|
if (_media && !serviceMsg()) {
|
||||||
return _media->notificationText();
|
return _media->notificationText();
|
||||||
} else if (!emptyText()) {
|
} else if (!emptyText()) {
|
||||||
return _text.toString();
|
return _text.toString();
|
||||||
|
|
|
@ -12,6 +12,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "base/value_ordering.h"
|
#include "base/value_ordering.h"
|
||||||
#include "data/data_media_types.h"
|
#include "data/data_media_types.h"
|
||||||
|
|
||||||
|
#include <any>
|
||||||
|
|
||||||
enum class UnreadMentionType;
|
enum class UnreadMentionType;
|
||||||
struct HistoryMessageReplyMarkup;
|
struct HistoryMessageReplyMarkup;
|
||||||
class ReplyKeyboard;
|
class ReplyKeyboard;
|
||||||
|
@ -63,6 +65,7 @@ struct ToPreviewOptions {
|
||||||
struct ItemPreview {
|
struct ItemPreview {
|
||||||
QString text;
|
QString text;
|
||||||
std::vector<QImage> images;
|
std::vector<QImage> images;
|
||||||
|
std::any loadingContext;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace HistoryView
|
} // namespace HistoryView
|
||||||
|
|
|
@ -926,9 +926,13 @@ bool HistoryService::needCheck() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemPreview HistoryService::toPreview(ToPreviewOptions options) const {
|
ItemPreview HistoryService::toPreview(ToPreviewOptions options) const {
|
||||||
// #TODO minis generate images
|
// Don't show for service messages (chat photo changed).
|
||||||
|
// Because larger version is shown exactly to the left of the preview.
|
||||||
|
//auto media = _media ? _media->toPreview(options) : ItemPreview();
|
||||||
return {
|
return {
|
||||||
.text = textcmdLink(1, TextUtilities::Clean(notificationText())),
|
.text = textcmdLink(1, TextUtilities::Clean(notificationText())),
|
||||||
|
//.images = std::move(media.images),
|
||||||
|
//.loadingContext = std::move(media.loadingContext),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue