mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-13 04:37:11 +02:00
Allow sending videos with covers.
This commit is contained in:
parent
6a415cf232
commit
e05bb75b8a
42 changed files with 571 additions and 104 deletions
|
@ -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";
|
||||
|
|
|
@ -74,6 +74,7 @@ struct MessageToSend {
|
|||
struct RemoteFileInfo {
|
||||
MTPInputFile file;
|
||||
std::optional<MTPInputFile> thumb;
|
||||
std::optional<MTPInputPhoto> videoCover;
|
||||
std::vector<MTPInputDocument> attachedStickers;
|
||||
};
|
||||
|
||||
|
|
|
@ -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(); };
|
||||
|
|
|
@ -111,7 +111,8 @@ MTPInputMedia PrepareUploadedDocument(
|
|||
| (info.thumb ? Flag::f_thumb : Flag())
|
||||
| (item->groupId() ? Flag::f_nosound_video : Flag())
|
||||
| (info.attachedStickers.empty() ? Flag::f_stickers : Flag())
|
||||
| (ttlSeconds ? Flag::f_ttl_seconds : Flag());
|
||||
| (ttlSeconds ? Flag::f_ttl_seconds : Flag())
|
||||
| (info.videoCover ? Flag::f_video_cover : Flag());
|
||||
const auto document = item->media()->document();
|
||||
return MTP_inputMediaUploadedDocument(
|
||||
MTP_flags(flags),
|
||||
|
@ -121,7 +122,7 @@ MTPInputMedia PrepareUploadedDocument(
|
|||
ComposeSendingDocumentAttributes(document),
|
||||
MTP_vector<MTPInputDocument>(
|
||||
ranges::to<QVector<MTPInputDocument>>(info.attachedStickers)),
|
||||
MTPInputPhoto(), // video_cover
|
||||
info.videoCover.value_or(MTPInputPhoto()),
|
||||
MTP_int(0), // video_timestamp
|
||||
MTP_int(ttlSeconds));
|
||||
}
|
||||
|
|
|
@ -549,10 +549,11 @@ void SendConfirmedFile(
|
|||
using Flag = MTPDmessageMediaDocument::Flag;
|
||||
return MTP_messageMediaDocument(
|
||||
MTP_flags(Flag::f_document
|
||||
| (file->spoiler ? Flag::f_spoiler : Flag())),
|
||||
| (file->spoiler ? Flag::f_spoiler : Flag())
|
||||
| (file->videoCover ? Flag::f_video_cover : Flag())),
|
||||
file->document,
|
||||
MTPVector<MTPDocument>(), // alt_documents
|
||||
MTPPhoto(), // video_cover
|
||||
file->videoCover ? file->videoCover->photo : MTPPhoto(),
|
||||
MTPint(), // video_timestamp
|
||||
MTPint());
|
||||
} else if (file->type == SendMediaType::Audio) {
|
||||
|
@ -561,10 +562,11 @@ void SendConfirmedFile(
|
|||
return MTP_messageMediaDocument(
|
||||
MTP_flags(Flag::f_document
|
||||
| Flag::f_voice
|
||||
| (ttlSeconds ? Flag::f_ttl_seconds : Flag())),
|
||||
| (ttlSeconds ? Flag::f_ttl_seconds : Flag())
|
||||
| (file->videoCover ? Flag::f_video_cover : Flag())),
|
||||
file->document,
|
||||
MTPVector<MTPDocument>(), // alt_documents
|
||||
MTPPhoto(), // video_cover
|
||||
file->videoCover ? file->videoCover->photo : MTPPhoto(),
|
||||
MTPint(), // video_timestamp
|
||||
MTP_int(ttlSeconds));
|
||||
} else if (file->type == SendMediaType::Round) {
|
||||
|
|
|
@ -3578,6 +3578,18 @@ void ApiWrap::editMedia(
|
|||
file.path,
|
||||
file.content,
|
||||
std::move(file.information),
|
||||
(file.videoCover
|
||||
? std::make_unique<FileLoadTask>(
|
||||
&session(),
|
||||
file.videoCover->path,
|
||||
file.videoCover->content,
|
||||
std::move(file.videoCover->information),
|
||||
nullptr,
|
||||
SendMediaType::Photo,
|
||||
to,
|
||||
TextWithTags(),
|
||||
false)
|
||||
: nullptr),
|
||||
type,
|
||||
to,
|
||||
caption,
|
||||
|
@ -3619,6 +3631,19 @@ void ApiWrap::sendFiles(
|
|||
file.path,
|
||||
file.content,
|
||||
std::move(file.information),
|
||||
(file.videoCover
|
||||
? std::make_unique<FileLoadTask>(
|
||||
&session(),
|
||||
file.videoCover->path,
|
||||
file.videoCover->content,
|
||||
std::move(file.videoCover->information),
|
||||
nullptr,
|
||||
SendMediaType::Photo,
|
||||
to,
|
||||
TextWithTags(),
|
||||
false,
|
||||
nullptr)
|
||||
: nullptr),
|
||||
uploadWithType,
|
||||
to,
|
||||
caption,
|
||||
|
@ -3644,11 +3669,13 @@ void ApiWrap::sendFile(
|
|||
auto caption = TextWithTags();
|
||||
const auto spoiler = false;
|
||||
const auto information = nullptr;
|
||||
const auto videoCover = nullptr;
|
||||
_fileLoader->addTask(std::make_unique<FileLoadTask>(
|
||||
&session(),
|
||||
QString(),
|
||||
fileContent,
|
||||
information,
|
||||
videoCover,
|
||||
type,
|
||||
to,
|
||||
caption,
|
||||
|
@ -4142,19 +4169,30 @@ void ApiWrap::uploadAlbumMedia(
|
|||
return;
|
||||
}
|
||||
const auto &fields = document->c_document();
|
||||
const auto mtpCover = data.vvideo_cover();
|
||||
const auto cover = (mtpCover && mtpCover->type() == mtpc_photo)
|
||||
? &(mtpCover->c_photo())
|
||||
: (const MTPDphoto*)nullptr;
|
||||
using Flag = MTPDinputMediaDocument::Flag;
|
||||
const auto flags = Flag()
|
||||
| (data.vttl_seconds() ? Flag::f_ttl_seconds : Flag())
|
||||
| (spoiler ? Flag::f_spoiler : Flag());
|
||||
| (spoiler ? Flag::f_spoiler : Flag())
|
||||
| (data.vvideo_timestamp() ? Flag::f_video_timestamp : Flag())
|
||||
| (cover ? Flag::f_video_cover : Flag());
|
||||
const auto media = MTP_inputMediaDocument(
|
||||
MTP_flags(flags),
|
||||
MTP_inputDocument(
|
||||
fields.vid(),
|
||||
fields.vaccess_hash(),
|
||||
fields.vfile_reference()),
|
||||
MTPInputPhoto(), // video_cover
|
||||
(cover
|
||||
? MTP_inputPhoto(
|
||||
cover->vid(),
|
||||
cover->vaccess_hash(),
|
||||
cover->vfile_reference())
|
||||
: MTPInputPhoto()),
|
||||
MTP_int(data.vvideo_timestamp().value_or_empty()),
|
||||
MTP_int(data.vttl_seconds().value_or_empty()),
|
||||
MTPint(), // video_timestamp
|
||||
MTPstring()); // query
|
||||
sendAlbumWithUploaded(item, groupId, media);
|
||||
} break;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -242,7 +242,7 @@ SendFilesBox::Block::Block(
|
|||
int till,
|
||||
Fn<bool()> gifPaused,
|
||||
SendFilesWay way,
|
||||
Fn<bool()> canToggleSpoiler)
|
||||
Fn<bool(const Ui::PreparedFile &, Ui::AttachActionType)> actionAllowed)
|
||||
: _items(items)
|
||||
, _from(from)
|
||||
, _till(till) {
|
||||
|
@ -260,7 +260,9 @@ SendFilesBox::Block::Block(
|
|||
st,
|
||||
my,
|
||||
way,
|
||||
std::move(canToggleSpoiler));
|
||||
[=](int index, Ui::AttachActionType type) {
|
||||
return actionAllowed((*_items)[from + index], type);
|
||||
});
|
||||
_preview.reset(preview);
|
||||
} else {
|
||||
const auto media = Ui::SingleMediaPreview::Create(
|
||||
|
@ -268,7 +270,9 @@ SendFilesBox::Block::Block(
|
|||
st,
|
||||
gifPaused,
|
||||
first,
|
||||
std::move(canToggleSpoiler));
|
||||
[=](Ui::AttachActionType type) {
|
||||
return actionAllowed((*_items)[from], type);
|
||||
});
|
||||
if (media) {
|
||||
_isSingleMedia = true;
|
||||
_preview.reset(media);
|
||||
|
@ -344,6 +348,38 @@ rpl::producer<int> SendFilesBox::Block::itemModifyRequest() const {
|
|||
}
|
||||
}
|
||||
|
||||
rpl::producer<int> SendFilesBox::Block::itemEditCoverRequest() const {
|
||||
using namespace rpl::mappers;
|
||||
|
||||
const auto preview = _preview.get();
|
||||
const auto from = _from;
|
||||
if (_isAlbum) {
|
||||
const auto album = static_cast<Ui::AlbumPreview*>(preview);
|
||||
return album->thumbEditCoverRequested() | rpl::map(_1 + from);
|
||||
} else if (_isSingleMedia) {
|
||||
const auto media = static_cast<Ui::SingleMediaPreview*>(preview);
|
||||
return media->editCoverRequests() | rpl::map_to(from);
|
||||
} else {
|
||||
return rpl::never<int>();
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<int> SendFilesBox::Block::itemClearCoverRequest() const {
|
||||
using namespace rpl::mappers;
|
||||
|
||||
const auto preview = _preview.get();
|
||||
const auto from = _from;
|
||||
if (_isAlbum) {
|
||||
const auto album = static_cast<Ui::AlbumPreview*>(preview);
|
||||
return album->thumbClearCoverRequested() | rpl::map(_1 + from);
|
||||
} else if (_isSingleMedia) {
|
||||
const auto media = static_cast<Ui::SingleMediaPreview*>(preview);
|
||||
return media->clearCoverRequests() | rpl::map_to(from);
|
||||
} else {
|
||||
return rpl::never<int>();
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<> SendFilesBox::Block::orderUpdated() const {
|
||||
if (_isAlbum) {
|
||||
const auto album = static_cast<Ui::AlbumPreview*>(_preview.get());
|
||||
|
@ -1008,7 +1044,16 @@ void SendFilesBox::pushBlock(int from, int till) {
|
|||
till,
|
||||
gifPaused,
|
||||
_sendWay.current(),
|
||||
[=] { return !hasPrice(); });
|
||||
[=](const Ui::PreparedFile &file, Ui::AttachActionType type) {
|
||||
return (type == Ui::AttachActionType::ToggleSpoiler)
|
||||
? !hasPrice()
|
||||
: (type == Ui::AttachActionType::EditCover)
|
||||
? (file.isVideoFile()
|
||||
&& _captionToPeer
|
||||
&& (_captionToPeer->isBroadcast()
|
||||
|| _captionToPeer->isSelf()))
|
||||
: (file.videoCover != nullptr);
|
||||
});
|
||||
auto &block = _blocks.back();
|
||||
const auto widget = _inner->add(
|
||||
block.takeWidget(),
|
||||
|
@ -1129,7 +1174,79 @@ void SendFilesBox::pushBlock(int from, int till) {
|
|||
show,
|
||||
&_list.files[index],
|
||||
st::sendMediaPreviewSize,
|
||||
[=] { refreshAllAfterChanges(from); });
|
||||
[=](bool ok) { if (ok) refreshAllAfterChanges(from); });
|
||||
}, widget->lifetime());
|
||||
|
||||
block.itemEditCoverRequest(
|
||||
) | rpl::start_with_next([=, show = _show](int index) {
|
||||
applyBlockChanges();
|
||||
|
||||
const auto replace = [=](Ui::PreparedList list) {
|
||||
if (list.files.empty()) {
|
||||
return;
|
||||
}
|
||||
auto &entry = _list.files[index];
|
||||
const auto video = entry.information
|
||||
? std::get_if<Ui::PreparedFileInformation::Video>(
|
||||
&entry.information->media)
|
||||
: nullptr;
|
||||
if (!video) {
|
||||
return;
|
||||
}
|
||||
auto old = std::shared_ptr<Ui::PreparedFile>(
|
||||
std::move(entry.videoCover));
|
||||
entry.videoCover = std::make_unique<Ui::PreparedFile>(
|
||||
std::move(list.files.front()));
|
||||
Editor::OpenWithPreparedFile(
|
||||
this,
|
||||
show,
|
||||
entry.videoCover.get(),
|
||||
st::sendMediaPreviewSize,
|
||||
crl::guard(this, [=](bool ok) {
|
||||
if (!ok) {
|
||||
_list.files[index].videoCover = old
|
||||
? std::make_unique<Ui::PreparedFile>(
|
||||
std::move(*old))
|
||||
: nullptr;
|
||||
}
|
||||
refreshAllAfterChanges(from);
|
||||
}),
|
||||
video->thumbnail.size());
|
||||
};
|
||||
const auto checkResult = [=](const Ui::PreparedList &list) {
|
||||
if (list.files.empty()) {
|
||||
return true;
|
||||
}
|
||||
if (list.files.front().type != Ui::PreparedFile::Type::Photo) {
|
||||
show->showToast(tr::lng_choose_cover_bad(tr::now));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
const auto callback = [=](FileDialog::OpenResult &&result) {
|
||||
const auto premium = _show->session().premium();
|
||||
FileDialogCallback(
|
||||
std::move(result),
|
||||
checkResult,
|
||||
replace,
|
||||
premium,
|
||||
show);
|
||||
};
|
||||
|
||||
FileDialog::GetOpenPath(
|
||||
this,
|
||||
tr::lng_choose_cover(tr::now),
|
||||
FileDialog::ImagesFilter(),
|
||||
crl::guard(this, callback));
|
||||
}, widget->lifetime());
|
||||
|
||||
block.itemClearCoverRequest(
|
||||
) | rpl::start_with_next([=](int index) {
|
||||
applyBlockChanges();
|
||||
refreshAllAfterChanges(from, [&] {
|
||||
auto &entry = _list.files[index];
|
||||
entry.videoCover = nullptr;
|
||||
});
|
||||
}, widget->lifetime());
|
||||
|
||||
block.orderUpdated() | rpl::start_with_next([=]{
|
||||
|
|
|
@ -153,7 +153,9 @@ private:
|
|||
int till,
|
||||
Fn<bool()> gifPaused,
|
||||
Ui::SendFilesWay way,
|
||||
Fn<bool()> canToggleSpoiler);
|
||||
Fn<bool(
|
||||
const Ui::PreparedFile &,
|
||||
Ui::AttachActionType)> actionAllowed);
|
||||
Block(Block &&other) = default;
|
||||
Block &operator=(Block &&other) = default;
|
||||
|
||||
|
@ -164,6 +166,8 @@ private:
|
|||
[[nodiscard]] rpl::producer<int> itemDeleteRequest() const;
|
||||
[[nodiscard]] rpl::producer<int> itemReplaceRequest() const;
|
||||
[[nodiscard]] rpl::producer<int> itemModifyRequest() const;
|
||||
[[nodiscard]] rpl::producer<int> itemEditCoverRequest() const;
|
||||
[[nodiscard]] rpl::producer<int> itemClearCoverRequest() const;
|
||||
[[nodiscard]] rpl::producer<> orderUpdated() const;
|
||||
|
||||
void setSendWay(Ui::SendFilesWay way);
|
||||
|
|
|
@ -329,6 +329,7 @@ not_null<DocumentData*> GenerateLocalSticker(
|
|||
path,
|
||||
QByteArray(),
|
||||
nullptr,
|
||||
nullptr,
|
||||
SendMediaType::File,
|
||||
FileLoadTo(0, {}, {}, 0),
|
||||
{},
|
||||
|
|
|
@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_streaming.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_reply_preview.h"
|
||||
#include "data/data_web_page.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "inline_bots/inline_bot_layout_item.h"
|
||||
#include "main/main_session.h"
|
||||
|
@ -1881,3 +1882,18 @@ void DocumentData::collectLocalData(not_null<DocumentData*> local) {
|
|||
session().local().writeFileLocation(mediaKey(), _location);
|
||||
}
|
||||
}
|
||||
|
||||
PhotoData *LookupVideoCover(
|
||||
not_null<DocumentData*> document,
|
||||
HistoryItem *item) {
|
||||
const auto media = item ? item->media() : nullptr;
|
||||
if (const auto webpage = media ? media->webpage() : nullptr) {
|
||||
if (webpage->document == document && webpage->photoIsVideoCover) {
|
||||
return webpage->photo;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
return (media && media->document() == document)
|
||||
? media->videoCover()
|
||||
: nullptr;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_cloud_file.h"
|
||||
#include "core/file_location.h"
|
||||
|
||||
class HistoryItem;
|
||||
class PhotoData;
|
||||
enum class ChatRestriction;
|
||||
class mtpFileLoader;
|
||||
|
||||
|
@ -398,6 +400,10 @@ private:
|
|||
|
||||
};
|
||||
|
||||
[[nodiscard]] PhotoData *LookupVideoCover(
|
||||
not_null<DocumentData*> document,
|
||||
HistoryItem *item);
|
||||
|
||||
VoiceWaveform documentWaveformDecode(const QByteArray &encoded5bit);
|
||||
QByteArray documentWaveformEncode5bit(const VoiceWaveform &waveform);
|
||||
|
||||
|
|
|
@ -1302,7 +1302,11 @@ bool MediaFile::updateSentMedia(const MTPMessageMedia &media) {
|
|||
"or with ttl_seconds in updateSentMedia()"));
|
||||
return false;
|
||||
}
|
||||
parent()->history()->owner().documentConvert(_document, *content);
|
||||
const auto owner = &parent()->history()->owner();
|
||||
owner->documentConvert(_document, *content);
|
||||
if (const auto cover = _videoCover ? data.vvideo_cover() : nullptr) {
|
||||
owner->photoConvert(_videoCover, *cover);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1334,7 +1338,6 @@ std::unique_ptr<HistoryView::Media> MediaFile::createView(
|
|||
message,
|
||||
realParent,
|
||||
_document,
|
||||
_videoCover,
|
||||
_spoiler);
|
||||
}
|
||||
} else if (_document->isAnimation() || _document->isVideoFile()) {
|
||||
|
@ -1342,7 +1345,6 @@ std::unique_ptr<HistoryView::Media> MediaFile::createView(
|
|||
message,
|
||||
realParent,
|
||||
_document,
|
||||
_videoCover,
|
||||
_spoiler);
|
||||
} else if (_document->isTheme() && _document->hasThumbnail()) {
|
||||
return std::make_unique<HistoryView::ThemeDocument>(
|
||||
|
@ -2611,7 +2613,6 @@ std::unique_ptr<HistoryView::Media> MediaStory::createView(
|
|||
message,
|
||||
realParent,
|
||||
story->document(),
|
||||
nullptr,
|
||||
spoiler);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -32,6 +32,7 @@ struct EditorData {
|
|||
|
||||
TextWithEntities about;
|
||||
QString confirm;
|
||||
QSize exactSize;
|
||||
CropType cropType = CropType::Rect;
|
||||
bool keepAspectRatio = false;
|
||||
};
|
||||
|
|
|
@ -26,22 +26,26 @@ void OpenWithPreparedFile(
|
|||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<Ui::PreparedFile*> file,
|
||||
int previewWidth,
|
||||
Fn<void()> &&doneCallback) {
|
||||
Fn<void(bool ok)> &&doneCallback,
|
||||
QSize exactSize) {
|
||||
using ImageInfo = Ui::PreparedFileInformation::Image;
|
||||
const auto image = std::get_if<ImageInfo>(&file->information->media);
|
||||
if (!image) {
|
||||
doneCallback(false);
|
||||
return;
|
||||
}
|
||||
const auto photoType = (file->type == Ui::PreparedFile::Type::Photo);
|
||||
const auto modifiedFileType = (file->type == Ui::PreparedFile::Type::File)
|
||||
&& !image->modifications.empty();
|
||||
if (!photoType && !modifiedFileType) {
|
||||
doneCallback(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto sideLimit = PhotoSideLimit();
|
||||
auto callback = [=, done = std::move(doneCallback)](
|
||||
const PhotoModifications &mods) {
|
||||
const auto accepted = std::make_shared<bool>();
|
||||
auto callback = [=](const PhotoModifications &mods) {
|
||||
*accepted = true;
|
||||
image->modifications = mods;
|
||||
Storage::UpdateImageDetails(*file, previewWidth, sideLimit);
|
||||
{
|
||||
|
@ -51,7 +55,7 @@ void OpenWithPreparedFile(
|
|||
? PreparedFile::Type::Photo
|
||||
: PreparedFile::Type::File;
|
||||
}
|
||||
done();
|
||||
doneCallback(true);
|
||||
};
|
||||
auto copy = image->data;
|
||||
const auto fileImage = std::make_shared<Image>(std::move(copy));
|
||||
|
@ -60,10 +64,16 @@ void OpenWithPreparedFile(
|
|||
show,
|
||||
show,
|
||||
fileImage,
|
||||
image->modifications);
|
||||
image->modifications,
|
||||
EditorData{ .exactSize = exactSize, .keepAspectRatio = true });
|
||||
const auto raw = editor.get();
|
||||
auto layer = std::make_unique<LayerWidget>(parent, std::move(editor));
|
||||
InitEditorLayer(layer.get(), raw, std::move(callback));
|
||||
QObject::connect(layer.get(), &QObject::destroyed, [=] {
|
||||
if (!*accepted) {
|
||||
doneCallback(false);
|
||||
}
|
||||
});
|
||||
show->showLayer(std::move(layer), Ui::LayerOption::KeepOther);
|
||||
}
|
||||
|
||||
|
|
|
@ -34,7 +34,8 @@ void OpenWithPreparedFile(
|
|||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<Ui::PreparedFile*> file,
|
||||
int previewWidth,
|
||||
Fn<void()> &&doneCallback);
|
||||
Fn<void(bool ok)> &&doneCallback,
|
||||
QSize exactSize = {});
|
||||
|
||||
void PrepareProfilePhoto(
|
||||
not_null<QWidget*> parent,
|
||||
|
|
|
@ -143,11 +143,10 @@ Gif::Gif(
|
|||
not_null<Element*> parent,
|
||||
not_null<HistoryItem*> realParent,
|
||||
not_null<DocumentData*> document,
|
||||
PhotoData *videoCover,
|
||||
bool spoiler)
|
||||
: File(parent, realParent)
|
||||
, _data(document)
|
||||
, _videoCover(videoCover)
|
||||
, _videoCover(LookupVideoCover(document, realParent))
|
||||
, _storyId(realParent->media()
|
||||
? realParent->media()->storyId()
|
||||
: FullStoryId())
|
||||
|
@ -1805,12 +1804,18 @@ void Gif::validateGroupedCache(
|
|||
|
||||
ensureDataMediaCreated();
|
||||
|
||||
const auto good = _dataMedia->goodThumbnail();
|
||||
const auto thumb = _dataMedia->thumbnail();
|
||||
const auto good = _videoCoverMedia
|
||||
? _videoCoverMedia->image(Data::PhotoSize::Large)
|
||||
: _dataMedia->goodThumbnail();
|
||||
const auto thumb = _videoCoverMedia
|
||||
? nullptr
|
||||
: _dataMedia->thumbnail();
|
||||
const auto image = good
|
||||
? good
|
||||
: thumb
|
||||
? thumb
|
||||
: _videoCoverMedia
|
||||
? _videoCoverMedia->thumbnailInline()
|
||||
: _dataMedia->thumbnailInline();
|
||||
const auto blur = !good
|
||||
&& (!thumb
|
||||
|
|
|
@ -54,7 +54,6 @@ public:
|
|||
not_null<Element*> parent,
|
||||
not_null<HistoryItem*> realParent,
|
||||
not_null<DocumentData*> document,
|
||||
PhotoData *videoCover,
|
||||
bool spoiler);
|
||||
~Gif();
|
||||
|
||||
|
|
|
@ -101,7 +101,6 @@ std::unique_ptr<Media> CreateAttach(
|
|||
parent,
|
||||
parent->data(),
|
||||
document,
|
||||
photo,
|
||||
spoiler);
|
||||
} else if (document->isWallPaper() || document->isTheme()) {
|
||||
return std::make_unique<ThemeDocument>(
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -2328,11 +2328,22 @@ void OverlayWidget::assignMediaPointer(DocumentData *document) {
|
|||
_quality = Core::App().settings().videoQuality();
|
||||
_chosenQuality = _document->chooseQuality(_message, _quality);
|
||||
_documentMedia = _document->createMediaView();
|
||||
_documentMedia->goodThumbnailWanted();
|
||||
_documentMedia->thumbnailWanted(fileOrigin());
|
||||
_videoCover = LookupVideoCover(_document, _message);
|
||||
if (_videoCover) {
|
||||
_videoCoverMedia = _videoCover->createMediaView();
|
||||
_videoCoverMedia->wanted(
|
||||
Data::PhotoSize::Large,
|
||||
fileOrigin());
|
||||
} else {
|
||||
_videoCoverMedia = nullptr;
|
||||
_documentMedia->goodThumbnailWanted();
|
||||
_documentMedia->thumbnailWanted(fileOrigin());
|
||||
}
|
||||
} else {
|
||||
_chosenQuality = nullptr;
|
||||
_documentMedia = nullptr;
|
||||
_videoCover = nullptr;
|
||||
_videoCoverMedia = nullptr;
|
||||
}
|
||||
_documentLoadingTo = QString();
|
||||
}
|
||||
|
@ -2346,6 +2357,8 @@ void OverlayWidget::assignMediaPointer(not_null<PhotoData*> photo) {
|
|||
_document = nullptr;
|
||||
_documentMedia = nullptr;
|
||||
_documentLoadingTo = QString();
|
||||
_videoCover = nullptr;
|
||||
_videoCoverMedia = nullptr;
|
||||
if (_photo != photo) {
|
||||
_flip = {};
|
||||
_photo = photo;
|
||||
|
@ -3988,13 +4001,19 @@ void OverlayWidget::initStreamingThumbnail() {
|
|||
}
|
||||
return thumbnail;
|
||||
};
|
||||
const auto good = _document
|
||||
const auto good = _videoCover
|
||||
? _videoCoverMedia->image(Data::PhotoSize::Large)
|
||||
: _document
|
||||
? _documentMedia->goodThumbnail()
|
||||
: _photoMedia->image(Data::PhotoSize::Large);
|
||||
const auto thumbnail = _document
|
||||
const auto thumbnail = _videoCover
|
||||
? _videoCoverMedia->image(Data::PhotoSize::Small)
|
||||
: _document
|
||||
? _documentMedia->thumbnail()
|
||||
: computePhotoThumbnail();
|
||||
const auto blurred = _document
|
||||
const auto blurred = _videoCover
|
||||
? _videoCoverMedia->thumbnailInline()
|
||||
: _document
|
||||
? _documentMedia->thumbnailInline()
|
||||
: _photoMedia->thumbnailInline();
|
||||
const auto size = _photo
|
||||
|
|
|
@ -561,10 +561,12 @@ private:
|
|||
PhotoData *_photo = nullptr;
|
||||
DocumentData *_document = nullptr;
|
||||
DocumentData *_chosenQuality = nullptr;
|
||||
PhotoData *_videoCover = nullptr;
|
||||
Media::VideoQuality _quality;
|
||||
QString _documentLoadingTo;
|
||||
std::shared_ptr<Data::PhotoMedia> _photoMedia;
|
||||
std::shared_ptr<Data::DocumentMedia> _documentMedia;
|
||||
std::shared_ptr<Data::PhotoMedia> _videoCoverMedia;
|
||||
base::flat_set<std::shared_ptr<Data::PhotoMedia>> _preloadPhotos;
|
||||
base::flat_set<std::shared_ptr<Data::DocumentMedia>> _preloadDocuments;
|
||||
int _rotation = 0;
|
||||
|
|
|
@ -486,6 +486,7 @@ Video::Video(
|
|||
MediaOptions options)
|
||||
: RadialProgressItem(delegate, parent)
|
||||
, _data(video)
|
||||
, _videoCover(LookupVideoCover(video, parent))
|
||||
, _duration(Ui::FormatDurationText(_data->duration() / 1000))
|
||||
, _spoiler(options.spoiler ? std::make_unique<Ui::SpoilerAnimation>([=] {
|
||||
delegate->repaintItem(this);
|
||||
|
@ -493,7 +494,13 @@ Video::Video(
|
|||
, _pinned(options.pinned)
|
||||
, _story(options.story) {
|
||||
setDocumentLinks(_data);
|
||||
_data->loadThumbnail(parent->fullId());
|
||||
if (!_videoCover) {
|
||||
_data->loadThumbnail(parent->fullId());
|
||||
} else if (_videoCover->inlineThumbnailBytes().isEmpty()
|
||||
&& (_videoCover->hasExact(Data::PhotoSize::Small)
|
||||
|| _videoCover->hasExact(Data::PhotoSize::Thumbnail))) {
|
||||
_videoCover->load(Data::PhotoSize::Small, parent->fullId());
|
||||
}
|
||||
}
|
||||
|
||||
Video::~Video() = default;
|
||||
|
@ -516,9 +523,19 @@ void Video::paint(Painter &p, const QRect &clip, TextSelection selection, const
|
|||
ensureDataMediaCreated();
|
||||
|
||||
const auto selected = (selection == FullSelection);
|
||||
const auto blurred = _dataMedia->thumbnailInline();
|
||||
const auto thumbnail = _spoiler ? nullptr : _dataMedia->thumbnail();
|
||||
const auto good = _spoiler ? nullptr : _dataMedia->goodThumbnail();
|
||||
const auto blurred = _videoCover
|
||||
? _videoCoverMedia->thumbnailInline()
|
||||
: _dataMedia->thumbnailInline();
|
||||
const auto thumbnail = _spoiler
|
||||
? nullptr
|
||||
: _videoCover
|
||||
? _videoCoverMedia->image(Data::PhotoSize::Small)
|
||||
: _dataMedia->thumbnail();
|
||||
const auto good = _spoiler
|
||||
? nullptr
|
||||
: _videoCover
|
||||
? _videoCoverMedia->image(Data::PhotoSize::Large)
|
||||
: _dataMedia->goodThumbnail();
|
||||
|
||||
bool loaded = dataLoaded(), displayLoading = _data->displayLoading();
|
||||
if (displayLoading) {
|
||||
|
@ -626,12 +643,17 @@ void Video::paint(Painter &p, const QRect &clip, TextSelection selection, const
|
|||
}
|
||||
|
||||
void Video::ensureDataMediaCreated() const {
|
||||
if (_dataMedia) {
|
||||
if (_dataMedia && (!_videoCover || _videoCoverMedia)) {
|
||||
return;
|
||||
}
|
||||
_dataMedia = _data->createMediaView();
|
||||
_dataMedia->goodThumbnailWanted();
|
||||
_dataMedia->thumbnailWanted(parent()->fullId());
|
||||
if (_videoCover) {
|
||||
_videoCoverMedia = _videoCover->createMediaView();
|
||||
_videoCover->load(Data::PhotoSize::Large, parent()->fullId());
|
||||
} else {
|
||||
_dataMedia->goodThumbnailWanted();
|
||||
_dataMedia->thumbnailWanted(parent()->fullId());
|
||||
}
|
||||
delegate()->registerHeavyItem(this);
|
||||
}
|
||||
|
||||
|
|
|
@ -316,7 +316,9 @@ private:
|
|||
void updateStatusText();
|
||||
|
||||
const not_null<DocumentData*> _data;
|
||||
PhotoData *_videoCover = nullptr;
|
||||
mutable std::shared_ptr<Data::DocumentMedia> _dataMedia;
|
||||
mutable std::shared_ptr<Data::PhotoMedia> _videoCoverMedia;
|
||||
StatusText _status;
|
||||
|
||||
QString _duration;
|
||||
|
|
|
@ -335,6 +335,11 @@ void Uploader::upload(
|
|||
if (file->type == SendMediaType::ThemeFile) {
|
||||
document->checkWallPaperProperties();
|
||||
}
|
||||
if (file->videoCover) {
|
||||
session().data().processPhoto(
|
||||
file->videoCover->photo,
|
||||
file->videoCover->photoThumbs);
|
||||
}
|
||||
}
|
||||
_queue.push_back({ itemId, file });
|
||||
if (!_nextTimer.isActive()) {
|
||||
|
@ -348,9 +353,26 @@ void Uploader::failed(FullMsgId itemId) {
|
|||
const auto entry = std::move(*i);
|
||||
_queue.erase(i);
|
||||
notifyFailed(entry);
|
||||
} else if (const auto coverId = _videoIdToCoverId.take(itemId)) {
|
||||
if (const auto video = _videoWaitingCover.take(*coverId)) {
|
||||
const auto document = session().data().document(video->id);
|
||||
if (document->uploading()) {
|
||||
document->status = FileUploadFailed;
|
||||
}
|
||||
_documentFailed.fire_copy(video->fullId);
|
||||
}
|
||||
failed(*coverId);
|
||||
} else if (const auto video = _videoWaitingCover.take(itemId)) {
|
||||
_videoIdToCoverId.remove(video->fullId);
|
||||
const auto document = session().data().document(video->id);
|
||||
if (document->uploading()) {
|
||||
document->status = FileUploadFailed;
|
||||
}
|
||||
_documentFailed.fire_copy(video->fullId);
|
||||
}
|
||||
cancelRequests(itemId);
|
||||
maybeFinishFront();
|
||||
|
||||
crl::on_main(this, [=] {
|
||||
maybeSend();
|
||||
});
|
||||
|
@ -854,7 +876,8 @@ void Uploader::finishFront() {
|
|||
MTP_int(entry.parts->size()),
|
||||
MTP_string(photoFilename),
|
||||
MTP_bytes(md5));
|
||||
_photoReady.fire({
|
||||
auto ready = UploadedMedia{
|
||||
.id = entry.file->id,
|
||||
.fullId = entry.itemId,
|
||||
.info = {
|
||||
.file = file,
|
||||
|
@ -862,7 +885,13 @@ void Uploader::finishFront() {
|
|||
},
|
||||
.options = options,
|
||||
.edit = edit,
|
||||
});
|
||||
};
|
||||
const auto i = _videoWaitingCover.find(entry.itemId);
|
||||
if (i != end(_videoWaitingCover)) {
|
||||
uploadCoverAsPhoto(i->second.fullId, std::move(ready));
|
||||
} else {
|
||||
_photoReady.fire(std::move(ready));
|
||||
}
|
||||
} else if (entry.file->type == SendMediaType::File
|
||||
|| entry.file->type == SendMediaType::ThemeFile
|
||||
|| entry.file->type == SendMediaType::Audio
|
||||
|
@ -892,7 +921,8 @@ void Uploader::finishFront() {
|
|||
MTP_string(thumbFilename),
|
||||
MTP_bytes(thumbMd5));
|
||||
}();
|
||||
_documentReady.fire({
|
||||
auto ready = UploadedMedia{
|
||||
.id = entry.file->id,
|
||||
.fullId = entry.itemId,
|
||||
.info = {
|
||||
.file = file,
|
||||
|
@ -901,7 +931,12 @@ void Uploader::finishFront() {
|
|||
},
|
||||
.options = options,
|
||||
.edit = edit,
|
||||
});
|
||||
};
|
||||
if (entry.file->videoCover) {
|
||||
uploadVideoCover(std::move(ready), entry.file->videoCover);
|
||||
} else {
|
||||
_documentReady.fire(std::move(ready));
|
||||
}
|
||||
} else if (entry.file->type == SendMediaType::Secure) {
|
||||
_secureReady.fire({
|
||||
entry.itemId,
|
||||
|
@ -916,4 +951,54 @@ void Uploader::partFailed(const MTP::Error &error, mtpRequestId requestId) {
|
|||
failed(request.itemId);
|
||||
}
|
||||
|
||||
void Uploader::uploadVideoCover(
|
||||
UploadedMedia &&video,
|
||||
std::shared_ptr<FilePrepareResult> videoCover) {
|
||||
const auto coverId = FullMsgId(
|
||||
videoCover->to.peer,
|
||||
session().data().nextLocalMessageId());
|
||||
_videoIdToCoverId.emplace(video.fullId, coverId);
|
||||
_videoWaitingCover.emplace(coverId, std::move(video));
|
||||
|
||||
upload(coverId, videoCover);
|
||||
}
|
||||
|
||||
void Uploader::uploadCoverAsPhoto(
|
||||
FullMsgId videoId,
|
||||
UploadedMedia &&cover) {
|
||||
const auto coverId = cover.fullId;
|
||||
_api->request(MTPmessages_UploadMedia(
|
||||
MTP_flags(0),
|
||||
MTPstring(), // business_connection_id
|
||||
session().data().peer(videoId.peer)->input,
|
||||
MTP_inputMediaUploadedPhoto(
|
||||
MTP_flags(0),
|
||||
cover.info.file,
|
||||
MTP_vector<MTPInputDocument>(0),
|
||||
MTP_int(0))
|
||||
)).done([=](const MTPMessageMedia &result) {
|
||||
result.match([&](const MTPDmessageMediaPhoto &data) {
|
||||
const auto photo = data.vphoto();
|
||||
if (!photo || photo->type() != mtpc_photo) {
|
||||
failed(coverId);
|
||||
return;
|
||||
}
|
||||
const auto &fields = photo->c_photo();
|
||||
if (const auto coverId = _videoIdToCoverId.take(videoId)) {
|
||||
if (auto video = _videoWaitingCover.take(*coverId)) {
|
||||
video->info.videoCover = MTP_inputPhoto(
|
||||
fields.vid(),
|
||||
fields.vaccess_hash(),
|
||||
fields.vfile_reference());
|
||||
_documentReady.fire(std::move(*video));
|
||||
}
|
||||
}
|
||||
}, [&](const auto &) {
|
||||
failed(coverId);
|
||||
});
|
||||
}).fail([=] {
|
||||
failed(coverId);
|
||||
}).send();
|
||||
}
|
||||
|
||||
} // namespace Storage
|
||||
|
|
|
@ -29,6 +29,7 @@ namespace Storage {
|
|||
constexpr auto kUseBigFilesFrom = 30 * 1024 * 1024;
|
||||
|
||||
struct UploadedMedia {
|
||||
uint64 id = 0;
|
||||
FullMsgId fullId;
|
||||
Api::RemoteFileInfo info;
|
||||
Api::SendOptions options;
|
||||
|
@ -134,6 +135,11 @@ private:
|
|||
void partFailed(const MTP::Error &error, mtpRequestId requestId);
|
||||
Request finishRequest(mtpRequestId requestId);
|
||||
|
||||
void uploadVideoCover(
|
||||
UploadedMedia &&video,
|
||||
std::shared_ptr<FilePrepareResult> videoCover);
|
||||
void uploadCoverAsPhoto(FullMsgId videoId, UploadedMedia &&cover);
|
||||
|
||||
void processPhotoProgress(FullMsgId itemId);
|
||||
void processPhotoFailed(FullMsgId itemId);
|
||||
void processDocumentProgress(FullMsgId itemId);
|
||||
|
@ -163,6 +169,9 @@ private:
|
|||
crl::time _latestDcIndexRemoved = 0;
|
||||
std::vector<Request> _pendingFromRemovedDcIndices;
|
||||
|
||||
base::flat_map<FullMsgId, FullMsgId> _videoIdToCoverId;
|
||||
base::flat_map<FullMsgId, UploadedMedia> _videoWaitingCover;
|
||||
|
||||
FullMsgId _pausedId;
|
||||
base::Timer _nextTimer, _stopSessionsTimer;
|
||||
|
||||
|
|
|
@ -468,6 +468,7 @@ FileLoadTask::FileLoadTask(
|
|||
const QString &filepath,
|
||||
const QByteArray &content,
|
||||
std::unique_ptr<Ui::PreparedFileInformation> information,
|
||||
std::unique_ptr<FileLoadTask> videoCover,
|
||||
SendMediaType type,
|
||||
const FileLoadTo &to,
|
||||
const TextWithTags &caption,
|
||||
|
@ -481,6 +482,7 @@ FileLoadTask::FileLoadTask(
|
|||
, _album(std::move(album))
|
||||
, _filepath(filepath)
|
||||
, _content(content)
|
||||
, _videoCover(std::move(videoCover))
|
||||
, _information(std::move(information))
|
||||
, _type(type)
|
||||
, _caption(caption)
|
||||
|
@ -688,6 +690,15 @@ void FileLoadTask::process(Args &&args) {
|
|||
.spoiler = _spoiler,
|
||||
.album = _album,
|
||||
});
|
||||
if (const auto cover = _videoCover.get()) {
|
||||
cover->process();
|
||||
if (const auto &result = cover->peekResult()) {
|
||||
if (result->type == SendMediaType::Photo
|
||||
&& !result->fileparts.empty()) {
|
||||
_result->videoCover = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString filename, filemime;
|
||||
qint64 filesize = 0;
|
||||
|
@ -1073,8 +1084,8 @@ void FileLoadTask::finish() {
|
|||
}
|
||||
}
|
||||
|
||||
FilePrepareResult *FileLoadTask::peekResult() const {
|
||||
return _result.get();
|
||||
const std::shared_ptr<FilePrepareResult> &FileLoadTask::peekResult() const {
|
||||
return _result;
|
||||
}
|
||||
|
||||
std::unique_ptr<Ui::PreparedFileInformation> FileLoadTask::readMediaInformation(
|
||||
|
|
|
@ -196,6 +196,8 @@ struct FilePrepareResult {
|
|||
|
||||
std::vector<MTPInputDocument> attachedStickers;
|
||||
|
||||
std::shared_ptr<FilePrepareResult> videoCover;
|
||||
|
||||
void setFileData(const QByteArray &filedata);
|
||||
void setThumbData(const QByteArray &thumbdata);
|
||||
|
||||
|
@ -222,6 +224,7 @@ public:
|
|||
const QString &filepath,
|
||||
const QByteArray &content,
|
||||
std::unique_ptr<Ui::PreparedFileInformation> information,
|
||||
std::unique_ptr<FileLoadTask> videoCover,
|
||||
SendMediaType type,
|
||||
const FileLoadTo &to,
|
||||
const TextWithTags &caption,
|
||||
|
@ -252,7 +255,8 @@ public:
|
|||
}
|
||||
void finish() override;
|
||||
|
||||
FilePrepareResult *peekResult() const;
|
||||
[[nodiscard]] auto peekResult() const
|
||||
-> const std::shared_ptr<FilePrepareResult> &;
|
||||
|
||||
private:
|
||||
static bool CheckForSong(
|
||||
|
@ -281,6 +285,7 @@ private:
|
|||
const std::shared_ptr<SendingAlbum> _album;
|
||||
QString _filepath;
|
||||
QByteArray _content;
|
||||
std::unique_ptr<FileLoadTask> _videoCover;
|
||||
std::unique_ptr<Ui::PreparedFileInformation> _information;
|
||||
crl::time _duration = 0;
|
||||
VoiceWaveform _waveform;
|
||||
|
|
|
@ -362,10 +362,22 @@ void UpdateImageDetails(
|
|||
|
||||
bool ApplyModifications(PreparedList &list) {
|
||||
auto applied = false;
|
||||
for (auto &file : list.files) {
|
||||
const auto apply = [&](PreparedFile &file, QSize strictSize = {}) {
|
||||
const auto image = std::get_if<Image>(&file.information->media);
|
||||
const auto guard = gsl::finally([&] {
|
||||
if (!image || strictSize.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
applied = true;
|
||||
file.path = QString();
|
||||
file.content = QByteArray();
|
||||
image->data = image->data.scaled(
|
||||
strictSize,
|
||||
Qt::IgnoreAspectRatio,
|
||||
Qt::SmoothTransformation);
|
||||
});
|
||||
if (!image || !image->modifications) {
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
applied = true;
|
||||
file.path = QString();
|
||||
|
@ -373,6 +385,16 @@ bool ApplyModifications(PreparedList &list) {
|
|||
image->data = Editor::ImageModified(
|
||||
std::move(image->data),
|
||||
image->modifications);
|
||||
};
|
||||
for (auto &file : list.files) {
|
||||
apply(file);
|
||||
if (const auto cover = file.videoCover.get()) {
|
||||
const auto video = file.information
|
||||
? std::get_if<Ui::PreparedFileInformation::Video>(
|
||||
&file.information->media)
|
||||
: nullptr;
|
||||
apply(*cover, video ? video->thumbnail.size() : QSize());
|
||||
}
|
||||
}
|
||||
return applied;
|
||||
}
|
||||
|
|
|
@ -34,10 +34,10 @@ AbstractSingleMediaPreview::AbstractSingleMediaPreview(
|
|||
QWidget *parent,
|
||||
const style::ComposeControls &st,
|
||||
AttachControls::Type type,
|
||||
Fn<bool()> canToggleSpoiler)
|
||||
Fn<bool(AttachActionType)> actionAllowed)
|
||||
: AbstractSinglePreview(parent)
|
||||
, _st(st)
|
||||
, _canToggleSpoiler(std::move(canToggleSpoiler))
|
||||
, _actionAllowed(std::move(actionAllowed))
|
||||
, _minThumbH(st::sendBoxAlbumGroupSize.height()
|
||||
+ st::sendBoxAlbumGroupSkipTop * 2)
|
||||
, _controls(base::make_unique_q<AttachControlsWidget>(this, type)) {
|
||||
|
@ -57,6 +57,14 @@ rpl::producer<> AbstractSingleMediaPreview::modifyRequests() const {
|
|||
return _photoEditorRequests.events();
|
||||
}
|
||||
|
||||
rpl::producer<> AbstractSingleMediaPreview::editCoverRequests() const {
|
||||
return _editCoverRequests.events();
|
||||
}
|
||||
|
||||
rpl::producer<> AbstractSingleMediaPreview::clearCoverRequests() const {
|
||||
return _clearCoverRequests.events();
|
||||
}
|
||||
|
||||
void AbstractSingleMediaPreview::setSendWay(SendFilesWay way) {
|
||||
_sendWay = way;
|
||||
update();
|
||||
|
@ -112,7 +120,7 @@ void AbstractSingleMediaPreview::preparePreview(QImage preview) {
|
|||
preview = Images::Prepare(
|
||||
std::move(preview),
|
||||
QSize(maxW, maxH) * ratio,
|
||||
{ .options = Images::Option::Blur, .outer = { maxW, maxH } });
|
||||
{ .outer = { maxW, maxH } });
|
||||
}
|
||||
auto originalWidth = preview.width();
|
||||
auto originalHeight = preview.height();
|
||||
|
@ -273,24 +281,33 @@ void AbstractSingleMediaPreview::applyCursor(style::cursor cursor) {
|
|||
}
|
||||
|
||||
void AbstractSingleMediaPreview::showContextMenu(QPoint position) {
|
||||
if (!_canToggleSpoiler()
|
||||
|| !_sendWay.sendImagesAsPhotos()
|
||||
|| !supportsSpoilers()) {
|
||||
return;
|
||||
}
|
||||
_menu = base::make_unique_q<Ui::PopupMenu>(
|
||||
this,
|
||||
_st.tabbed.menu);
|
||||
|
||||
const auto &icons = _st.tabbed.icons;
|
||||
const auto spoilered = hasSpoiler();
|
||||
_menu->addAction(spoilered
|
||||
? tr::lng_context_disable_spoiler(tr::now)
|
||||
: tr::lng_context_spoiler_effect(tr::now), [=] {
|
||||
setSpoiler(!spoilered);
|
||||
_spoileredChanges.fire_copy(!spoilered);
|
||||
}, spoilered ? &icons.menuSpoilerOff : &icons.menuSpoiler);
|
||||
if (_actionAllowed(AttachActionType::ToggleSpoiler)
|
||||
&& _sendWay.sendImagesAsPhotos()
|
||||
&& supportsSpoilers()) {
|
||||
const auto spoilered = hasSpoiler();
|
||||
_menu->addAction(spoilered
|
||||
? tr::lng_context_disable_spoiler(tr::now)
|
||||
: tr::lng_context_spoiler_effect(tr::now), [=] {
|
||||
setSpoiler(!spoilered);
|
||||
_spoileredChanges.fire_copy(!spoilered);
|
||||
}, spoilered ? &icons.menuSpoilerOff : &icons.menuSpoiler);
|
||||
}
|
||||
if (_actionAllowed(AttachActionType::EditCover)) {
|
||||
_menu->addAction(tr::lng_context_edit_cover(tr::now), [=] {
|
||||
_editCoverRequests.fire({});
|
||||
}, &st::menuIconEdit);
|
||||
|
||||
if (_actionAllowed(AttachActionType::ClearCover)) {
|
||||
_menu->addAction(tr::lng_context_clear_cover(tr::now), [=] {
|
||||
_clearCoverRequests.fire({});
|
||||
}, &st::menuIconCancel);
|
||||
}
|
||||
}
|
||||
if (_menu->empty()) {
|
||||
_menu = nullptr;
|
||||
} else {
|
||||
|
|
|
@ -27,7 +27,7 @@ public:
|
|||
QWidget *parent,
|
||||
const style::ComposeControls &st,
|
||||
AttachControls::Type type,
|
||||
Fn<bool()> canToggleSpoiler);
|
||||
Fn<bool(AttachActionType)> actionAllowed);
|
||||
~AbstractSingleMediaPreview();
|
||||
|
||||
void setSendWay(SendFilesWay way);
|
||||
|
@ -36,6 +36,8 @@ public:
|
|||
[[nodiscard]] rpl::producer<> deleteRequests() const override;
|
||||
[[nodiscard]] rpl::producer<> editRequests() const override;
|
||||
[[nodiscard]] rpl::producer<> modifyRequests() const override;
|
||||
[[nodiscard]] rpl::producer<> editCoverRequests() const;
|
||||
[[nodiscard]] rpl::producer<> clearCoverRequests() const;
|
||||
|
||||
[[nodiscard]] bool isPhoto() const;
|
||||
|
||||
|
@ -74,7 +76,7 @@ private:
|
|||
|
||||
const style::ComposeControls &_st;
|
||||
SendFilesWay _sendWay;
|
||||
Fn<bool()> _canToggleSpoiler;
|
||||
Fn<bool(AttachActionType)> _actionAllowed;
|
||||
bool _animated = false;
|
||||
QPixmap _preview;
|
||||
QPixmap _previewBlurred;
|
||||
|
@ -89,6 +91,8 @@ private:
|
|||
const int _minThumbH;
|
||||
const base::unique_qptr<AttachControlsWidget> _controls;
|
||||
rpl::event_stream<> _photoEditorRequests;
|
||||
rpl::event_stream<> _editCoverRequests;
|
||||
rpl::event_stream<> _clearCoverRequests;
|
||||
|
||||
style::cursor _cursor = style::cur_default;
|
||||
bool _pressed = false;
|
||||
|
|
|
@ -38,11 +38,11 @@ AlbumPreview::AlbumPreview(
|
|||
const style::ComposeControls &st,
|
||||
gsl::span<Ui::PreparedFile> items,
|
||||
SendFilesWay way,
|
||||
Fn<bool()> canToggleSpoiler)
|
||||
Fn<bool(int, AttachActionType)> actionAllowed)
|
||||
: RpWidget(parent)
|
||||
, _st(st)
|
||||
, _sendWay(way)
|
||||
, _canToggleSpoiler(std::move(canToggleSpoiler))
|
||||
, _actionAllowed(std::move(actionAllowed))
|
||||
, _dragTimer([=] { switchToDrag(); }) {
|
||||
setMouseTracking(true);
|
||||
prepareThumbs(items);
|
||||
|
@ -582,19 +582,31 @@ void AlbumPreview::mouseReleaseEvent(QMouseEvent *e) {
|
|||
void AlbumPreview::showContextMenu(
|
||||
not_null<AlbumThumbnail*> thumb,
|
||||
QPoint position) {
|
||||
if (!_canToggleSpoiler() || !_sendWay.sendImagesAsPhotos()) {
|
||||
return;
|
||||
}
|
||||
_menu = base::make_unique_q<Ui::PopupMenu>(
|
||||
this,
|
||||
st::popupMenuWithIcons);
|
||||
|
||||
const auto spoilered = thumb->hasSpoiler();
|
||||
_menu->addAction(spoilered
|
||||
? tr::lng_context_disable_spoiler(tr::now)
|
||||
: tr::lng_context_spoiler_effect(tr::now), [=] {
|
||||
thumb->setSpoiler(!spoilered);
|
||||
}, spoilered ? &st::menuIconSpoilerOff : &st::menuIconSpoiler);
|
||||
const auto index = orderIndex(thumb);
|
||||
if (_actionAllowed(index, AttachActionType::ToggleSpoiler)
|
||||
&& _sendWay.sendImagesAsPhotos()) {
|
||||
const auto spoilered = thumb->hasSpoiler();
|
||||
_menu->addAction(spoilered
|
||||
? tr::lng_context_disable_spoiler(tr::now)
|
||||
: tr::lng_context_spoiler_effect(tr::now), [=] {
|
||||
thumb->setSpoiler(!spoilered);
|
||||
}, spoilered ? &st::menuIconSpoilerOff : &st::menuIconSpoiler);
|
||||
}
|
||||
if (_actionAllowed(index, AttachActionType::EditCover)) {
|
||||
_menu->addAction(tr::lng_context_edit_cover(tr::now), [=] {
|
||||
_thumbEditCoverRequested.fire_copy(index);
|
||||
}, &st::menuIconEdit);
|
||||
|
||||
if (_actionAllowed(index, AttachActionType::ClearCover)) {
|
||||
_menu->addAction(tr::lng_context_clear_cover(tr::now), [=] {
|
||||
_thumbClearCoverRequested.fire_copy(index);
|
||||
}, &st::menuIconCancel);
|
||||
}
|
||||
}
|
||||
|
||||
if (_menu->empty()) {
|
||||
_menu = nullptr;
|
||||
|
|
|
@ -29,7 +29,7 @@ public:
|
|||
const style::ComposeControls &st,
|
||||
gsl::span<Ui::PreparedFile> items,
|
||||
SendFilesWay way,
|
||||
Fn<bool()> canToggleSpoiler);
|
||||
Fn<bool(int, AttachActionType)> actionAllowed);
|
||||
~AlbumPreview();
|
||||
|
||||
void setSendWay(SendFilesWay way);
|
||||
|
@ -42,15 +42,18 @@ public:
|
|||
[[nodiscard]] rpl::producer<int> thumbDeleted() const {
|
||||
return _thumbDeleted.events();
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::producer<int> thumbChanged() const {
|
||||
return _thumbChanged.events();
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::producer<int> thumbModified() const {
|
||||
return _thumbModified.events();
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::producer<int> thumbEditCoverRequested() const {
|
||||
return _thumbEditCoverRequested.events();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<int> thumbClearCoverRequested() const {
|
||||
return _thumbClearCoverRequested.events();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<> orderUpdated() const {
|
||||
return _orderUpdated.events();
|
||||
}
|
||||
|
@ -101,7 +104,7 @@ private:
|
|||
|
||||
const style::ComposeControls &_st;
|
||||
SendFilesWay _sendWay;
|
||||
Fn<bool()> _canToggleSpoiler;
|
||||
Fn<bool(int, AttachActionType)> _actionAllowed;
|
||||
style::cursor _cursor = style::cur_default;
|
||||
std::vector<int> _order;
|
||||
std::vector<QSize> _itemsShownDimensions;
|
||||
|
@ -124,6 +127,8 @@ private:
|
|||
rpl::event_stream<int> _thumbDeleted;
|
||||
rpl::event_stream<int> _thumbChanged;
|
||||
rpl::event_stream<int> _thumbModified;
|
||||
rpl::event_stream<int> _thumbEditCoverRequested;
|
||||
rpl::event_stream<int> _thumbClearCoverRequested;
|
||||
rpl::event_stream<> _orderUpdated;
|
||||
|
||||
base::unique_qptr<PopupMenu> _menu;
|
||||
|
|
|
@ -35,7 +35,7 @@ AlbumThumbnail::AlbumThumbnail(
|
|||
Fn<void()> deleteCallback)
|
||||
: _st(st)
|
||||
, _layout(layout)
|
||||
, _fullPreview(file.preview)
|
||||
, _fullPreview(file.videoCover ? file.videoCover->preview : file.preview)
|
||||
, _shrinkSize(int(std::ceil(st::roundRadiusLarge / 1.4)))
|
||||
, _isPhoto(file.type == PreparedFile::Type::Photo)
|
||||
, _isVideo(file.type == PreparedFile::Type::Video)
|
||||
|
|
|
@ -36,8 +36,15 @@ ItemSingleMediaPreview::ItemSingleMediaPreview(
|
|||
Fn<bool()> gifPaused,
|
||||
not_null<HistoryItem*> item,
|
||||
AttachControls::Type type)
|
||||
: AbstractSingleMediaPreview(parent, st, type, [] { return true; })
|
||||
: AbstractSingleMediaPreview(parent, st, type, [=](AttachActionType type) {
|
||||
if (type == AttachActionType::EditCover) {
|
||||
return _isVideoFile;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
, _gifPaused(std::move(gifPaused))
|
||||
, _isVideoFile(item->media()->document()
|
||||
&& item->media()->document()->isVideoFile())
|
||||
, _fullId(item->fullId()) {
|
||||
const auto media = item->media();
|
||||
Assert(media != nullptr);
|
||||
|
|
|
@ -56,6 +56,7 @@ private:
|
|||
void startStreamedPlayer();
|
||||
|
||||
const Fn<bool()> _gifPaused;
|
||||
const bool _isVideoFile;
|
||||
const FullMsgId _fullId;
|
||||
|
||||
std::shared_ptr<::Data::PhotoMedia> _photoMedia;
|
||||
|
|
|
@ -42,6 +42,15 @@ bool PreparedFile::isSticker() const {
|
|||
&& Core::IsMimeSticker(information->filemime);
|
||||
}
|
||||
|
||||
bool PreparedFile::isVideoFile() const {
|
||||
Expects(information != nullptr);
|
||||
|
||||
using Video = Ui::PreparedFileInformation::Video;
|
||||
return (type == PreparedFile::Type::Video)
|
||||
&& v::is<Video>(information->media)
|
||||
&& !v::get<Video>(information->media).isGifv;
|
||||
}
|
||||
|
||||
bool PreparedFile::isGifv() const {
|
||||
Expects(information != nullptr);
|
||||
|
||||
|
|
|
@ -74,12 +74,14 @@ struct PreparedFile {
|
|||
[[nodiscard]] bool canBeInAlbumType(AlbumType album) const;
|
||||
[[nodiscard]] AlbumType albumType(bool sendImagesAsPhotos) const;
|
||||
[[nodiscard]] bool isSticker() const;
|
||||
[[nodiscard]] bool isVideoFile() const;
|
||||
[[nodiscard]] bool isGifv() const;
|
||||
|
||||
QString path;
|
||||
QByteArray content;
|
||||
int64 size = 0;
|
||||
std::unique_ptr<Ui::PreparedFileInformation> information;
|
||||
std::unique_ptr<PreparedFileInformation> information;
|
||||
std::unique_ptr<PreparedFile> videoCover;
|
||||
QImage preview;
|
||||
QSize shownDimensions;
|
||||
QSize originalDimensions;
|
||||
|
|
|
@ -11,6 +11,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
namespace Ui {
|
||||
|
||||
enum class AttachActionType {
|
||||
ToggleSpoiler,
|
||||
EditCover,
|
||||
ClearCover,
|
||||
};
|
||||
|
||||
enum class AttachButtonType {
|
||||
Edit,
|
||||
Delete,
|
||||
|
|
|
@ -19,7 +19,7 @@ SingleMediaPreview *SingleMediaPreview::Create(
|
|||
const style::ComposeControls &st,
|
||||
Fn<bool()> gifPaused,
|
||||
const PreparedFile &file,
|
||||
Fn<bool()> canToggleSpoiler,
|
||||
Fn<bool(AttachActionType)> actionAllowed,
|
||||
AttachControls::Type type) {
|
||||
auto preview = QImage();
|
||||
auto animated = false;
|
||||
|
@ -32,7 +32,9 @@ SingleMediaPreview *SingleMediaPreview::Create(
|
|||
hasModifications = !image->modifications.empty();
|
||||
} else if (const auto video = std::get_if<PreparedFileInformation::Video>(
|
||||
&file.information->media)) {
|
||||
preview = video->thumbnail;
|
||||
preview = file.videoCover
|
||||
? file.videoCover->preview
|
||||
: video->thumbnail;
|
||||
animated = true;
|
||||
animationPreview = video->isGifv;
|
||||
}
|
||||
|
@ -53,7 +55,7 @@ SingleMediaPreview *SingleMediaPreview::Create(
|
|||
file.spoiler,
|
||||
animationPreview ? file.path : QString(),
|
||||
type,
|
||||
std::move(canToggleSpoiler));
|
||||
std::move(actionAllowed));
|
||||
}
|
||||
|
||||
SingleMediaPreview::SingleMediaPreview(
|
||||
|
@ -66,8 +68,8 @@ SingleMediaPreview::SingleMediaPreview(
|
|||
bool spoiler,
|
||||
const QString &animatedPreviewPath,
|
||||
AttachControls::Type type,
|
||||
Fn<bool()> canToggleSpoiler)
|
||||
: AbstractSingleMediaPreview(parent, st, type, std::move(canToggleSpoiler))
|
||||
Fn<bool(AttachActionType)> actionAllowed)
|
||||
: AbstractSingleMediaPreview(parent, st, type, std::move(actionAllowed))
|
||||
, _gifPaused(std::move(gifPaused))
|
||||
, _sticker(sticker) {
|
||||
Expects(!preview.isNull());
|
||||
|
|
|
@ -25,7 +25,7 @@ public:
|
|||
const style::ComposeControls &st,
|
||||
Fn<bool()> gifPaused,
|
||||
const PreparedFile &file,
|
||||
Fn<bool()> canToggleSpoiler,
|
||||
Fn<bool(AttachActionType)> actionAllowed,
|
||||
AttachControls::Type type = AttachControls::Type::Full);
|
||||
|
||||
SingleMediaPreview(
|
||||
|
@ -38,7 +38,7 @@ public:
|
|||
bool spoiler,
|
||||
const QString &animatedPreviewPath,
|
||||
AttachControls::Type type,
|
||||
Fn<bool()> canToggleSpoiler);
|
||||
Fn<bool(AttachActionType)> actionAllowed);
|
||||
|
||||
protected:
|
||||
bool supportsSpoilers() const override;
|
||||
|
|
Loading…
Add table
Reference in a new issue