Allow sending videos with covers.

This commit is contained in:
John Preston 2025-01-29 09:24:01 +04:00
parent 6a415cf232
commit e05bb75b8a
42 changed files with 571 additions and 104 deletions

View file

@ -3883,6 +3883,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_choose_image" = "Choose an image";
"lng_choose_file" = "Choose a file";
"lng_choose_files" = "Choose Files";
"lng_choose_cover" = "Choose video cover";
"lng_choose_cover_bad" = "Can't use this file as a caption.";
"lng_game_tag" = "Game";
"lng_context_new_window" = "Open in new window";
@ -4028,6 +4030,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_disable_spoiler" = "Remove Spoiler";
"lng_context_make_paid" = "Make This Content Paid";
"lng_context_change_price" = "Change Price";
"lng_context_edit_cover" = "Edit Cover";
"lng_context_clear_cover" = "Clear Cover";
"lng_context_mention" = "Mention";
"lng_context_search_from" = "Search messages";

View file

@ -74,6 +74,7 @@ struct MessageToSend {
struct RemoteFileInfo {
MTPInputFile file;
std::optional<MTPInputFile> thumb;
std::optional<MTPInputPhoto> videoCover;
std::vector<MTPInputDocument> attachedStickers;
};

View file

@ -276,16 +276,22 @@ mtpRequestId EditTextMessage(
takeFileReference = [=] { return photo->fileReference(); };
} else if (const auto document = media->document()) {
using Flag = MTPDinputMediaDocument::Flag;
const auto videoCover = media->videoCover();
const auto videoTimestamp = media->videoTimestamp();
const auto flags = Flag()
| (media->ttlSeconds() ? Flag::f_ttl_seconds : Flag())
| (spoilered ? Flag::f_spoiler : Flag());
| (spoilered ? Flag::f_spoiler : Flag())
| (videoTimestamp ? Flag::f_video_timestamp : Flag())
| (videoCover ? Flag::f_video_cover : Flag());
takeInputMedia = [=] {
return MTP_inputMediaDocument(
MTP_flags(flags),
document->mtpInput(),
MTPInputPhoto(), // video_cover
(videoCover
? videoCover->mtpInput()
: MTPInputPhoto()),
MTP_int(media->ttlSeconds()),
MTPint(), // video_timestamp
MTP_int(videoTimestamp),
MTPstring()); // query
};
takeFileReference = [=] { return document->fileReference(); };

View file

@ -111,7 +111,8 @@ MTPInputMedia PrepareUploadedDocument(
| (info.thumb ? Flag::f_thumb : Flag())
| (item->groupId() ? Flag::f_nosound_video : Flag())
| (info.attachedStickers.empty() ? Flag::f_stickers : Flag())
| (ttlSeconds ? Flag::f_ttl_seconds : Flag());
| (ttlSeconds ? Flag::f_ttl_seconds : Flag())
| (info.videoCover ? Flag::f_video_cover : Flag());
const auto document = item->media()->document();
return MTP_inputMediaUploadedDocument(
MTP_flags(flags),
@ -121,7 +122,7 @@ MTPInputMedia PrepareUploadedDocument(
ComposeSendingDocumentAttributes(document),
MTP_vector<MTPInputDocument>(
ranges::to<QVector<MTPInputDocument>>(info.attachedStickers)),
MTPInputPhoto(), // video_cover
info.videoCover.value_or(MTPInputPhoto()),
MTP_int(0), // video_timestamp
MTP_int(ttlSeconds));
}

View file

@ -549,10 +549,11 @@ void SendConfirmedFile(
using Flag = MTPDmessageMediaDocument::Flag;
return MTP_messageMediaDocument(
MTP_flags(Flag::f_document
| (file->spoiler ? Flag::f_spoiler : Flag())),
| (file->spoiler ? Flag::f_spoiler : Flag())
| (file->videoCover ? Flag::f_video_cover : Flag())),
file->document,
MTPVector<MTPDocument>(), // alt_documents
MTPPhoto(), // video_cover
file->videoCover ? file->videoCover->photo : MTPPhoto(),
MTPint(), // video_timestamp
MTPint());
} else if (file->type == SendMediaType::Audio) {
@ -561,10 +562,11 @@ void SendConfirmedFile(
return MTP_messageMediaDocument(
MTP_flags(Flag::f_document
| Flag::f_voice
| (ttlSeconds ? Flag::f_ttl_seconds : Flag())),
| (ttlSeconds ? Flag::f_ttl_seconds : Flag())
| (file->videoCover ? Flag::f_video_cover : Flag())),
file->document,
MTPVector<MTPDocument>(), // alt_documents
MTPPhoto(), // video_cover
file->videoCover ? file->videoCover->photo : MTPPhoto(),
MTPint(), // video_timestamp
MTP_int(ttlSeconds));
} else if (file->type == SendMediaType::Round) {

View file

@ -3578,6 +3578,18 @@ void ApiWrap::editMedia(
file.path,
file.content,
std::move(file.information),
(file.videoCover
? std::make_unique<FileLoadTask>(
&session(),
file.videoCover->path,
file.videoCover->content,
std::move(file.videoCover->information),
nullptr,
SendMediaType::Photo,
to,
TextWithTags(),
false)
: nullptr),
type,
to,
caption,
@ -3619,6 +3631,19 @@ void ApiWrap::sendFiles(
file.path,
file.content,
std::move(file.information),
(file.videoCover
? std::make_unique<FileLoadTask>(
&session(),
file.videoCover->path,
file.videoCover->content,
std::move(file.videoCover->information),
nullptr,
SendMediaType::Photo,
to,
TextWithTags(),
false,
nullptr)
: nullptr),
uploadWithType,
to,
caption,
@ -3644,11 +3669,13 @@ void ApiWrap::sendFile(
auto caption = TextWithTags();
const auto spoiler = false;
const auto information = nullptr;
const auto videoCover = nullptr;
_fileLoader->addTask(std::make_unique<FileLoadTask>(
&session(),
QString(),
fileContent,
information,
videoCover,
type,
to,
caption,
@ -4142,19 +4169,30 @@ void ApiWrap::uploadAlbumMedia(
return;
}
const auto &fields = document->c_document();
const auto mtpCover = data.vvideo_cover();
const auto cover = (mtpCover && mtpCover->type() == mtpc_photo)
? &(mtpCover->c_photo())
: (const MTPDphoto*)nullptr;
using Flag = MTPDinputMediaDocument::Flag;
const auto flags = Flag()
| (data.vttl_seconds() ? Flag::f_ttl_seconds : Flag())
| (spoiler ? Flag::f_spoiler : Flag());
| (spoiler ? Flag::f_spoiler : Flag())
| (data.vvideo_timestamp() ? Flag::f_video_timestamp : Flag())
| (cover ? Flag::f_video_cover : Flag());
const auto media = MTP_inputMediaDocument(
MTP_flags(flags),
MTP_inputDocument(
fields.vid(),
fields.vaccess_hash(),
fields.vfile_reference()),
MTPInputPhoto(), // video_cover
(cover
? MTP_inputPhoto(
cover->vid(),
cover->vaccess_hash(),
cover->vfile_reference())
: MTPInputPhoto()),
MTP_int(data.vvideo_timestamp().value_or_empty()),
MTP_int(data.vttl_seconds().value_or_empty()),
MTPint(), // video_timestamp
MTPstring()); // query
sendAlbumWithUploaded(item, groupId, media);
} break;

View file

@ -467,13 +467,16 @@ void EditCaptionBox::rebuildPreview() {
}
} else {
const auto &file = _preparedList.files.front();
const auto isVideoFile = file.isVideoFile();
const auto media = Ui::SingleMediaPreview::Create(
this,
st::defaultComposeControls,
gifPaused,
file,
[] { return true; },
[=](Ui::AttachActionType type) {
return (type != Ui::AttachActionType::EditCover)
|| isVideoFile;
},
Ui::AttachControls::Type::EditOnly);
_isPhoto = (media && media->isPhoto());
const auto withCheckbox = _isPhoto && CanBeCompressed(_albumType);
@ -719,7 +722,7 @@ void EditCaptionBox::setupPhotoEditorEventHandler() {
controller->uiShow(),
&_preparedList.files.front(),
st::sendMediaPreviewSize,
[=] { rebuildPreview(); });
[=](bool ok) { if (ok) rebuildPreview(); });
} else {
EditPhotoImage(_controller, _photoMedia, hasSpoiler(), [=](
Ui::PreparedList &&list) {

View file

@ -242,7 +242,7 @@ SendFilesBox::Block::Block(
int till,
Fn<bool()> gifPaused,
SendFilesWay way,
Fn<bool()> canToggleSpoiler)
Fn<bool(const Ui::PreparedFile &, Ui::AttachActionType)> actionAllowed)
: _items(items)
, _from(from)
, _till(till) {
@ -260,7 +260,9 @@ SendFilesBox::Block::Block(
st,
my,
way,
std::move(canToggleSpoiler));
[=](int index, Ui::AttachActionType type) {
return actionAllowed((*_items)[from + index], type);
});
_preview.reset(preview);
} else {
const auto media = Ui::SingleMediaPreview::Create(
@ -268,7 +270,9 @@ SendFilesBox::Block::Block(
st,
gifPaused,
first,
std::move(canToggleSpoiler));
[=](Ui::AttachActionType type) {
return actionAllowed((*_items)[from], type);
});
if (media) {
_isSingleMedia = true;
_preview.reset(media);
@ -344,6 +348,38 @@ rpl::producer<int> SendFilesBox::Block::itemModifyRequest() const {
}
}
rpl::producer<int> SendFilesBox::Block::itemEditCoverRequest() const {
using namespace rpl::mappers;
const auto preview = _preview.get();
const auto from = _from;
if (_isAlbum) {
const auto album = static_cast<Ui::AlbumPreview*>(preview);
return album->thumbEditCoverRequested() | rpl::map(_1 + from);
} else if (_isSingleMedia) {
const auto media = static_cast<Ui::SingleMediaPreview*>(preview);
return media->editCoverRequests() | rpl::map_to(from);
} else {
return rpl::never<int>();
}
}
rpl::producer<int> SendFilesBox::Block::itemClearCoverRequest() const {
using namespace rpl::mappers;
const auto preview = _preview.get();
const auto from = _from;
if (_isAlbum) {
const auto album = static_cast<Ui::AlbumPreview*>(preview);
return album->thumbClearCoverRequested() | rpl::map(_1 + from);
} else if (_isSingleMedia) {
const auto media = static_cast<Ui::SingleMediaPreview*>(preview);
return media->clearCoverRequests() | rpl::map_to(from);
} else {
return rpl::never<int>();
}
}
rpl::producer<> SendFilesBox::Block::orderUpdated() const {
if (_isAlbum) {
const auto album = static_cast<Ui::AlbumPreview*>(_preview.get());
@ -1008,7 +1044,16 @@ void SendFilesBox::pushBlock(int from, int till) {
till,
gifPaused,
_sendWay.current(),
[=] { return !hasPrice(); });
[=](const Ui::PreparedFile &file, Ui::AttachActionType type) {
return (type == Ui::AttachActionType::ToggleSpoiler)
? !hasPrice()
: (type == Ui::AttachActionType::EditCover)
? (file.isVideoFile()
&& _captionToPeer
&& (_captionToPeer->isBroadcast()
|| _captionToPeer->isSelf()))
: (file.videoCover != nullptr);
});
auto &block = _blocks.back();
const auto widget = _inner->add(
block.takeWidget(),
@ -1129,7 +1174,79 @@ void SendFilesBox::pushBlock(int from, int till) {
show,
&_list.files[index],
st::sendMediaPreviewSize,
[=] { refreshAllAfterChanges(from); });
[=](bool ok) { if (ok) refreshAllAfterChanges(from); });
}, widget->lifetime());
block.itemEditCoverRequest(
) | rpl::start_with_next([=, show = _show](int index) {
applyBlockChanges();
const auto replace = [=](Ui::PreparedList list) {
if (list.files.empty()) {
return;
}
auto &entry = _list.files[index];
const auto video = entry.information
? std::get_if<Ui::PreparedFileInformation::Video>(
&entry.information->media)
: nullptr;
if (!video) {
return;
}
auto old = std::shared_ptr<Ui::PreparedFile>(
std::move(entry.videoCover));
entry.videoCover = std::make_unique<Ui::PreparedFile>(
std::move(list.files.front()));
Editor::OpenWithPreparedFile(
this,
show,
entry.videoCover.get(),
st::sendMediaPreviewSize,
crl::guard(this, [=](bool ok) {
if (!ok) {
_list.files[index].videoCover = old
? std::make_unique<Ui::PreparedFile>(
std::move(*old))
: nullptr;
}
refreshAllAfterChanges(from);
}),
video->thumbnail.size());
};
const auto checkResult = [=](const Ui::PreparedList &list) {
if (list.files.empty()) {
return true;
}
if (list.files.front().type != Ui::PreparedFile::Type::Photo) {
show->showToast(tr::lng_choose_cover_bad(tr::now));
return false;
}
return true;
};
const auto callback = [=](FileDialog::OpenResult &&result) {
const auto premium = _show->session().premium();
FileDialogCallback(
std::move(result),
checkResult,
replace,
premium,
show);
};
FileDialog::GetOpenPath(
this,
tr::lng_choose_cover(tr::now),
FileDialog::ImagesFilter(),
crl::guard(this, callback));
}, widget->lifetime());
block.itemClearCoverRequest(
) | rpl::start_with_next([=](int index) {
applyBlockChanges();
refreshAllAfterChanges(from, [&] {
auto &entry = _list.files[index];
entry.videoCover = nullptr;
});
}, widget->lifetime());
block.orderUpdated() | rpl::start_with_next([=]{

View file

@ -153,7 +153,9 @@ private:
int till,
Fn<bool()> gifPaused,
Ui::SendFilesWay way,
Fn<bool()> canToggleSpoiler);
Fn<bool(
const Ui::PreparedFile &,
Ui::AttachActionType)> actionAllowed);
Block(Block &&other) = default;
Block &operator=(Block &&other) = default;
@ -164,6 +166,8 @@ private:
[[nodiscard]] rpl::producer<int> itemDeleteRequest() const;
[[nodiscard]] rpl::producer<int> itemReplaceRequest() const;
[[nodiscard]] rpl::producer<int> itemModifyRequest() const;
[[nodiscard]] rpl::producer<int> itemEditCoverRequest() const;
[[nodiscard]] rpl::producer<int> itemClearCoverRequest() const;
[[nodiscard]] rpl::producer<> orderUpdated() const;
void setSendWay(Ui::SendFilesWay way);

View file

@ -329,6 +329,7 @@ not_null<DocumentData*> GenerateLocalSticker(
path,
QByteArray(),
nullptr,
nullptr,
SendMediaType::File,
FileLoadTo(0, {}, {}, 0),
{},

View file

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_streaming.h"
#include "data/data_document_media.h"
#include "data/data_reply_preview.h"
#include "data/data_web_page.h"
#include "lang/lang_keys.h"
#include "inline_bots/inline_bot_layout_item.h"
#include "main/main_session.h"
@ -1881,3 +1882,18 @@ void DocumentData::collectLocalData(not_null<DocumentData*> local) {
session().local().writeFileLocation(mediaKey(), _location);
}
}
PhotoData *LookupVideoCover(
not_null<DocumentData*> document,
HistoryItem *item) {
const auto media = item ? item->media() : nullptr;
if (const auto webpage = media ? media->webpage() : nullptr) {
if (webpage->document == document && webpage->photoIsVideoCover) {
return webpage->photo;
}
return nullptr;
}
return (media && media->document() == document)
? media->videoCover()
: nullptr;
}

View file

@ -13,6 +13,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_cloud_file.h"
#include "core/file_location.h"
class HistoryItem;
class PhotoData;
enum class ChatRestriction;
class mtpFileLoader;
@ -398,6 +400,10 @@ private:
};
[[nodiscard]] PhotoData *LookupVideoCover(
not_null<DocumentData*> document,
HistoryItem *item);
VoiceWaveform documentWaveformDecode(const QByteArray &encoded5bit);
QByteArray documentWaveformEncode5bit(const VoiceWaveform &waveform);

View file

@ -1302,7 +1302,11 @@ bool MediaFile::updateSentMedia(const MTPMessageMedia &media) {
"or with ttl_seconds in updateSentMedia()"));
return false;
}
parent()->history()->owner().documentConvert(_document, *content);
const auto owner = &parent()->history()->owner();
owner->documentConvert(_document, *content);
if (const auto cover = _videoCover ? data.vvideo_cover() : nullptr) {
owner->photoConvert(_videoCover, *cover);
}
return true;
}
@ -1334,7 +1338,6 @@ std::unique_ptr<HistoryView::Media> MediaFile::createView(
message,
realParent,
_document,
_videoCover,
_spoiler);
}
} else if (_document->isAnimation() || _document->isVideoFile()) {
@ -1342,7 +1345,6 @@ std::unique_ptr<HistoryView::Media> MediaFile::createView(
message,
realParent,
_document,
_videoCover,
_spoiler);
} else if (_document->isTheme() && _document->hasThumbnail()) {
return std::make_unique<HistoryView::ThemeDocument>(
@ -2611,7 +2613,6 @@ std::unique_ptr<HistoryView::Media> MediaStory::createView(
message,
realParent,
story->document(),
nullptr,
spoiler);
}
}

