From e05bb75b8a3702c5381efd9d0f8a9c01fb8a6852 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 29 Jan 2025 09:24:01 +0400 Subject: [PATCH] Allow sending videos with covers. --- Telegram/Resources/langs/lang.strings | 4 + Telegram/SourceFiles/api/api_common.h | 1 + Telegram/SourceFiles/api/api_editing.cpp | 12 +- Telegram/SourceFiles/api/api_media.cpp | 5 +- Telegram/SourceFiles/api/api_sending.cpp | 10 +- Telegram/SourceFiles/apiwrap.cpp | 44 +++++- .../SourceFiles/boxes/edit_caption_box.cpp | 9 +- Telegram/SourceFiles/boxes/send_files_box.cpp | 127 +++++++++++++++++- Telegram/SourceFiles/boxes/send_files_box.h | 6 +- .../chat_helpers/stickers_lottie.cpp | 1 + Telegram/SourceFiles/data/data_document.cpp | 16 +++ Telegram/SourceFiles/data/data_document.h | 6 + .../SourceFiles/data/data_media_types.cpp | 9 +- Telegram/SourceFiles/editor/editor_crop.cpp | 11 ++ .../SourceFiles/editor/photo_editor_common.h | 1 + .../editor/photo_editor_layer_widget.cpp | 20 ++- .../editor/photo_editor_layer_widget.h | 3 +- .../history/view/media/history_view_gif.cpp | 13 +- .../history/view/media/history_view_gif.h | 1 - .../view/media/history_view_media_common.cpp | 1 - .../main/main_session_settings.cpp | 5 +- .../media/view/media_view_overlay_widget.cpp | 29 +++- .../media/view/media_view_overlay_widget.h | 2 + .../SourceFiles/overview/overview_layout.cpp | 36 ++++- .../SourceFiles/overview/overview_layout.h | 2 + Telegram/SourceFiles/storage/file_upload.cpp | 93 ++++++++++++- Telegram/SourceFiles/storage/file_upload.h | 9 ++ .../SourceFiles/storage/localimageloader.cpp | 15 ++- .../SourceFiles/storage/localimageloader.h | 7 +- .../storage/storage_media_prepare.cpp | 26 +++- .../attach_abstract_single_media_preview.cpp | 47 ++++--- .../attach_abstract_single_media_preview.h | 8 +- .../ui/chat/attach/attach_album_preview.cpp | 34 +++-- .../ui/chat/attach/attach_album_preview.h | 15 ++- .../ui/chat/attach/attach_album_thumbnail.cpp | 2 +- .../attach_item_single_media_preview.cpp | 9 +- .../attach/attach_item_single_media_preview.h | 1 + .../ui/chat/attach/attach_prepare.cpp | 9 ++ .../ui/chat/attach/attach_prepare.h | 4 +- .../ui/chat/attach/attach_send_files_way.h | 6 + .../attach/attach_single_media_preview.cpp | 12 +- .../chat/attach/attach_single_media_preview.h | 4 +- 42 files changed, 571 insertions(+), 104 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 5d276d712..b8025092f 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -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"; diff --git a/Telegram/SourceFiles/api/api_common.h b/Telegram/SourceFiles/api/api_common.h index 81f098d67..77c30d095 100644 --- a/Telegram/SourceFiles/api/api_common.h +++ b/Telegram/SourceFiles/api/api_common.h @@ -74,6 +74,7 @@ struct MessageToSend { struct RemoteFileInfo { MTPInputFile file; std::optional thumb; + std::optional videoCover; std::vector attachedStickers; }; diff --git a/Telegram/SourceFiles/api/api_editing.cpp b/Telegram/SourceFiles/api/api_editing.cpp index 313d6bfef..4f5073a3b 100644 --- a/Telegram/SourceFiles/api/api_editing.cpp +++ b/Telegram/SourceFiles/api/api_editing.cpp @@ -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(); }; diff --git a/Telegram/SourceFiles/api/api_media.cpp b/Telegram/SourceFiles/api/api_media.cpp index 3bbc00f5f..f7331694b 100644 --- a/Telegram/SourceFiles/api/api_media.cpp +++ b/Telegram/SourceFiles/api/api_media.cpp @@ -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( ranges::to>(info.attachedStickers)), - MTPInputPhoto(), // video_cover + info.videoCover.value_or(MTPInputPhoto()), MTP_int(0), // video_timestamp MTP_int(ttlSeconds)); } diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp index 193e2e71b..80b6f1007 100644 --- a/Telegram/SourceFiles/api/api_sending.cpp +++ b/Telegram/SourceFiles/api/api_sending.cpp @@ -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(), // 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(), // alt_documents - MTPPhoto(), // video_cover + file->videoCover ? file->videoCover->photo : MTPPhoto(), MTPint(), // video_timestamp MTP_int(ttlSeconds)); } else if (file->type == SendMediaType::Round) { diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 8378af367..edaea5972 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -3578,6 +3578,18 @@ void ApiWrap::editMedia( file.path, file.content, std::move(file.information), + (file.videoCover + ? std::make_unique( + &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( + &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( &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; diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.cpp b/Telegram/SourceFiles/boxes/edit_caption_box.cpp index ae2cbc3d7..25501bd29 100644 --- a/Telegram/SourceFiles/boxes/edit_caption_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_caption_box.cpp @@ -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) { diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index 7eb1a676b..b7e987b6e 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -242,7 +242,7 @@ SendFilesBox::Block::Block( int till, Fn gifPaused, SendFilesWay way, - Fn canToggleSpoiler) + Fn 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 SendFilesBox::Block::itemModifyRequest() const { } } +rpl::producer SendFilesBox::Block::itemEditCoverRequest() const { + using namespace rpl::mappers; + + const auto preview = _preview.get(); + const auto from = _from; + if (_isAlbum) { + const auto album = static_cast(preview); + return album->thumbEditCoverRequested() | rpl::map(_1 + from); + } else if (_isSingleMedia) { + const auto media = static_cast(preview); + return media->editCoverRequests() | rpl::map_to(from); + } else { + return rpl::never(); + } +} + +rpl::producer SendFilesBox::Block::itemClearCoverRequest() const { + using namespace rpl::mappers; + + const auto preview = _preview.get(); + const auto from = _from; + if (_isAlbum) { + const auto album = static_cast(preview); + return album->thumbClearCoverRequested() | rpl::map(_1 + from); + } else if (_isSingleMedia) { + const auto media = static_cast(preview); + return media->clearCoverRequests() | rpl::map_to(from); + } else { + return rpl::never(); + } +} + rpl::producer<> SendFilesBox::Block::orderUpdated() const { if (_isAlbum) { const auto album = static_cast(_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( + &entry.information->media) + : nullptr; + if (!video) { + return; + } + auto old = std::shared_ptr( + std::move(entry.videoCover)); + entry.videoCover = std::make_unique( + 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( + 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([=]{ diff --git a/Telegram/SourceFiles/boxes/send_files_box.h b/Telegram/SourceFiles/boxes/send_files_box.h index 9b4123d1c..9a0fea06d 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.h +++ b/Telegram/SourceFiles/boxes/send_files_box.h @@ -153,7 +153,9 @@ private: int till, Fn gifPaused, Ui::SendFilesWay way, - Fn canToggleSpoiler); + Fn actionAllowed); Block(Block &&other) = default; Block &operator=(Block &&other) = default; @@ -164,6 +166,8 @@ private: [[nodiscard]] rpl::producer itemDeleteRequest() const; [[nodiscard]] rpl::producer itemReplaceRequest() const; [[nodiscard]] rpl::producer itemModifyRequest() const; + [[nodiscard]] rpl::producer itemEditCoverRequest() const; + [[nodiscard]] rpl::producer itemClearCoverRequest() const; [[nodiscard]] rpl::producer<> orderUpdated() const; void setSendWay(Ui::SendFilesWay way); diff --git a/Telegram/SourceFiles/chat_helpers/stickers_lottie.cpp b/Telegram/SourceFiles/chat_helpers/stickers_lottie.cpp index 1a189fa7f..04ceda97c 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_lottie.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_lottie.cpp @@ -329,6 +329,7 @@ not_null GenerateLocalSticker( path, QByteArray(), nullptr, + nullptr, SendMediaType::File, FileLoadTo(0, {}, {}, 0), {}, diff --git a/Telegram/SourceFiles/data/data_document.cpp b/Telegram/SourceFiles/data/data_document.cpp index c14d29bfc..c6befff04 100644 --- a/Telegram/SourceFiles/data/data_document.cpp +++ b/Telegram/SourceFiles/data/data_document.cpp @@ -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 local) { session().local().writeFileLocation(mediaKey(), _location); } } + +PhotoData *LookupVideoCover( + not_null 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; +} diff --git a/Telegram/SourceFiles/data/data_document.h b/Telegram/SourceFiles/data/data_document.h index 6aa21caa9..f91f828a1 100644 --- a/Telegram/SourceFiles/data/data_document.h +++ b/Telegram/SourceFiles/data/data_document.h @@ -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 document, + HistoryItem *item); + VoiceWaveform documentWaveformDecode(const QByteArray &encoded5bit); QByteArray documentWaveformEncode5bit(const VoiceWaveform &waveform); diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index a2c8134ff..02c129987 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -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 MediaFile::createView( message, realParent, _document, - _videoCover, _spoiler); } } else if (_document->isAnimation() || _document->isVideoFile()) { @@ -1342,7 +1345,6 @@ std::unique_ptr MediaFile::createView( message, realParent, _document, - _videoCover, _spoiler); } else if (_document->isTheme() && _document->hasThumbnail()) { return std::make_unique( @@ -2611,7 +2613,6 @@ std::unique_ptr MediaStory::createView( message, realParent, story->document(), - nullptr, spoiler); } } diff --git a/Telegram/SourceFiles/editor/editor_crop.cpp b/Telegram/SourceFiles/editor/editor_crop.cpp index 54f8369c2..b52888101 100644 --- a/Telegram/SourceFiles/editor/editor_crop.cpp +++ b/Telegram/SourceFiles/editor/editor_crop.cpp @@ -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) diff --git a/Telegram/SourceFiles/editor/photo_editor_common.h b/Telegram/SourceFiles/editor/photo_editor_common.h index 028f55c6f..b52b2110a 100644 --- a/Telegram/SourceFiles/editor/photo_editor_common.h +++ b/Telegram/SourceFiles/editor/photo_editor_common.h @@ -32,6 +32,7 @@ struct EditorData { TextWithEntities about; QString confirm; + QSize exactSize; CropType cropType = CropType::Rect; bool keepAspectRatio = false; }; diff --git a/Telegram/SourceFiles/editor/photo_editor_layer_widget.cpp b/Telegram/SourceFiles/editor/photo_editor_layer_widget.cpp index 33ed5e837..5143af395 100644 --- a/Telegram/SourceFiles/editor/photo_editor_layer_widget.cpp +++ b/Telegram/SourceFiles/editor/photo_editor_layer_widget.cpp @@ -26,22 +26,26 @@ void OpenWithPreparedFile( std::shared_ptr show, not_null file, int previewWidth, - Fn &&doneCallback) { + Fn &&doneCallback, + QSize exactSize) { using ImageInfo = Ui::PreparedFileInformation::Image; const auto image = std::get_if(&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(); + 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(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(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); } diff --git a/Telegram/SourceFiles/editor/photo_editor_layer_widget.h b/Telegram/SourceFiles/editor/photo_editor_layer_widget.h index f1c2fd445..0259a0fb7 100644 --- a/Telegram/SourceFiles/editor/photo_editor_layer_widget.h +++ b/Telegram/SourceFiles/editor/photo_editor_layer_widget.h @@ -34,7 +34,8 @@ void OpenWithPreparedFile( std::shared_ptr show, not_null file, int previewWidth, - Fn &&doneCallback); + Fn &&doneCallback, + QSize exactSize = {}); void PrepareProfilePhoto( not_null parent, diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index ad4f7ee8c..68546d6aa 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -143,11 +143,10 @@ Gif::Gif( not_null parent, not_null realParent, not_null 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 diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.h b/Telegram/SourceFiles/history/view/media/history_view_gif.h index b7ce6575d..f663d00d2 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.h +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.h @@ -54,7 +54,6 @@ public: not_null parent, not_null realParent, not_null document, - PhotoData *videoCover, bool spoiler); ~Gif(); diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp index 9364648a3..b899d9fc9 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp @@ -101,7 +101,6 @@ std::unique_ptr CreateAttach( parent, parent->data(), document, - photo, spoiler); } else if (document->isWallPaper() || document->isTheme()) { return std::make_unique( diff --git a/Telegram/SourceFiles/main/main_session_settings.cpp b/Telegram/SourceFiles/main/main_session_settings.cpp index 22a97db43..7d13cde46 100644 --- a/Telegram/SourceFiles/main/main_session_settings.cpp +++ b/Telegram/SourceFiles/main/main_session_settings.cpp @@ -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) diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index a3d6251fc..e752cc846 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -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 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 diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h index 82c7163e6..fb8efce90 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h @@ -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 _photoMedia; std::shared_ptr _documentMedia; + std::shared_ptr _videoCoverMedia; base::flat_set> _preloadPhotos; base::flat_set> _preloadDocuments; int _rotation = 0; diff --git a/Telegram/SourceFiles/overview/overview_layout.cpp b/Telegram/SourceFiles/overview/overview_layout.cpp index 4357a17eb..fd8468a53 100644 --- a/Telegram/SourceFiles/overview/overview_layout.cpp +++ b/Telegram/SourceFiles/overview/overview_layout.cpp @@ -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([=] { 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); } diff --git a/Telegram/SourceFiles/overview/overview_layout.h b/Telegram/SourceFiles/overview/overview_layout.h index 936379e36..489e2666e 100644 --- a/Telegram/SourceFiles/overview/overview_layout.h +++ b/Telegram/SourceFiles/overview/overview_layout.h @@ -316,7 +316,9 @@ private: void updateStatusText(); const not_null _data; + PhotoData *_videoCover = nullptr; mutable std::shared_ptr _dataMedia; + mutable std::shared_ptr _videoCoverMedia; StatusText _status; QString _duration; diff --git a/Telegram/SourceFiles/storage/file_upload.cpp b/Telegram/SourceFiles/storage/file_upload.cpp index 765bcb72e..39be04dc4 100644 --- a/Telegram/SourceFiles/storage/file_upload.cpp +++ b/Telegram/SourceFiles/storage/file_upload.cpp @@ -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 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(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 diff --git a/Telegram/SourceFiles/storage/file_upload.h b/Telegram/SourceFiles/storage/file_upload.h index bd0188f23..c4e5c9301 100644 --- a/Telegram/SourceFiles/storage/file_upload.h +++ b/Telegram/SourceFiles/storage/file_upload.h @@ -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 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 _pendingFromRemovedDcIndices; + base::flat_map _videoIdToCoverId; + base::flat_map _videoWaitingCover; + FullMsgId _pausedId; base::Timer _nextTimer, _stopSessionsTimer; diff --git a/Telegram/SourceFiles/storage/localimageloader.cpp b/Telegram/SourceFiles/storage/localimageloader.cpp index 8a9869139..476a1521b 100644 --- a/Telegram/SourceFiles/storage/localimageloader.cpp +++ b/Telegram/SourceFiles/storage/localimageloader.cpp @@ -468,6 +468,7 @@ FileLoadTask::FileLoadTask( const QString &filepath, const QByteArray &content, std::unique_ptr information, + std::unique_ptr 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 &FileLoadTask::peekResult() const { + return _result; } std::unique_ptr FileLoadTask::readMediaInformation( diff --git a/Telegram/SourceFiles/storage/localimageloader.h b/Telegram/SourceFiles/storage/localimageloader.h index e0cef82c2..744950699 100644 --- a/Telegram/SourceFiles/storage/localimageloader.h +++ b/Telegram/SourceFiles/storage/localimageloader.h @@ -196,6 +196,8 @@ struct FilePrepareResult { std::vector attachedStickers; + std::shared_ptr 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 information, + std::unique_ptr 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 &; private: static bool CheckForSong( @@ -281,6 +285,7 @@ private: const std::shared_ptr _album; QString _filepath; QByteArray _content; + std::unique_ptr _videoCover; std::unique_ptr _information; crl::time _duration = 0; VoiceWaveform _waveform; diff --git a/Telegram/SourceFiles/storage/storage_media_prepare.cpp b/Telegram/SourceFiles/storage/storage_media_prepare.cpp index 5e3421b48..290fae458 100644 --- a/Telegram/SourceFiles/storage/storage_media_prepare.cpp +++ b/Telegram/SourceFiles/storage/storage_media_prepare.cpp @@ -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(&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( + &file.information->media) + : nullptr; + apply(*cover, video ? video->thumbnail.size() : QSize()); + } } return applied; } diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.cpp index 3275c8a5a..0d57e7413 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.cpp @@ -34,10 +34,10 @@ AbstractSingleMediaPreview::AbstractSingleMediaPreview( QWidget *parent, const style::ComposeControls &st, AttachControls::Type type, - Fn canToggleSpoiler) + Fn 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(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( 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 { diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.h b/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.h index ba9595f28..2cdeae6c5 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.h @@ -27,7 +27,7 @@ public: QWidget *parent, const style::ComposeControls &st, AttachControls::Type type, - Fn canToggleSpoiler); + Fn 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 _canToggleSpoiler; + Fn _actionAllowed; bool _animated = false; QPixmap _preview; QPixmap _previewBlurred; @@ -89,6 +91,8 @@ private: const int _minThumbH; const base::unique_qptr _controls; rpl::event_stream<> _photoEditorRequests; + rpl::event_stream<> _editCoverRequests; + rpl::event_stream<> _clearCoverRequests; style::cursor _cursor = style::cur_default; bool _pressed = false; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.cpp index b02409084..28431418a 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.cpp @@ -38,11 +38,11 @@ AlbumPreview::AlbumPreview( const style::ComposeControls &st, gsl::span items, SendFilesWay way, - Fn canToggleSpoiler) + Fn 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 thumb, QPoint position) { - if (!_canToggleSpoiler() || !_sendWay.sendImagesAsPhotos()) { - return; - } _menu = base::make_unique_q( 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; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.h b/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.h index 9f1c9969d..9a740eb5b 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.h @@ -29,7 +29,7 @@ public: const style::ComposeControls &st, gsl::span items, SendFilesWay way, - Fn canToggleSpoiler); + Fn actionAllowed); ~AlbumPreview(); void setSendWay(SendFilesWay way); @@ -42,15 +42,18 @@ public: [[nodiscard]] rpl::producer thumbDeleted() const { return _thumbDeleted.events(); } - [[nodiscard]] rpl::producer thumbChanged() const { return _thumbChanged.events(); } - [[nodiscard]] rpl::producer thumbModified() const { return _thumbModified.events(); } - + [[nodiscard]] rpl::producer thumbEditCoverRequested() const { + return _thumbEditCoverRequested.events(); + } + [[nodiscard]] rpl::producer 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 _canToggleSpoiler; + Fn _actionAllowed; style::cursor _cursor = style::cur_default; std::vector _order; std::vector _itemsShownDimensions; @@ -124,6 +127,8 @@ private: rpl::event_stream _thumbDeleted; rpl::event_stream _thumbChanged; rpl::event_stream _thumbModified; + rpl::event_stream _thumbEditCoverRequested; + rpl::event_stream _thumbClearCoverRequested; rpl::event_stream<> _orderUpdated; base::unique_qptr _menu; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.cpp index 92025e3cc..562ae2d44 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.cpp @@ -35,7 +35,7 @@ AlbumThumbnail::AlbumThumbnail( Fn 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) diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_item_single_media_preview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_item_single_media_preview.cpp index 49eeacd46..02f3c9258 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_item_single_media_preview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_item_single_media_preview.cpp @@ -36,8 +36,15 @@ ItemSingleMediaPreview::ItemSingleMediaPreview( Fn gifPaused, not_null 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); diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_item_single_media_preview.h b/Telegram/SourceFiles/ui/chat/attach/attach_item_single_media_preview.h index 69c6604a4..080e77185 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_item_single_media_preview.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_item_single_media_preview.h @@ -56,6 +56,7 @@ private: void startStreamedPlayer(); const Fn _gifPaused; + const bool _isVideoFile; const FullMsgId _fullId; std::shared_ptr<::Data::PhotoMedia> _photoMedia; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp index 505fa455b..3c5e0e707 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp @@ -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