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_image" = "Choose an image";
"lng_choose_file" = "Choose a file"; "lng_choose_file" = "Choose a file";
"lng_choose_files" = "Choose Files"; "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_game_tag" = "Game";
"lng_context_new_window" = "Open in new window"; "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_disable_spoiler" = "Remove Spoiler";
"lng_context_make_paid" = "Make This Content Paid"; "lng_context_make_paid" = "Make This Content Paid";
"lng_context_change_price" = "Change Price"; "lng_context_change_price" = "Change Price";
"lng_context_edit_cover" = "Edit Cover";
"lng_context_clear_cover" = "Clear Cover";
"lng_context_mention" = "Mention"; "lng_context_mention" = "Mention";
"lng_context_search_from" = "Search messages"; "lng_context_search_from" = "Search messages";

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -242,7 +242,7 @@ SendFilesBox::Block::Block(
int till, int till,
Fn<bool()> gifPaused, Fn<bool()> gifPaused,
SendFilesWay way, SendFilesWay way,
Fn<bool()> canToggleSpoiler) Fn<bool(const Ui::PreparedFile &, Ui::AttachActionType)> actionAllowed)
: _items(items) : _items(items)
, _from(from) , _from(from)
, _till(till) { , _till(till) {
@ -260,7 +260,9 @@ SendFilesBox::Block::Block(
st, st,
my, my,
way, way,
std::move(canToggleSpoiler)); [=](int index, Ui::AttachActionType type) {
return actionAllowed((*_items)[from + index], type);
});
_preview.reset(preview); _preview.reset(preview);
} else { } else {
const auto media = Ui::SingleMediaPreview::Create( const auto media = Ui::SingleMediaPreview::Create(
@ -268,7 +270,9 @@ SendFilesBox::Block::Block(
st, st,
gifPaused, gifPaused,
first, first,
std::move(canToggleSpoiler)); [=](Ui::AttachActionType type) {
return actionAllowed((*_items)[from], type);
});
if (media) { if (media) {
_isSingleMedia = true; _isSingleMedia = true;
_preview.reset(media); _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 { rpl::producer<> SendFilesBox::Block::orderUpdated() const {
if (_isAlbum) { if (_isAlbum) {
const auto album = static_cast<Ui::AlbumPreview*>(_preview.get()); const auto album = static_cast<Ui::AlbumPreview*>(_preview.get());
@ -1008,7 +1044,16 @@ void SendFilesBox::pushBlock(int from, int till) {
till, till,
gifPaused, gifPaused,
_sendWay.current(), _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(); auto &block = _blocks.back();
const auto widget = _inner->add( const auto widget = _inner->add(
block.takeWidget(), block.takeWidget(),
@ -1129,7 +1174,79 @@ void SendFilesBox::pushBlock(int from, int till) {
show, show,
&_list.files[index], &_list.files[index],
st::sendMediaPreviewSize, 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()); }, widget->lifetime());
block.orderUpdated() | rpl::start_with_next([=]{ block.orderUpdated() | rpl::start_with_next([=]{

View file

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

View file

@ -329,6 +329,7 @@ not_null<DocumentData*> GenerateLocalSticker(
path, path,
QByteArray(), QByteArray(),
nullptr, nullptr,
nullptr,
SendMediaType::File, SendMediaType::File,
FileLoadTo(0, {}, {}, 0), 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_streaming.h"
#include "data/data_document_media.h" #include "data/data_document_media.h"
#include "data/data_reply_preview.h" #include "data/data_reply_preview.h"
#include "data/data_web_page.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "inline_bots/inline_bot_layout_item.h" #include "inline_bots/inline_bot_layout_item.h"
#include "main/main_session.h" #include "main/main_session.h"
@ -1881,3 +1882,18 @@ void DocumentData::collectLocalData(not_null<DocumentData*> local) {
session().local().writeFileLocation(mediaKey(), _location); 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 "data/data_cloud_file.h"
#include "core/file_location.h" #include "core/file_location.h"
class HistoryItem;
class PhotoData;
enum class ChatRestriction; enum class ChatRestriction;
class mtpFileLoader; class mtpFileLoader;
@ -398,6 +400,10 @@ private:
}; };
[[nodiscard]] PhotoData *LookupVideoCover(
not_null<DocumentData*> document,
HistoryItem *item);
VoiceWaveform documentWaveformDecode(const QByteArray &encoded5bit); VoiceWaveform documentWaveformDecode(const QByteArray &encoded5bit);
QByteArray documentWaveformEncode5bit(const VoiceWaveform &waveform); QByteArray documentWaveformEncode5bit(const VoiceWaveform &waveform);

View file

@ -1302,7 +1302,11 @@ bool MediaFile::updateSentMedia(const MTPMessageMedia &media) {
"or with ttl_seconds in updateSentMedia()")); "or with ttl_seconds in updateSentMedia()"));
return false; 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; return true;
} }
@ -1334,7 +1338,6 @@ std::unique_ptr<HistoryView::Media> MediaFile::createView(
message, message,
realParent, realParent,
_document, _document,
_videoCover,
_spoiler); _spoiler);
} }
} else if (_document->isAnimation() || _document->isVideoFile()) { } else if (_document->isAnimation() || _document->isVideoFile()) {
@ -1342,7 +1345,6 @@ std::unique_ptr<HistoryView::Media> MediaFile::createView(
message, message,
realParent, realParent,
_document, _document,
_videoCover,
_spoiler); _spoiler);
} else if (_document->isTheme() && _document->hasThumbnail()) { } else if (_document->isTheme() && _document->hasThumbnail()) {
return std::make_unique<HistoryView::ThemeDocument>( return std::make_unique<HistoryView::ThemeDocument>(
@ -2611,7 +2613,6 @@ std::unique_ptr<HistoryView::Media> MediaStory::createView(
message, message,
realParent, realParent,
story->document(), story->document(),
nullptr,
spoiler); spoiler);
} }
} }

View file

@ -42,6 +42,15 @@ QSizeF FlipSizeByRotation(const QSizeF &size, int angle) {
return (((angle / 90) % 2) == 1) ? size.transposed() : size; 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 } // namespace
Crop::Crop( Crop::Crop(
@ -60,6 +69,8 @@ Crop::Crop(
, _data(std::move(data)) , _data(std::move(data))
, _cropOriginal(modifications.crop.isValid() , _cropOriginal(modifications.crop.isValid()
? modifications.crop ? modifications.crop
: !_data.exactSize.isEmpty()
? OriginalCrop(_imageSize, _data.exactSize)
: QRectF(QPoint(), _imageSize)) : QRectF(QPoint(), _imageSize))
, _angle(modifications.angle) , _angle(modifications.angle)
, _flipped(modifications.flipped) , _flipped(modifications.flipped)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -335,6 +335,11 @@ void Uploader::upload(
if (file->type == SendMediaType::ThemeFile) { if (file->type == SendMediaType::ThemeFile) {
document->checkWallPaperProperties(); document->checkWallPaperProperties();
} }
if (file->videoCover) {
session().data().processPhoto(
file->videoCover->photo,
file->videoCover->photoThumbs);
}
} }
_queue.push_back({ itemId, file }); _queue.push_back({ itemId, file });
if (!_nextTimer.isActive()) { if (!_nextTimer.isActive()) {
@ -348,9 +353,26 @@ void Uploader::failed(FullMsgId itemId) {
const auto entry = std::move(*i); const auto entry = std::move(*i);
_queue.erase(i); _queue.erase(i);
notifyFailed(entry); 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); cancelRequests(itemId);
maybeFinishFront(); maybeFinishFront();
crl::on_main(this, [=] { crl::on_main(this, [=] {
maybeSend(); maybeSend();
}); });
@ -854,7 +876,8 @@ void Uploader::finishFront() {
MTP_int(entry.parts->size()), MTP_int(entry.parts->size()),
MTP_string(photoFilename), MTP_string(photoFilename),
MTP_bytes(md5)); MTP_bytes(md5));
_photoReady.fire({ auto ready = UploadedMedia{
.id = entry.file->id,
.fullId = entry.itemId, .fullId = entry.itemId,
.info = { .info = {
.file = file, .file = file,
@ -862,7 +885,13 @@ void Uploader::finishFront() {
}, },
.options = options, .options = options,
.edit = edit, .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 } else if (entry.file->type == SendMediaType::File
|| entry.file->type == SendMediaType::ThemeFile || entry.file->type == SendMediaType::ThemeFile
|| entry.file->type == SendMediaType::Audio || entry.file->type == SendMediaType::Audio
@ -892,7 +921,8 @@ void Uploader::finishFront() {
MTP_string(thumbFilename), MTP_string(thumbFilename),
MTP_bytes(thumbMd5)); MTP_bytes(thumbMd5));
}(); }();
_documentReady.fire({ auto ready = UploadedMedia{
.id = entry.file->id,
.fullId = entry.itemId, .fullId = entry.itemId,
.info = { .info = {
.file = file, .file = file,
@ -901,7 +931,12 @@ void Uploader::finishFront() {
}, },
.options = options, .options = options,
.edit = edit, .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) { } else if (entry.file->type == SendMediaType::Secure) {
_secureReady.fire({ _secureReady.fire({
entry.itemId, entry.itemId,
@ -916,4 +951,54 @@ void Uploader::partFailed(const MTP::Error &error, mtpRequestId requestId) {
failed(request.itemId); 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 } // namespace Storage

View file

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

View file

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

View file

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

View file

@ -362,10 +362,22 @@ void UpdateImageDetails(
bool ApplyModifications(PreparedList &list) { bool ApplyModifications(PreparedList &list) {
auto applied = false; 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 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) { if (!image || !image->modifications) {
continue; return;
} }
applied = true; applied = true;
file.path = QString(); file.path = QString();
@ -373,6 +385,16 @@ bool ApplyModifications(PreparedList &list) {
image->data = Editor::ImageModified( image->data = Editor::ImageModified(
std::move(image->data), std::move(image->data),
image->modifications); 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; return applied;
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -36,8 +36,15 @@ ItemSingleMediaPreview::ItemSingleMediaPreview(
Fn<bool()> gifPaused, Fn<bool()> gifPaused,
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
AttachControls::Type type) 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)) , _gifPaused(std::move(gifPaused))
, _isVideoFile(item->media()->document()
&& item->media()->document()->isVideoFile())
, _fullId(item->fullId()) { , _fullId(item->fullId()) {
const auto media = item->media(); const auto media = item->media();
Assert(media != nullptr); Assert(media != nullptr);

View file

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

View file

@ -42,6 +42,15 @@ bool PreparedFile::isSticker() const {
&& Core::IsMimeSticker(information->filemime); && 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 { bool PreparedFile::isGifv() const {
Expects(information != nullptr); Expects(information != nullptr);

View file

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

View file

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

View file

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

View file

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