View file

@ -42,6 +42,15 @@ QSizeF FlipSizeByRotation(const QSizeF &size, int angle) {
return (((angle / 90) % 2) == 1) ? size.transposed() : size;
}
[[nodiscard]] QRectF OriginalCrop(QSize outer, QSize inner) {
const auto size = inner.scaled(outer, Qt::KeepAspectRatio);
return QRectF(
(outer.width() - size.width()) / 2,
(outer.height() - size.height()) / 2,
size.width(),
size.height());
}
} // namespace
Crop::Crop(
@ -60,6 +69,8 @@ Crop::Crop(
, _data(std::move(data))
, _cropOriginal(modifications.crop.isValid()
? modifications.crop
: !_data.exactSize.isEmpty()
? OriginalCrop(_imageSize, _data.exactSize)
: QRectF(QPoint(), _imageSize))
, _angle(modifications.angle)
, _flipped(modifications.flipped)

View file

@ -32,6 +32,7 @@ struct EditorData {
TextWithEntities about;
QString confirm;
QSize exactSize;
CropType cropType = CropType::Rect;
bool keepAspectRatio = false;
};

View file

@ -26,22 +26,26 @@ void OpenWithPreparedFile(
std::shared_ptr<ChatHelpers::Show> show,
not_null<Ui::PreparedFile*> file,
int previewWidth,
Fn<void()> &&doneCallback) {
Fn<void(bool ok)> &&doneCallback,
QSize exactSize) {
using ImageInfo = Ui::PreparedFileInformation::Image;
const auto image = std::get_if<ImageInfo>(&file->information->media);
if (!image) {
doneCallback(false);
return;
}
const auto photoType = (file->type == Ui::PreparedFile::Type::Photo);
const auto modifiedFileType = (file->type == Ui::PreparedFile::Type::File)
&& !image->modifications.empty();
if (!photoType && !modifiedFileType) {
doneCallback(false);
return;
}
const auto sideLimit = PhotoSideLimit();
auto callback = [=, done = std::move(doneCallback)](
const PhotoModifications &mods) {
const auto accepted = std::make_shared<bool>();
auto callback = [=](const PhotoModifications &mods) {
*accepted = true;
image->modifications = mods;
Storage::UpdateImageDetails(*file, previewWidth, sideLimit);
{
@ -51,7 +55,7 @@ void OpenWithPreparedFile(
? PreparedFile::Type::Photo
: PreparedFile::Type::File;
}
done();
doneCallback(true);
};
auto copy = image->data;
const auto fileImage = std::make_shared<Image>(std::move(copy));
@ -60,10 +64,16 @@ void OpenWithPreparedFile(
show,
show,
fileImage,
image->modifications);
image->modifications,
EditorData{ .exactSize = exactSize, .keepAspectRatio = true });
const auto raw = editor.get();
auto layer = std::make_unique<LayerWidget>(parent, std::move(editor));
InitEditorLayer(layer.get(), raw, std::move(callback));
QObject::connect(layer.get(), &QObject::destroyed, [=] {
if (!*accepted) {
doneCallback(false);
}
});
show->showLayer(std::move(layer), Ui::LayerOption::KeepOther);
}

View file

@ -34,7 +34,8 @@ void OpenWithPreparedFile(
std::shared_ptr<ChatHelpers::Show> show,
not_null<Ui::PreparedFile*> file,
int previewWidth,
Fn<void()> &&doneCallback);
Fn<void(bool ok)> &&doneCallback,
QSize exactSize = {});
void PrepareProfilePhoto(
not_null<QWidget*> parent,

View file

@ -143,11 +143,10 @@ Gif::Gif(
not_null<Element*> parent,
not_null<HistoryItem*> realParent,
not_null<DocumentData*> document,
PhotoData *videoCover,
bool spoiler)
: File(parent, realParent)
, _data(document)
, _videoCover(videoCover)
, _videoCover(LookupVideoCover(document, realParent))
, _storyId(realParent->media()
? realParent->media()->storyId()
: FullStoryId())
@ -1805,12 +1804,18 @@ void Gif::validateGroupedCache(
ensureDataMediaCreated();
const auto good = _dataMedia->goodThumbnail();
const auto thumb = _dataMedia->thumbnail();
const auto good = _videoCoverMedia
? _videoCoverMedia->image(Data::PhotoSize::Large)
: _dataMedia->goodThumbnail();
const auto thumb = _videoCoverMedia
? nullptr
: _dataMedia->thumbnail();
const auto image = good
? good
: thumb
? thumb
: _videoCoverMedia
? _videoCoverMedia->thumbnailInline()
: _dataMedia->thumbnailInline();
const auto blur = !good
&& (!thumb

View file

@ -54,7 +54,6 @@ public:
not_null<Element*> parent,
not_null<HistoryItem*> realParent,
not_null<DocumentData*> document,
PhotoData *videoCover,
bool spoiler);
~Gif();

View file

@ -101,7 +101,6 @@ std::unique_ptr<Media> CreateAttach(
parent,
parent->data(),
document,
photo,
spoiler);
} else if (document->isWallPaper() || document->isTheme()) {
return std::make_unique<ThemeDocument>(

View file

@ -37,9 +37,7 @@ QByteArray SessionSettings::serialize() const {
+ _groupStickersSectionHidden.size() * sizeof(quint64)
+ sizeof(qint32) * 4
+ Serialize::bytearraySize(autoDownload)
+ sizeof(qint32) * 4
+ sizeof(qint32) * 5
+ sizeof(qint32)
+ sizeof(qint32) * 11
+ (_mutePeriods.size() * sizeof(quint64))
+ sizeof(qint32) * 2
+ _hiddenPinnedMessages.size() * (sizeof(quint64) * 3)
@ -69,6 +67,7 @@ QByteArray SessionSettings::serialize() const {
<< qint32(_archiveCollapsed.current() ? 1 : 0)
<< qint32(_archiveInMainMenu.current() ? 1 : 0)
<< qint32(_skipArchiveInSearch.current() ? 1 : 0)
<< qint32(0) // old _mediaLastPlaybackPosition.size());
<< qint32(0) // very old _hiddenPinnedMessages.size());
<< qint32(_dialogsFiltersEnabled ? 1 : 0)
<< qint32(_supportAllSilent ? 1 : 0)

View file

@ -2328,11 +2328,22 @@ void OverlayWidget::assignMediaPointer(DocumentData *document) {
_quality = Core::App().settings().videoQuality();
_chosenQuality = _document->chooseQuality(_message, _quality);
_documentMedia = _document->createMediaView();
_documentMedia->goodThumbnailWanted();
_documentMedia->thumbnailWanted(fileOrigin());
_videoCover = LookupVideoCover(_document, _message);
if (_videoCover) {
_videoCoverMedia = _videoCover->createMediaView();
_videoCoverMedia->wanted(
Data::PhotoSize::Large,
fileOrigin());
} else {
_videoCoverMedia = nullptr;
_documentMedia->goodThumbnailWanted();
_documentMedia->thumbnailWanted(fileOrigin());
}
} else {
_chosenQuality = nullptr;
_documentMedia = nullptr;
_videoCover = nullptr;
_videoCoverMedia = nullptr;
}
_documentLoadingTo = QString();
}
@ -2346,6 +2357,8 @@ void OverlayWidget::assignMediaPointer(not_null<PhotoData*> photo) {
_document = nullptr;
_documentMedia = nullptr;
_documentLoadingTo = QString();
_videoCover = nullptr;
_videoCoverMedia = nullptr;
if (_photo != photo) {
_flip = {};
_photo = photo;
@ -3988,13 +4001,19 @@ void OverlayWidget::initStreamingThumbnail() {
}
return thumbnail;
};
const auto good = _document
const auto good = _videoCover
? _videoCoverMedia->image(Data::PhotoSize::Large)
: _document
? _documentMedia->goodThumbnail()
: _photoMedia->image(Data::PhotoSize::Large);
const auto thumbnail = _document
const auto thumbnail = _videoCover
? _videoCoverMedia->image(Data::PhotoSize::Small)
: _document
? _documentMedia->thumbnail()
: computePhotoThumbnail();
const auto blurred = _document
const auto blurred = _videoCover
? _videoCoverMedia->thumbnailInline()
: _document
? _documentMedia->thumbnailInline()
: _photoMedia->thumbnailInline();
const auto size = _photo

View file

@ -561,10 +561,12 @@ private:
PhotoData *_photo = nullptr;
DocumentData *_document = nullptr;
DocumentData *_chosenQuality = nullptr;
PhotoData *_videoCover = nullptr;
Media::VideoQuality _quality;
QString _documentLoadingTo;
std::shared_ptr<Data::PhotoMedia> _photoMedia;
std::shared_ptr<Data::DocumentMedia> _documentMedia;
std::shared_ptr<Data::PhotoMedia> _videoCoverMedia;
base::flat_set<std::shared_ptr<Data::PhotoMedia>> _preloadPhotos;
base::flat_set<std::shared_ptr<Data::DocumentMedia>> _preloadDocuments;
int _rotation = 0;

View file

@ -486,6 +486,7 @@ Video::Video(
MediaOptions options)
: RadialProgressItem(delegate, parent)
, _data(video)
, _videoCover(LookupVideoCover(video, parent))
, _duration(Ui::FormatDurationText(_data->duration() / 1000))
, _spoiler(options.spoiler ? std::make_unique<Ui::SpoilerAnimation>([=] {
delegate->repaintItem(this);
@ -493,7 +494,13 @@ Video::Video(
, _pinned(options.pinned)
, _story(options.story) {
setDocumentLinks(_data);
_data->loadThumbnail(parent->fullId());
if (!_videoCover) {
_data->loadThumbnail(parent->fullId());
} else if (_videoCover->inlineThumbnailBytes().isEmpty()
&& (_videoCover->hasExact(Data::PhotoSize::Small)
|| _videoCover->hasExact(Data::PhotoSize::Thumbnail))) {
_videoCover->load(Data::PhotoSize::Small, parent->fullId());
}
}
Video::~Video() = default;
@ -516,9 +523,19 @@ void Video::paint(Painter &p, const QRect &clip, TextSelection selection, const
ensureDataMediaCreated();
const auto selected = (selection == FullSelection);
const auto blurred = _dataMedia->thumbnailInline();
const auto thumbnail = _spoiler ? nullptr : _dataMedia->thumbnail();
const auto good = _spoiler ? nullptr : _dataMedia->goodThumbnail();
const auto blurred = _videoCover
? _videoCoverMedia->thumbnailInline()
: _dataMedia->thumbnailInline();
const auto thumbnail = _spoiler
? nullptr
: _videoCover
? _videoCoverMedia->image(Data::PhotoSize::Small)
: _dataMedia->thumbnail();
const auto good = _spoiler
? nullptr
: _videoCover
? _videoCoverMedia->image(Data::PhotoSize::Large)
: _dataMedia->goodThumbnail();
bool loaded = dataLoaded(), displayLoading = _data->displayLoading();
if (displayLoading) {
@ -626,12 +643,17 @@ void Video::paint(Painter &p, const QRect &clip, TextSelection selection, const
}
void Video::ensureDataMediaCreated() const {
if (_dataMedia) {
if (_dataMedia && (!_videoCover || _videoCoverMedia)) {
return;
}
_dataMedia = _data->createMediaView();
_dataMedia->goodThumbnailWanted();
_dataMedia->thumbnailWanted(parent()->fullId());
if (_videoCover) {
_videoCoverMedia = _videoCover->createMediaView();
_videoCover->load(Data::PhotoSize::Large, parent()->fullId());
} else {
_dataMedia->goodThumbnailWanted();
_dataMedia->thumbnailWanted(parent()->fullId());
}
delegate()->registerHeavyItem(this);
}

View file

@ -316,7 +316,9 @@ private:
void updateStatusText();
const not_null<DocumentData*> _data;
PhotoData *_videoCover = nullptr;
mutable std::shared_ptr<Data::DocumentMedia> _dataMedia;
mutable std::shared_ptr<Data::PhotoMedia> _videoCoverMedia;
StatusText _status;
QString _duration;

View file

@ -335,6 +335,11 @@ void Uploader::upload(
if (file->type == SendMediaType::ThemeFile) {
document->checkWallPaperProperties();
}
if (file->videoCover) {
session().data().processPhoto(
file->videoCover->photo,
file->videoCover->photoThumbs);
}
}
_queue.push_back({ itemId, file });
if (!_nextTimer.isActive()) {
@ -348,9 +353,26 @@ void Uploader::failed(FullMsgId itemId) {
const auto entry = std::move(*i);
_queue.erase(i);
notifyFailed(entry);
} else if (const auto coverId = _videoIdToCoverId.take(itemId)) {
if (const auto video = _videoWaitingCover.take(*coverId)) {
const auto document = session().data().document(video->id);
if (document->uploading()) {
document->status = FileUploadFailed;
}
_documentFailed.fire_copy(video->fullId);
}
failed(*coverId);
} else if (const auto video = _videoWaitingCover.take(itemId)) {
_videoIdToCoverId.remove(video->fullId);
const auto document = session().data().document(video->id);
if (document->uploading()) {
document->status = FileUploadFailed;
}
_documentFailed.fire_copy(video->fullId);
}
cancelRequests(itemId);
maybeFinishFront();
crl::on_main(this, [=] {
maybeSend();
});
@ -854,7 +876,8 @@ void Uploader::finishFront() {
MTP_int(entry.parts->size()),
MTP_string(photoFilename),
MTP_bytes(md5));
_photoReady.fire({
auto ready = UploadedMedia{
.id = entry.file->id,
.fullId = entry.itemId,
.info = {
.file = file,
@ -862,7 +885,13 @@ void Uploader::finishFront() {
},
.options = options,
.edit = edit,
});
};
const auto i = _videoWaitingCover.find(entry.itemId);
if (i != end(_videoWaitingCover)) {
uploadCoverAsPhoto(i->second.fullId, std::move(ready));
} else {
_photoReady.fire(std::move(ready));
}
} else if (entry.file->type == SendMediaType::File
|| entry.file->type == SendMediaType::ThemeFile
|| entry.file->type == SendMediaType::Audio
@ -892,7 +921,8 @@ void Uploader::finishFront() {
MTP_string(thumbFilename),
MTP_bytes(thumbMd5));
}();
_documentReady.fire({
auto ready = UploadedMedia{
.id = entry.file->id,
.fullId = entry.itemId,
.info = {
.file = file,
@ -901,7 +931,12 @@ void Uploader::finishFront() {
},
.options = options,
.edit = edit,
});
};
if (entry.file->videoCover) {
uploadVideoCover(std::move(ready), entry.file->videoCover);
} else {
_documentReady.fire(std::move(ready));
}
} else if (entry.file->type == SendMediaType::Secure) {
_secureReady.fire({
entry.itemId,
@ -916,4 +951,54 @@ void Uploader::partFailed(const MTP::Error &error, mtpRequestId requestId) {
failed(request.itemId);
}
void Uploader::uploadVideoCover(
UploadedMedia &&video,
std::shared_ptr<FilePrepareResult> videoCover) {
const auto coverId = FullMsgId(
videoCover->to.peer,
session().data().nextLocalMessageId());
_videoIdToCoverId.emplace(video.fullId, coverId);
_videoWaitingCover.emplace(coverId, std::move(video));
upload(coverId, videoCover);
}
void Uploader::uploadCoverAsPhoto(
FullMsgId videoId,
UploadedMedia &&cover) {
const auto coverId = cover.fullId;
_api->request(MTPmessages_UploadMedia(
MTP_flags(0),
MTPstring(), // business_connection_id
session().data().peer(videoId.peer)->input,
MTP_inputMediaUploadedPhoto(
MTP_flags(0),
cover.info.file,
MTP_vector<MTPInputDocument>(0),
MTP_int(0))
)).done([=](const MTPMessageMedia &result) {
result.match([&](const MTPDmessageMediaPhoto &data) {
const auto photo = data.vphoto();
if (!photo || photo->type() != mtpc_photo) {
failed(coverId);
return;
}
const auto &fields = photo->c_photo();
if (const auto coverId = _videoIdToCoverId.take(videoId)) {
if (auto video = _videoWaitingCover.take(*coverId)) {
video->info.videoCover = MTP_inputPhoto(
fields.vid(),
fields.vaccess_hash(),
fields.vfile_reference());
_documentReady.fire(std::move(*video));
}
}
}, [&](const auto &) {
failed(coverId);
});
}).fail([=] {
failed(coverId);
}).send();
}
} // namespace Storage

View file

@ -29,6 +29,7 @@ namespace Storage {
constexpr auto kUseBigFilesFrom = 30 * 1024 * 1024;
struct UploadedMedia {
uint64 id = 0;
FullMsgId fullId;
Api::RemoteFileInfo info;
Api::SendOptions options;
@ -134,6 +135,11 @@ private:
void partFailed(const MTP::Error &error, mtpRequestId requestId);
Request finishRequest(mtpRequestId requestId);
void uploadVideoCover(
UploadedMedia &&video,
std::shared_ptr<FilePrepareResult> videoCover);
void uploadCoverAsPhoto(FullMsgId videoId, UploadedMedia &&cover);
void processPhotoProgress(FullMsgId itemId);
void processPhotoFailed(FullMsgId itemId);
void processDocumentProgress(FullMsgId itemId);
@ -163,6 +169,9 @@ private:
crl::time _latestDcIndexRemoved = 0;
std::vector<Request> _pendingFromRemovedDcIndices;
base::flat_map<FullMsgId, FullMsgId> _videoIdToCoverId;
base::flat_map<FullMsgId, UploadedMedia> _videoWaitingCover;
FullMsgId _pausedId;
base::Timer _nextTimer, _stopSessionsTimer;

View file

@ -468,6 +468,7 @@ FileLoadTask::FileLoadTask(
const QString &filepath,
const QByteArray &content,
std::unique_ptr<Ui::PreparedFileInformation> information,
std::unique_ptr<FileLoadTask> videoCover,
SendMediaType type,
const FileLoadTo &to,
const TextWithTags &caption,
@ -481,6 +482,7 @@ FileLoadTask::FileLoadTask(
, _album(std::move(album))
, _filepath(filepath)
, _content(content)
, _videoCover(std::move(videoCover))
, _information(std::move(information))
, _type(type)
, _caption(caption)
@ -688,6 +690,15 @@ void FileLoadTask::process(Args &&args) {
.spoiler = _spoiler,
.album = _album,
});
if (const auto cover = _videoCover.get()) {
cover->process();
if (const auto &result = cover->peekResult()) {
if (result->type == SendMediaType::Photo
&& !result->fileparts.empty()) {
_result->videoCover = result;
}
}
}
QString filename, filemime;
qint64 filesize = 0;
@ -1073,8 +1084,8 @@ void FileLoadTask::finish() {
}
}
FilePrepareResult *FileLoadTask::peekResult() const {
return _result.get();
const std::shared_ptr<FilePrepareResult> &FileLoadTask::peekResult() const {
return _result;
}
std::unique_ptr<Ui::PreparedFileInformation> FileLoadTask::readMediaInformation(

View file

@ -196,6 +196,8 @@ struct FilePrepareResult {
std::vector<MTPInputDocument> attachedStickers;
std::shared_ptr<FilePrepareResult> videoCover;
void setFileData(const QByteArray &filedata);
void setThumbData(const QByteArray &thumbdata);
@ -222,6 +224,7 @@ public:
const QString &filepath,
const QByteArray &content,
std::unique_ptr<Ui::PreparedFileInformation> information,
std::unique_ptr<FileLoadTask> videoCover,
SendMediaType type,
const FileLoadTo &to,
const TextWithTags &caption,
@ -252,7 +255,8 @@ public:
}
void finish() override;
FilePrepareResult *peekResult() const;
[[nodiscard]] auto peekResult() const
-> const std::shared_ptr<FilePrepareResult> &;
private:
static bool CheckForSong(
@ -281,6 +285,7 @@ private:
const std::shared_ptr<SendingAlbum> _album;
QString _filepath;
QByteArray _content;
std::unique_ptr<FileLoadTask> _videoCover;
std::unique_ptr<Ui::PreparedFileInformation> _information;
crl::time _duration = 0;
VoiceWaveform _waveform;

View file

@ -362,10 +362,22 @@ void UpdateImageDetails(
bool ApplyModifications(PreparedList &list) {
auto applied = false;
for (auto &file : list.files) {
const auto apply = [&](PreparedFile &file, QSize strictSize = {}) {
const auto image = std::get_if<Image>(&file.information->media);
const auto guard = gsl::finally([&] {
if (!image || strictSize.isEmpty()) {
return;
}
applied = true;
file.path = QString();
file.content = QByteArray();
image->data = image->data.scaled(
strictSize,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
});
if (!image || !image->modifications) {
continue;
return;
}
applied = true;
file.path = QString();
@ -373,6 +385,16 @@ bool ApplyModifications(PreparedList &list) {
image->data = Editor::ImageModified(
std::move(image->data),
image->modifications);
};
for (auto &file : list.files) {
apply(file);
if (const auto cover = file.videoCover.get()) {
const auto video = file.information
? std::get_if<Ui::PreparedFileInformation::Video>(
&file.information->media)
: nullptr;
apply(*cover, video ? video->thumbnail.size() : QSize());
}
}
return applied;
}

View file

@ -34,10 +34,10 @@ AbstractSingleMediaPreview::AbstractSingleMediaPreview(
QWidget *parent,
const style::ComposeControls &st,
AttachControls::Type type,
Fn<bool()> canToggleSpoiler)
Fn<bool(AttachActionType)> actionAllowed)
: AbstractSinglePreview(parent)
, _st(st)
, _canToggleSpoiler(std::move(canToggleSpoiler))
, _actionAllowed(std::move(actionAllowed))
, _minThumbH(st::sendBoxAlbumGroupSize.height()
+ st::sendBoxAlbumGroupSkipTop * 2)
, _controls(base::make_unique_q<AttachControlsWidget>(this, type)) {
@ -57,6 +57,14 @@ rpl::producer<> AbstractSingleMediaPreview::modifyRequests() const {
return _photoEditorRequests.events();
}
rpl::producer<> AbstractSingleMediaPreview::editCoverRequests() const {
return _editCoverRequests.events();
}
rpl::producer<> AbstractSingleMediaPreview::clearCoverRequests() const {
return _clearCoverRequests.events();
}
void AbstractSingleMediaPreview::setSendWay(SendFilesWay way) {
_sendWay = way;
update();
@ -112,7 +120,7 @@ void AbstractSingleMediaPreview::preparePreview(QImage preview) {
preview = Images::Prepare(
std::move(preview),
QSize(maxW, maxH) * ratio,
{ .options = Images::Option::Blur, .outer = { maxW, maxH } });
{ .outer = { maxW, maxH } });
}
auto originalWidth = preview.width();
auto originalHeight = preview.height();
@ -273,24 +281,33 @@ void AbstractSingleMediaPreview::applyCursor(style::cursor cursor) {
}
void AbstractSingleMediaPreview::showContextMenu(QPoint position) {
if (!_canToggleSpoiler()
|| !_sendWay.sendImagesAsPhotos()
|| !supportsSpoilers()) {
return;
}
_menu = base::make_unique_q<Ui::PopupMenu>(
this,
_st.tabbed.menu);
const auto &icons = _st.tabbed.icons;
const auto spoilered = hasSpoiler();
_menu->addAction(spoilered
? tr::lng_context_disable_spoiler(tr::now)
: tr::lng_context_spoiler_effect(tr::now), [=] {
setSpoiler(!spoilered);
_spoileredChanges.fire_copy(!spoilered);
}, spoilered ? &icons.menuSpoilerOff : &icons.menuSpoiler);
if (_actionAllowed(AttachActionType::ToggleSpoiler)
&& _sendWay.sendImagesAsPhotos()
&& supportsSpoilers()) {
const auto spoilered = hasSpoiler();
_menu->addAction(spoilered
? tr::lng_context_disable_spoiler(tr::now)
: tr::lng_context_spoiler_effect(tr::now), [=] {
setSpoiler(!spoilered);
_spoileredChanges.fire_copy(!spoilered);
}, spoilered ? &icons.menuSpoilerOff : &icons.menuSpoiler);
}
if (_actionAllowed(AttachActionType::EditCover)) {
_menu->addAction(tr::lng_context_edit_cover(tr::now), [=] {
_editCoverRequests.fire({});
}, &st::menuIconEdit);
if (_actionAllowed(AttachActionType::ClearCover)) {
_menu->addAction(tr::lng_context_clear_cover(tr::now), [=] {
_clearCoverRequests.fire({});
}, &st::menuIconCancel);
}
}
if (_menu->empty()) {
_menu = nullptr;
} else {

View file

@ -27,7 +27,7 @@ public:
QWidget *parent,
const style::ComposeControls &st,
AttachControls::Type type,
Fn<bool()> canToggleSpoiler);
Fn<bool(AttachActionType)> actionAllowed);
~AbstractSingleMediaPreview();
void setSendWay(SendFilesWay way);
@ -36,6 +36,8 @@ public:
[[nodiscard]] rpl::producer<> deleteRequests() const override;
[[nodiscard]] rpl::producer<> editRequests() const override;
[[nodiscard]] rpl::producer<> modifyRequests() const override;
[[nodiscard]] rpl::producer<> editCoverRequests() const;
[[nodiscard]] rpl::producer<> clearCoverRequests() const;
[[nodiscard]] bool isPhoto() const;
@ -74,7 +76,7 @@ private:
const style::ComposeControls &_st;
SendFilesWay _sendWay;
Fn<bool()> _canToggleSpoiler;
Fn<bool(AttachActionType)> _actionAllowed;
bool _animated = false;
QPixmap _preview;
QPixmap _previewBlurred;
@ -89,6 +91,8 @@ private:
const int _minThumbH;
const base::unique_qptr<AttachControlsWidget> _controls;
rpl::event_stream<> _photoEditorRequests;
rpl::event_stream<> _editCoverRequests;
rpl::event_stream<> _clearCoverRequests;
style::cursor _cursor = style::cur_default;
bool _pressed = false;

View file

@ -38,11 +38,11 @@ AlbumPreview::AlbumPreview(
const style::ComposeControls &st,
gsl::span<Ui::PreparedFile> items,
SendFilesWay way,
Fn<bool()> canToggleSpoiler)
Fn<bool(int, AttachActionType)> actionAllowed)
: RpWidget(parent)
, _st(st)
, _sendWay(way)
, _canToggleSpoiler(std::move(canToggleSpoiler))
, _actionAllowed(std::move(actionAllowed))
, _dragTimer([=] { switchToDrag(); }) {
setMouseTracking(true);
prepareThumbs(items);
@ -582,19 +582,31 @@ void AlbumPreview::mouseReleaseEvent(QMouseEvent *e) {
void AlbumPreview::showContextMenu(
not_null<AlbumThumbnail*> thumb,
QPoint position) {
if (!_canToggleSpoiler() || !_sendWay.sendImagesAsPhotos()) {
return;
}
_menu = base::make_unique_q<Ui::PopupMenu>(
this,
st::popupMenuWithIcons);
const auto spoilered = thumb->hasSpoiler();
_menu->addAction(spoilered
? tr::lng_context_disable_spoiler(tr::now)
: tr::lng_context_spoiler_effect(tr::now), [=] {
thumb->setSpoiler(!spoilered);
}, spoilered ? &st::menuIconSpoilerOff : &st::menuIconSpoiler);
const auto index = orderIndex(thumb);
if (_actionAllowed(index, AttachActionType::ToggleSpoiler)
&& _sendWay.sendImagesAsPhotos()) {
const auto spoilered = thumb->hasSpoiler();
_menu->addAction(spoilered
? tr::lng_context_disable_spoiler(tr::now)
: tr::lng_context_spoiler_effect(tr::now), [=] {
thumb->setSpoiler(!spoilered);
}, spoilered ? &st::menuIconSpoilerOff : &st::menuIconSpoiler);
}
if (_actionAllowed(index, AttachActionType::EditCover)) {
_menu->addAction(tr::lng_context_edit_cover(tr::now), [=] {
_thumbEditCoverRequested.fire_copy(index);
}, &st::menuIconEdit);
if (_actionAllowed(index, AttachActionType::ClearCover)) {
_menu->addAction(tr::lng_context_clear_cover(tr::now), [=] {
_thumbClearCoverRequested.fire_copy(index);
}, &st::menuIconCancel);
}
}
if (_menu->empty()) {
_menu = nullptr;

View file

@ -29,7 +29,7 @@ public:
const style::ComposeControls &st,
gsl::span<Ui::PreparedFile> items,
SendFilesWay way,
Fn<bool()> canToggleSpoiler);
Fn<bool(int, AttachActionType)> actionAllowed);
~AlbumPreview();
void setSendWay(SendFilesWay way);
@ -42,15 +42,18 @@ public:
[[nodiscard]] rpl::producer<int> thumbDeleted() const {
return _thumbDeleted.events();
}
[[nodiscard]] rpl::producer<int> thumbChanged() const {
return _thumbChanged.events();
}
[[nodiscard]] rpl::producer<int> thumbModified() const {
return _thumbModified.events();
}
[[nodiscard]] rpl::producer<int> thumbEditCoverRequested() const {
return _thumbEditCoverRequested.events();
}
[[nodiscard]] rpl::producer<int> thumbClearCoverRequested() const {
return _thumbClearCoverRequested.events();
}
[[nodiscard]] rpl::producer<> orderUpdated() const {
return _orderUpdated.events();
}
@ -101,7 +104,7 @@ private:
const style::ComposeControls &_st;
SendFilesWay _sendWay;
Fn<bool()> _canToggleSpoiler;
Fn<bool(int, AttachActionType)> _actionAllowed;
style::cursor _cursor = style::cur_default;
std::vector<int> _order;
std::vector<QSize> _itemsShownDimensions;
@ -124,6 +127,8 @@ private:
rpl::event_stream<int> _thumbDeleted;
rpl::event_stream<int> _thumbChanged;
rpl::event_stream<int> _thumbModified;
rpl::event_stream<int> _thumbEditCoverRequested;
rpl::event_stream<int> _thumbClearCoverRequested;
rpl::event_stream<> _orderUpdated;
base::unique_qptr<PopupMenu> _menu;

View file

@ -35,7 +35,7 @@ AlbumThumbnail::AlbumThumbnail(
Fn<void()> deleteCallback)
: _st(st)
, _layout(layout)
, _fullPreview(file.preview)
, _fullPreview(file.videoCover ? file.videoCover->preview : file.preview)
, _shrinkSize(int(std::ceil(st::roundRadiusLarge / 1.4)))
, _isPhoto(file.type == PreparedFile::Type::Photo)
, _isVideo(file.type == PreparedFile::Type::Video)

View file

@ -36,8 +36,15 @@ ItemSingleMediaPreview::ItemSingleMediaPreview(
Fn<bool()> gifPaused,
not_null<HistoryItem*> item,
AttachControls::Type type)
: AbstractSingleMediaPreview(parent, st, type, [] { return true; })
: AbstractSingleMediaPreview(parent, st, type, [=](AttachActionType type) {
if (type == AttachActionType::EditCover) {
return _isVideoFile;
}
return true;
})
, _gifPaused(std::move(gifPaused))
, _isVideoFile(item->media()->document()
&& item->media()->document()->isVideoFile())
, _fullId(item->fullId()) {
const auto media = item->media();
Assert(media != nullptr);

View file

@ -56,6 +56,7 @@ private:
void startStreamedPlayer();
const Fn<bool()> _gifPaused;
const bool _isVideoFile;
const FullMsgId _fullId;
std::shared_ptr<::Data::PhotoMedia> _photoMedia;

View file

@ -42,6 +42,15 @@ bool PreparedFile::isSticker() const {
&& Core::IsMimeSticker(information->filemime);
}
bool PreparedFile::isVideoFile() const {
Expects(information != nullptr);
using Video = Ui::PreparedFileInformation::Video;
return (type == PreparedFile::Type::Video)
&& v::is<Video>(information->media)
&& !v::get<Video>(information->media).isGifv;
}
bool PreparedFile::isGifv() const {
Expects(information != nullptr);

View file

@ -74,12 +74,14 @@ struct PreparedFile {
[[nodiscard]] bool canBeInAlbumType(AlbumType album) const;
[[nodiscard]] AlbumType albumType(bool sendImagesAsPhotos) const;
[[nodiscard]] bool isSticker() const;
[[nodiscard]] bool isVideoFile() const;
[[nodiscard]] bool isGifv() const;
QString path;
QByteArray content;
int64 size = 0;
std::unique_ptr<Ui::PreparedFileInformation> information;
std::unique_ptr<PreparedFileInformation> information;
std::unique_ptr<PreparedFile> videoCover;
QImage preview;
QSize shownDimensions;
QSize originalDimensions;

View file

@ -11,6 +11,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Ui {
enum class AttachActionType {
ToggleSpoiler,
EditCover,
ClearCover,
};
enum class AttachButtonType {
Edit,
Delete,

View file

@ -19,7 +19,7 @@ SingleMediaPreview *SingleMediaPreview::Create(
const style::ComposeControls &st,
Fn<bool()> gifPaused,
const PreparedFile &file,
Fn<bool()> canToggleSpoiler,
Fn<bool(AttachActionType)> actionAllowed,
AttachControls::Type type) {
auto preview = QImage();
auto animated = false;
@ -32,7 +32,9 @@ SingleMediaPreview *SingleMediaPreview::Create(
hasModifications = !image->modifications.empty();
} else if (const auto video = std::get_if<PreparedFileInformation::Video>(
&file.information->media)) {
preview = video->thumbnail;
preview = file.videoCover
? file.videoCover->preview
: video->thumbnail;
animated = true;
animationPreview = video->isGifv;
}
@ -53,7 +55,7 @@ SingleMediaPreview *SingleMediaPreview::Create(
file.spoiler,
animationPreview ? file.path : QString(),
type,
std::move(canToggleSpoiler));
std::move(actionAllowed));
}
SingleMediaPreview::SingleMediaPreview(
@ -66,8 +68,8 @@ SingleMediaPreview::SingleMediaPreview(
bool spoiler,
const QString &animatedPreviewPath,
AttachControls::Type type,
Fn<bool()> canToggleSpoiler)
: AbstractSingleMediaPreview(parent, st, type, std::move(canToggleSpoiler))
Fn<bool(AttachActionType)> actionAllowed)
: AbstractSingleMediaPreview(parent, st, type, std::move(actionAllowed))
, _gifPaused(std::move(gifPaused))
, _sticker(sticker) {
Expects(!preview.isNull());

View file

@ -25,7 +25,7 @@ public:
const style::ComposeControls &st,
Fn<bool()> gifPaused,
const PreparedFile &file,
Fn<bool()> canToggleSpoiler,
Fn<bool(AttachActionType)> actionAllowed,
AttachControls::Type type = AttachControls::Type::Full);
SingleMediaPreview(
@ -38,7 +38,7 @@ public:
bool spoiler,
const QString &animatedPreviewPath,
AttachControls::Type type,
Fn<bool()> canToggleSpoiler);
Fn<bool(AttachActionType)> actionAllowed);
protected:
bool supportsSpoilers() const override;