mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-07-27 16:03:03 +02:00
Send files grouped in albums, show captions.
This commit is contained in:
parent
86612f0a67
commit
85d08c8f52
18 changed files with 292 additions and 99 deletions
|
@ -4232,7 +4232,9 @@ void ApiWrap::sendFiles(
|
||||||
if (album) {
|
if (album) {
|
||||||
switch (file.type) {
|
switch (file.type) {
|
||||||
case Ui::PreparedFile::AlbumType::Photo:
|
case Ui::PreparedFile::AlbumType::Photo:
|
||||||
type = SendMediaType::Photo;
|
type = (type == SendMediaType::File)
|
||||||
|
? type
|
||||||
|
: SendMediaType::Photo;
|
||||||
break;
|
break;
|
||||||
case Ui::PreparedFile::AlbumType::Video:
|
case Ui::PreparedFile::AlbumType::Video:
|
||||||
case Ui::PreparedFile::AlbumType::File:
|
case Ui::PreparedFile::AlbumType::File:
|
||||||
|
|
|
@ -763,6 +763,9 @@ void SendFilesBox::addPreparedAsyncFile(Ui::PreparedFile &&file) {
|
||||||
if (_list.files.size() > count) {
|
if (_list.files.size() > count) {
|
||||||
refreshAllAfterChanges(count);
|
refreshAllAfterChanges(count);
|
||||||
}
|
}
|
||||||
|
if (!_preparing && _whenReadySend) {
|
||||||
|
_whenReadySend();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SendFilesBox::addFile(Ui::PreparedFile &&file) {
|
void SendFilesBox::addFile(Ui::PreparedFile &&file) {
|
||||||
|
@ -893,6 +896,12 @@ void SendFilesBox::send(
|
||||||
&& !options.scheduled) {
|
&& !options.scheduled) {
|
||||||
return sendScheduled();
|
return sendScheduled();
|
||||||
}
|
}
|
||||||
|
if (_preparing) {
|
||||||
|
_whenReadySend = [=] {
|
||||||
|
send(options, ctrlShiftEnter);
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto way = _sendWay.current();
|
auto way = _sendWay.current();
|
||||||
auto oldWay = Core::App().settings().sendFilesWay();
|
auto oldWay = Core::App().settings().sendFilesWay();
|
||||||
|
|
|
@ -187,6 +187,7 @@ private:
|
||||||
object_ptr<Ui::ScrollArea> _scroll;
|
object_ptr<Ui::ScrollArea> _scroll;
|
||||||
QPointer<Ui::VerticalLayout> _inner;
|
QPointer<Ui::VerticalLayout> _inner;
|
||||||
std::vector<Block> _blocks;
|
std::vector<Block> _blocks;
|
||||||
|
Fn<void()> _whenReadySend;
|
||||||
bool _preparing = false;
|
bool _preparing = false;
|
||||||
|
|
||||||
int _lastScrollTop = 0;
|
int _lastScrollTop = 0;
|
||||||
|
|
|
@ -479,7 +479,14 @@ Storage::SharedMediaTypesMask MediaFile::sharedMediaTypes() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MediaFile::canBeGrouped() const {
|
bool MediaFile::canBeGrouped() const {
|
||||||
return _document->isVideoFile() || _document->isSong();
|
if (_document->sticker() || _document->isAnimation()) {
|
||||||
|
return false;
|
||||||
|
} else if (_document->isVideoFile()) {
|
||||||
|
return true;
|
||||||
|
} else if (_document->isTheme() && _document->hasThumbnail()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MediaFile::hasReplyPreview() const {
|
bool MediaFile::hasReplyPreview() const {
|
||||||
|
@ -704,7 +711,10 @@ std::unique_ptr<HistoryView::Media> MediaFile::createView(
|
||||||
message,
|
message,
|
||||||
_document);
|
_document);
|
||||||
}
|
}
|
||||||
return std::make_unique<HistoryView::Document>(message, _document);
|
return std::make_unique<HistoryView::Document>(
|
||||||
|
message,
|
||||||
|
realParent,
|
||||||
|
_document);
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaContact::MediaContact(
|
MediaContact::MediaContact(
|
||||||
|
|
|
@ -4177,22 +4177,12 @@ bool HistoryWidget::confirmSendingFiles(
|
||||||
TextWithTags &&caption,
|
TextWithTags &&caption,
|
||||||
Api::SendOptions options,
|
Api::SendOptions options,
|
||||||
bool ctrlShiftEnter) {
|
bool ctrlShiftEnter) {
|
||||||
if (showSendingFilesError(list)) {
|
sendingFilesConfirmed(
|
||||||
return;
|
|
||||||
}
|
|
||||||
const auto type = way.sendImagesAsPhotos()
|
|
||||||
? SendMediaType::Photo
|
|
||||||
: SendMediaType::File;
|
|
||||||
const auto album = way.groupMediaInAlbums() // #TODO files
|
|
||||||
? std::make_shared<SendingAlbum>()
|
|
||||||
: nullptr;
|
|
||||||
uploadFilesAfterConfirmation(
|
|
||||||
std::move(list),
|
std::move(list),
|
||||||
type,
|
way,
|
||||||
std::move(caption),
|
std::move(caption),
|
||||||
replyToId(),
|
|
||||||
options,
|
options,
|
||||||
album);
|
ctrlShiftEnter);
|
||||||
}));
|
}));
|
||||||
box->setCancelledCallback(crl::guard(this, [=] {
|
box->setCancelledCallback(crl::guard(this, [=] {
|
||||||
_field->setTextWithTags(text);
|
_field->setTextWithTags(text);
|
||||||
|
@ -4214,6 +4204,73 @@ bool HistoryWidget::confirmSendingFiles(
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HistoryWidget::sendingFilesConfirmed(
|
||||||
|
Ui::PreparedList &&list,
|
||||||
|
Ui::SendFilesWay way,
|
||||||
|
TextWithTags &&caption,
|
||||||
|
Api::SendOptions options,
|
||||||
|
bool ctrlShiftEnter) {
|
||||||
|
Expects(list.filesToProcess.empty());
|
||||||
|
|
||||||
|
if (showSendingFilesError(list)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto slowmode = _peer->slowmodeApplied();
|
||||||
|
const auto sendImagesAsPhotos = way.sendImagesAsPhotos();
|
||||||
|
const auto sendType = sendImagesAsPhotos
|
||||||
|
? SendMediaType::Photo
|
||||||
|
: SendMediaType::File;
|
||||||
|
const auto groupMedia = way.groupMediaInAlbums() || slowmode;
|
||||||
|
const auto groupFiles = way.groupFiles() || slowmode;
|
||||||
|
|
||||||
|
auto group = Ui::PreparedList();
|
||||||
|
|
||||||
|
// For groupType Type::Video means media album,
|
||||||
|
// Type::File means file album,
|
||||||
|
// Type::None means no grouping.
|
||||||
|
using Type = Ui::PreparedFile::AlbumType;
|
||||||
|
auto groupType = Type::None;
|
||||||
|
|
||||||
|
const auto reply = replyToId();
|
||||||
|
auto sendGroup = [&] {
|
||||||
|
if (group.files.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto album = (groupType == Type::None)
|
||||||
|
? nullptr
|
||||||
|
: std::make_shared<SendingAlbum>();
|
||||||
|
uploadFilesAfterConfirmation(
|
||||||
|
base::take(group),
|
||||||
|
sendType,
|
||||||
|
base::take(caption),
|
||||||
|
reply,
|
||||||
|
options,
|
||||||
|
std::move(album));
|
||||||
|
};
|
||||||
|
for (auto i = 0; i != list.files.size(); ++i) {
|
||||||
|
auto &file = list.files[i];
|
||||||
|
const auto fileGroupType = (file.type == Type::Video)
|
||||||
|
? (groupMedia ? Type::Video : Type::None)
|
||||||
|
: (file.type == Type::Photo)
|
||||||
|
? ((groupMedia && sendImagesAsPhotos)
|
||||||
|
? Type::Video
|
||||||
|
: (groupFiles && !sendImagesAsPhotos)
|
||||||
|
? Type::File
|
||||||
|
: Type::None)
|
||||||
|
: (file.type == Type::File)
|
||||||
|
? (groupFiles ? Type::File : Type::None)
|
||||||
|
: Type::None;
|
||||||
|
if ((!group.files.empty() && groupType != fileGroupType)
|
||||||
|
|| ((groupType != Type::None)
|
||||||
|
&& (group.files.size() == Ui::MaxAlbumItems()))) {
|
||||||
|
sendGroup();
|
||||||
|
}
|
||||||
|
group.files.push_back(std::move(file));
|
||||||
|
groupType = fileGroupType;
|
||||||
|
}
|
||||||
|
sendGroup();
|
||||||
|
}
|
||||||
|
|
||||||
bool HistoryWidget::confirmSendingFiles(
|
bool HistoryWidget::confirmSendingFiles(
|
||||||
QImage &&image,
|
QImage &&image,
|
||||||
QByteArray &&content,
|
QByteArray &&content,
|
||||||
|
|
|
@ -66,6 +66,7 @@ class LinkButton;
|
||||||
class RoundButton;
|
class RoundButton;
|
||||||
class PinnedBar;
|
class PinnedBar;
|
||||||
struct PreparedList;
|
struct PreparedList;
|
||||||
|
class SendFilesWay;
|
||||||
namespace Toast {
|
namespace Toast {
|
||||||
class Instance;
|
class Instance;
|
||||||
} // namespace Toast
|
} // namespace Toast
|
||||||
|
@ -425,6 +426,13 @@ private:
|
||||||
const QString &insertTextOnCancel = QString());
|
const QString &insertTextOnCancel = QString());
|
||||||
bool showSendingFilesError(const Ui::PreparedList &list) const;
|
bool showSendingFilesError(const Ui::PreparedList &list) const;
|
||||||
|
|
||||||
|
void sendingFilesConfirmed(
|
||||||
|
Ui::PreparedList &&list,
|
||||||
|
Ui::SendFilesWay way,
|
||||||
|
TextWithTags &&caption,
|
||||||
|
Api::SendOptions options,
|
||||||
|
bool ctrlShiftEnter);
|
||||||
|
|
||||||
void uploadFile(const QByteArray &fileContent, SendMediaType type);
|
void uploadFile(const QByteArray &fileContent, SendMediaType type);
|
||||||
|
|
||||||
void uploadFilesAfterConfirmation(
|
void uploadFilesAfterConfirmation(
|
||||||
|
|
|
@ -16,7 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
#include "styles/style_history.h"
|
#include "styles/style_chat.h"
|
||||||
|
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
|
@ -65,8 +65,9 @@ constexpr auto kAudioVoiceMsgUpdateView = crl::time(100);
|
||||||
|
|
||||||
Document::Document(
|
Document::Document(
|
||||||
not_null<Element*> parent,
|
not_null<Element*> parent,
|
||||||
|
not_null<HistoryItem*> realParent,
|
||||||
not_null<DocumentData*> document)
|
not_null<DocumentData*> document)
|
||||||
: File(parent, parent->data())
|
: File(parent, realParent)
|
||||||
, _data(document) {
|
, _data(document) {
|
||||||
const auto item = parent->data();
|
const auto item = parent->data();
|
||||||
auto caption = createCaption();
|
auto caption = createCaption();
|
||||||
|
@ -152,7 +153,7 @@ QSize Document::countOptimalSize() {
|
||||||
const auto item = _parent->data();
|
const auto item = _parent->data();
|
||||||
|
|
||||||
auto captioned = Get<HistoryDocumentCaptioned>();
|
auto captioned = Get<HistoryDocumentCaptioned>();
|
||||||
if (_parent->media() != this) {
|
if (_parent->media() != this && !_realParent->groupId()) {
|
||||||
if (captioned) {
|
if (captioned) {
|
||||||
RemoveComponents(HistoryDocumentCaptioned::Bit());
|
RemoveComponents(HistoryDocumentCaptioned::Bit());
|
||||||
captioned = nullptr;
|
captioned = nullptr;
|
||||||
|
@ -508,7 +509,7 @@ void Document::draw(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode == LayoutMode::Full) {
|
if (mode != LayoutMode::GroupedLast) {
|
||||||
if (auto captioned = Get<HistoryDocumentCaptioned>()) {
|
if (auto captioned = Get<HistoryDocumentCaptioned>()) {
|
||||||
p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg));
|
p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg));
|
||||||
captioned->_caption.draw(p, st::msgPadding.left(), bottom, captionw, style::al_left, 0, -1, selection);
|
captioned->_caption.draw(p, st::msgPadding.left(), bottom, captionw, style::al_left, 0, -1, selection);
|
||||||
|
@ -682,7 +683,7 @@ TextState Document::textState(
|
||||||
}
|
}
|
||||||
|
|
||||||
auto painth = layout.height();
|
auto painth = layout.height();
|
||||||
if (mode == LayoutMode::Full) {
|
if (mode != LayoutMode::GroupedLast) {
|
||||||
if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
|
if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
|
||||||
if (point.y() >= bottom) {
|
if (point.y() >= bottom) {
|
||||||
result = TextState(_parent, captioned->_caption.getState(
|
result = TextState(_parent, captioned->_caption.getState(
|
||||||
|
@ -859,10 +860,43 @@ bool Document::hideForwardedFrom() const {
|
||||||
return _data->isSong();
|
return _data->isSong();
|
||||||
}
|
}
|
||||||
|
|
||||||
QSize Document::sizeForGrouping() const {
|
QSize Document::sizeForGroupingOptimal(int maxWidth, bool last) const {
|
||||||
const auto height = st::msgFilePadding.top()
|
auto height = Has<HistoryDocumentThumbed>()
|
||||||
+ st::msgFileSize
|
? (st::msgFileThumbPadding.top()
|
||||||
+ st::msgFilePadding.bottom();
|
+ st::msgFileThumbSize
|
||||||
|
+ st::msgFileThumbPadding.bottom())
|
||||||
|
: (st::msgFilePadding.top()
|
||||||
|
+ st::msgFileSize
|
||||||
|
+ st::msgFilePadding.bottom());
|
||||||
|
if (!last) {
|
||||||
|
if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
|
||||||
|
auto captionw = maxWidth
|
||||||
|
- st::msgPadding.left()
|
||||||
|
- st::msgPadding.right();
|
||||||
|
height += captioned->_caption.countHeight(captionw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { maxWidth, height };
|
||||||
|
}
|
||||||
|
|
||||||
|
QSize Document::sizeForGrouping(int width, bool last) const {
|
||||||
|
auto height = Has<HistoryDocumentThumbed>()
|
||||||
|
? (st::msgFileThumbPadding.top()
|
||||||
|
+ st::msgFileThumbSize
|
||||||
|
+ st::msgFileThumbPadding.bottom())
|
||||||
|
: (st::msgFilePadding.top()
|
||||||
|
+ st::msgFileSize
|
||||||
|
+ st::msgFilePadding.bottom());
|
||||||
|
if (!last) {
|
||||||
|
if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
|
||||||
|
auto captionw = width
|
||||||
|
- st::msgPadding.left()
|
||||||
|
- st::msgPadding.right();
|
||||||
|
height += captioned->_caption.countHeight(captionw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return { maxWidth(), height };
|
return { maxWidth(), height };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -875,9 +909,15 @@ void Document::drawGrouped(
|
||||||
RectParts sides,
|
RectParts sides,
|
||||||
RectParts corners,
|
RectParts corners,
|
||||||
not_null<uint64*> cacheKey,
|
not_null<uint64*> cacheKey,
|
||||||
not_null<QPixmap*> cache) const {
|
not_null<QPixmap*> cache,
|
||||||
|
bool last) const {
|
||||||
p.translate(geometry.topLeft());
|
p.translate(geometry.topLeft());
|
||||||
draw(p, geometry.width(), selection, ms, LayoutMode::Grouped);
|
draw(
|
||||||
|
p,
|
||||||
|
geometry.width(),
|
||||||
|
selection,
|
||||||
|
ms,
|
||||||
|
last ? LayoutMode::GroupedLast : LayoutMode::Grouped);
|
||||||
p.translate(-geometry.topLeft());
|
p.translate(-geometry.topLeft());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -885,9 +925,14 @@ TextState Document::getStateGrouped(
|
||||||
const QRect &geometry,
|
const QRect &geometry,
|
||||||
RectParts sides,
|
RectParts sides,
|
||||||
QPoint point,
|
QPoint point,
|
||||||
StateRequest request) const {
|
StateRequest request,
|
||||||
|
bool last) const {
|
||||||
point -= geometry.topLeft();
|
point -= geometry.topLeft();
|
||||||
return textState(point, geometry.size(), request, LayoutMode::Grouped);
|
return textState(
|
||||||
|
point,
|
||||||
|
geometry.size(),
|
||||||
|
request,
|
||||||
|
last ? LayoutMode::GroupedLast : LayoutMode::Grouped);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Document::voiceProgressAnimationCallback(crl::time now) {
|
bool Document::voiceProgressAnimationCallback(crl::time now) {
|
||||||
|
@ -952,7 +997,7 @@ void Document::refreshParentId(not_null<HistoryItem*> realParent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Document::parentTextUpdated() {
|
void Document::parentTextUpdated() {
|
||||||
auto caption = (_parent->media() == this)
|
auto caption = (_parent->media() == this || _realParent->groupId())
|
||||||
? createCaption()
|
? createCaption()
|
||||||
: Ui::Text::String();
|
: Ui::Text::String();
|
||||||
if (!caption.isEmpty()) {
|
if (!caption.isEmpty()) {
|
||||||
|
@ -980,7 +1025,7 @@ Ui::Text::String Document::createCaption() {
|
||||||
? DocumentTimestampLinkBase(_data, _realParent->fullId())
|
? DocumentTimestampLinkBase(_data, _realParent->fullId())
|
||||||
: QString();
|
: QString();
|
||||||
return File::createCaption(
|
return File::createCaption(
|
||||||
_parent->data(),
|
_realParent,
|
||||||
timestampLinksDuration,
|
timestampLinksDuration,
|
||||||
timestampLinkBase);
|
timestampLinkBase);
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ class Document final
|
||||||
public:
|
public:
|
||||||
Document(
|
Document(
|
||||||
not_null<Element*> parent,
|
not_null<Element*> parent,
|
||||||
|
not_null<HistoryItem*> realParent,
|
||||||
not_null<DocumentData*> document);
|
not_null<DocumentData*> document);
|
||||||
~Document();
|
~Document();
|
||||||
|
|
||||||
|
@ -61,7 +62,8 @@ public:
|
||||||
QMargins bubbleMargins() const override;
|
QMargins bubbleMargins() const override;
|
||||||
bool hideForwardedFrom() const override;
|
bool hideForwardedFrom() const override;
|
||||||
|
|
||||||
QSize sizeForGrouping() const override;
|
QSize sizeForGroupingOptimal(int maxWidth, bool last) const override;
|
||||||
|
QSize sizeForGrouping(int width, bool last) const override;
|
||||||
void drawGrouped(
|
void drawGrouped(
|
||||||
Painter &p,
|
Painter &p,
|
||||||
const QRect &clip,
|
const QRect &clip,
|
||||||
|
@ -71,12 +73,14 @@ public:
|
||||||
RectParts sides,
|
RectParts sides,
|
||||||
RectParts corners,
|
RectParts corners,
|
||||||
not_null<uint64*> cacheKey,
|
not_null<uint64*> cacheKey,
|
||||||
not_null<QPixmap*> cache) const override;
|
not_null<QPixmap*> cache,
|
||||||
|
bool last) const override;
|
||||||
TextState getStateGrouped(
|
TextState getStateGrouped(
|
||||||
const QRect &geometry,
|
const QRect &geometry,
|
||||||
RectParts sides,
|
RectParts sides,
|
||||||
QPoint point,
|
QPoint point,
|
||||||
StateRequest request) const override;
|
StateRequest request,
|
||||||
|
bool last) const override;
|
||||||
|
|
||||||
bool voiceProgressAnimationCallback(crl::time now);
|
bool voiceProgressAnimationCallback(crl::time now);
|
||||||
|
|
||||||
|
@ -102,6 +106,7 @@ private:
|
||||||
enum class LayoutMode {
|
enum class LayoutMode {
|
||||||
Full,
|
Full,
|
||||||
Grouped,
|
Grouped,
|
||||||
|
GroupedLast,
|
||||||
};
|
};
|
||||||
|
|
||||||
void draw(
|
void draw(
|
||||||
|
|
|
@ -884,7 +884,11 @@ bool Gif::fullFeaturedGrouped(RectParts sides) const {
|
||||||
return (sides & RectPart::Left) && (sides & RectPart::Right);
|
return (sides & RectPart::Left) && (sides & RectPart::Right);
|
||||||
}
|
}
|
||||||
|
|
||||||
QSize Gif::sizeForGrouping() const {
|
QSize Gif::sizeForGroupingOptimal(int maxWidth, bool last) const {
|
||||||
|
return sizeForAspectRatio();
|
||||||
|
}
|
||||||
|
|
||||||
|
QSize Gif::sizeForGrouping(int width, bool last) const {
|
||||||
return sizeForAspectRatio();
|
return sizeForAspectRatio();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -897,7 +901,8 @@ void Gif::drawGrouped(
|
||||||
RectParts sides,
|
RectParts sides,
|
||||||
RectParts corners,
|
RectParts corners,
|
||||||
not_null<uint64*> cacheKey,
|
not_null<uint64*> cacheKey,
|
||||||
not_null<QPixmap*> cache) const {
|
not_null<QPixmap*> cache,
|
||||||
|
bool last) const {
|
||||||
ensureDataMediaCreated();
|
ensureDataMediaCreated();
|
||||||
const auto item = _parent->data();
|
const auto item = _parent->data();
|
||||||
const auto loaded = dataLoaded();
|
const auto loaded = dataLoaded();
|
||||||
|
@ -1085,7 +1090,8 @@ TextState Gif::getStateGrouped(
|
||||||
const QRect &geometry,
|
const QRect &geometry,
|
||||||
RectParts sides,
|
RectParts sides,
|
||||||
QPoint point,
|
QPoint point,
|
||||||
StateRequest request) const {
|
StateRequest request,
|
||||||
|
bool last) const {
|
||||||
if (!geometry.contains(point)) {
|
if (!geometry.contains(point)) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,7 +69,8 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
bool fullFeaturedGrouped(RectParts sides) const;
|
bool fullFeaturedGrouped(RectParts sides) const;
|
||||||
QSize sizeForGrouping() const override;
|
QSize sizeForGroupingOptimal(int maxWidth, bool last) const override;
|
||||||
|
QSize sizeForGrouping(int width, bool last) const override;
|
||||||
void drawGrouped(
|
void drawGrouped(
|
||||||
Painter &p,
|
Painter &p,
|
||||||
const QRect &clip,
|
const QRect &clip,
|
||||||
|
@ -79,12 +80,14 @@ public:
|
||||||
RectParts sides,
|
RectParts sides,
|
||||||
RectParts corners,
|
RectParts corners,
|
||||||
not_null<uint64*> cacheKey,
|
not_null<uint64*> cacheKey,
|
||||||
not_null<QPixmap*> cache) const override;
|
not_null<QPixmap*> cache,
|
||||||
|
bool last) const override;
|
||||||
TextState getStateGrouped(
|
TextState getStateGrouped(
|
||||||
const QRect &geometry,
|
const QRect &geometry,
|
||||||
RectParts sides,
|
RectParts sides,
|
||||||
QPoint point,
|
QPoint point,
|
||||||
StateRequest request) const override;
|
StateRequest request,
|
||||||
|
bool last) const override;
|
||||||
|
|
||||||
void stopAnimation() override;
|
void stopAnimation() override;
|
||||||
void checkAnimation() override;
|
void checkAnimation() override;
|
||||||
|
|
|
@ -182,7 +182,8 @@ TextState Media::getStateGrouped(
|
||||||
const QRect &geometry,
|
const QRect &geometry,
|
||||||
RectParts sides,
|
RectParts sides,
|
||||||
QPoint point,
|
QPoint point,
|
||||||
StateRequest request) const {
|
StateRequest request,
|
||||||
|
bool last) const {
|
||||||
Unexpected("Grouping method call.");
|
Unexpected("Grouping method call.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -163,7 +163,12 @@ public:
|
||||||
virtual void checkAnimation() {
|
virtual void checkAnimation() {
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] virtual QSize sizeForGrouping() const {
|
[[nodiscard]] virtual QSize sizeForGroupingOptimal(
|
||||||
|
int maxWidth,
|
||||||
|
bool last) const {
|
||||||
|
Unexpected("Grouping method call.");
|
||||||
|
}
|
||||||
|
[[nodiscard]] virtual QSize sizeForGrouping(int width, bool last) const {
|
||||||
Unexpected("Grouping method call.");
|
Unexpected("Grouping method call.");
|
||||||
}
|
}
|
||||||
virtual void drawGrouped(
|
virtual void drawGrouped(
|
||||||
|
@ -175,14 +180,16 @@ public:
|
||||||
RectParts sides,
|
RectParts sides,
|
||||||
RectParts corners,
|
RectParts corners,
|
||||||
not_null<uint64*> cacheKey,
|
not_null<uint64*> cacheKey,
|
||||||
not_null<QPixmap*> cache) const {
|
not_null<QPixmap*> cache,
|
||||||
|
bool last) const {
|
||||||
Unexpected("Grouping method call.");
|
Unexpected("Grouping method call.");
|
||||||
}
|
}
|
||||||
[[nodiscard]] virtual TextState getStateGrouped(
|
[[nodiscard]] virtual TextState getStateGrouped(
|
||||||
const QRect &geometry,
|
const QRect &geometry,
|
||||||
RectParts sides,
|
RectParts sides,
|
||||||
QPoint point,
|
QPoint point,
|
||||||
StateRequest request) const;
|
StateRequest request,
|
||||||
|
bool last) const;
|
||||||
|
|
||||||
[[nodiscard]] virtual bool animating() const {
|
[[nodiscard]] virtual bool animating() const {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -80,7 +80,7 @@ std::unique_ptr<Media> CreateAttach(
|
||||||
document,
|
document,
|
||||||
webpageUrl);
|
webpageUrl);
|
||||||
}
|
}
|
||||||
return std::make_unique<Document>(parent, document);
|
return std::make_unique<Document>(parent, parent->data(), document);
|
||||||
} else if (photo) {
|
} else if (photo) {
|
||||||
return std::make_unique<Photo>(
|
return std::make_unique<Photo>(
|
||||||
parent,
|
parent,
|
||||||
|
|
|
@ -94,8 +94,8 @@ GroupedMedia::~GroupedMedia() {
|
||||||
|
|
||||||
GroupedMedia::Mode GroupedMedia::DetectMode(not_null<Data::Media*> media) {
|
GroupedMedia::Mode GroupedMedia::DetectMode(not_null<Data::Media*> media) {
|
||||||
const auto document = media->document();
|
const auto document = media->document();
|
||||||
return (document && document->isSong())
|
return (document && !document->isVideoFile())
|
||||||
? Mode::Playlist
|
? Mode::Column
|
||||||
: Mode::Grid;
|
: Mode::Grid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,11 +107,20 @@ QSize GroupedMedia::countOptimalSize() {
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<QSize> sizes;
|
std::vector<QSize> sizes;
|
||||||
sizes.reserve(_parts.size());
|
const auto partsCount = _parts.size();
|
||||||
|
sizes.reserve(partsCount);
|
||||||
|
auto maxWidth = 0;
|
||||||
|
if (_mode == Mode::Column) {
|
||||||
|
for (const auto &part : _parts) {
|
||||||
|
const auto &media = part.content;
|
||||||
|
media->initDimensions();
|
||||||
|
accumulate_max(maxWidth, media->maxWidth());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto index = 0;
|
||||||
for (const auto &part : _parts) {
|
for (const auto &part : _parts) {
|
||||||
const auto &media = part.content;
|
const auto last = (++index == partsCount);
|
||||||
media->initDimensions();
|
sizes.push_back(part.content->sizeForGroupingOptimal(maxWidth, last));
|
||||||
sizes.push_back(media->sizeForGrouping());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto layout = (_mode == Mode::Grid)
|
const auto layout = (_mode == Mode::Grid)
|
||||||
|
@ -123,7 +132,6 @@ QSize GroupedMedia::countOptimalSize() {
|
||||||
: LayoutPlaylist(sizes);
|
: LayoutPlaylist(sizes);
|
||||||
Assert(layout.size() == _parts.size());
|
Assert(layout.size() == _parts.size());
|
||||||
|
|
||||||
auto maxWidth = 0;
|
|
||||||
auto minHeight = 0;
|
auto minHeight = 0;
|
||||||
for (auto i = 0, count = int(layout.size()); i != count; ++i) {
|
for (auto i = 0, count = int(layout.size()); i != count; ++i) {
|
||||||
const auto &item = layout[i];
|
const auto &item = layout[i];
|
||||||
|
@ -146,42 +154,51 @@ QSize GroupedMedia::countOptimalSize() {
|
||||||
QSize GroupedMedia::countCurrentSize(int newWidth) {
|
QSize GroupedMedia::countCurrentSize(int newWidth) {
|
||||||
accumulate_min(newWidth, maxWidth());
|
accumulate_min(newWidth, maxWidth());
|
||||||
auto newHeight = 0;
|
auto newHeight = 0;
|
||||||
if (newWidth < st::historyGroupWidthMin) {
|
if (_mode == Mode::Grid && newWidth < st::historyGroupWidthMin) {
|
||||||
return { newWidth, newHeight };
|
return { newWidth, newHeight };
|
||||||
|
} else if (_mode == Mode::Column) {
|
||||||
|
auto index = 0;
|
||||||
|
auto top = 0;
|
||||||
|
for (auto &part : _parts) {
|
||||||
|
const auto last = (++index == _parts.size());
|
||||||
|
const auto size = part.content->sizeForGrouping(newWidth, last);
|
||||||
|
part.geometry = QRect(0, top, newWidth, size.height());
|
||||||
|
top += size.height();
|
||||||
|
}
|
||||||
|
newHeight = top;
|
||||||
|
} else {
|
||||||
|
const auto initialSpacing = st::historyGroupSkip;
|
||||||
|
const auto factor = newWidth / float64(maxWidth());
|
||||||
|
const auto scale = [&](int value) {
|
||||||
|
return int(std::round(value * factor));
|
||||||
|
};
|
||||||
|
const auto spacing = scale(initialSpacing);
|
||||||
|
for (auto &part : _parts) {
|
||||||
|
const auto sides = part.sides;
|
||||||
|
const auto initialGeometry = part.initialGeometry;
|
||||||
|
const auto needRightSkip = !(sides & RectPart::Right);
|
||||||
|
const auto needBottomSkip = !(sides & RectPart::Bottom);
|
||||||
|
const auto initialLeft = initialGeometry.x();
|
||||||
|
const auto initialTop = initialGeometry.y();
|
||||||
|
const auto initialRight = initialLeft
|
||||||
|
+ initialGeometry.width()
|
||||||
|
+ (needRightSkip ? initialSpacing : 0);
|
||||||
|
const auto initialBottom = initialTop
|
||||||
|
+ initialGeometry.height()
|
||||||
|
+ (needBottomSkip ? initialSpacing : 0);
|
||||||
|
const auto left = scale(initialLeft);
|
||||||
|
const auto top = scale(initialTop);
|
||||||
|
const auto width = scale(initialRight)
|
||||||
|
- left
|
||||||
|
- (needRightSkip ? spacing : 0);
|
||||||
|
const auto height = scale(initialBottom)
|
||||||
|
- top
|
||||||
|
- (needBottomSkip ? spacing : 0);
|
||||||
|
part.geometry = QRect(left, top, width, height);
|
||||||
|
|
||||||
|
accumulate_max(newHeight, top + height);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto initialSpacing = st::historyGroupSkip;
|
|
||||||
const auto factor = newWidth / float64(maxWidth());
|
|
||||||
const auto scale = [&](int value) {
|
|
||||||
return int(std::round(value * factor));
|
|
||||||
};
|
|
||||||
const auto spacing = scale(initialSpacing);
|
|
||||||
for (auto &part : _parts) {
|
|
||||||
const auto sides = part.sides;
|
|
||||||
const auto initialGeometry = part.initialGeometry;
|
|
||||||
const auto needRightSkip = !(sides & RectPart::Right);
|
|
||||||
const auto needBottomSkip = !(sides & RectPart::Bottom);
|
|
||||||
const auto initialLeft = initialGeometry.x();
|
|
||||||
const auto initialTop = initialGeometry.y();
|
|
||||||
const auto initialRight = initialLeft
|
|
||||||
+ initialGeometry.width()
|
|
||||||
+ (needRightSkip ? initialSpacing : 0);
|
|
||||||
const auto initialBottom = initialTop
|
|
||||||
+ initialGeometry.height()
|
|
||||||
+ (needBottomSkip ? initialSpacing : 0);
|
|
||||||
const auto left = scale(initialLeft);
|
|
||||||
const auto top = scale(initialTop);
|
|
||||||
const auto width = scale(initialRight)
|
|
||||||
- left
|
|
||||||
- (needRightSkip ? spacing : 0);
|
|
||||||
const auto height = scale(initialBottom)
|
|
||||||
- top
|
|
||||||
- (needBottomSkip ? spacing : 0);
|
|
||||||
part.geometry = QRect(left, top, width, height);
|
|
||||||
|
|
||||||
accumulate_max(newHeight, top + height);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_caption.isEmpty()) {
|
if (!_caption.isEmpty()) {
|
||||||
const auto captionw = newWidth - st::msgPadding.left() - st::msgPadding.right();
|
const auto captionw = newWidth - st::msgPadding.left() - st::msgPadding.right();
|
||||||
newHeight += st::mediaCaptionSkip + _caption.countHeight(captionw);
|
newHeight += st::mediaCaptionSkip + _caption.countHeight(captionw);
|
||||||
|
@ -223,6 +240,7 @@ void GroupedMedia::draw(
|
||||||
: IsGroupItemSelection(selection, i)
|
: IsGroupItemSelection(selection, i)
|
||||||
? FullSelection
|
? FullSelection
|
||||||
: TextSelection();
|
: TextSelection();
|
||||||
|
const auto last = (i + 1 == count);
|
||||||
part.content->drawGrouped(
|
part.content->drawGrouped(
|
||||||
p,
|
p,
|
||||||
clip,
|
clip,
|
||||||
|
@ -232,7 +250,8 @@ void GroupedMedia::draw(
|
||||||
part.sides,
|
part.sides,
|
||||||
cornersFromSides(part.sides),
|
cornersFromSides(part.sides),
|
||||||
&part.cacheKey,
|
&part.cacheKey,
|
||||||
&part.cache);
|
&part.cache,
|
||||||
|
last);
|
||||||
}
|
}
|
||||||
|
|
||||||
// date
|
// date
|
||||||
|
@ -262,13 +281,17 @@ void GroupedMedia::draw(
|
||||||
TextState GroupedMedia::getPartState(
|
TextState GroupedMedia::getPartState(
|
||||||
QPoint point,
|
QPoint point,
|
||||||
StateRequest request) const {
|
StateRequest request) const {
|
||||||
|
auto index = 0;
|
||||||
for (const auto &part : _parts) {
|
for (const auto &part : _parts) {
|
||||||
|
++index;
|
||||||
if (part.geometry.contains(point)) {
|
if (part.geometry.contains(point)) {
|
||||||
|
const auto last = (index == _parts.size());
|
||||||
auto result = part.content->getStateGrouped(
|
auto result = part.content->getStateGrouped(
|
||||||
part.geometry,
|
part.geometry,
|
||||||
part.sides,
|
part.sides,
|
||||||
point,
|
point,
|
||||||
request);
|
request,
|
||||||
|
last);
|
||||||
result.itemId = part.item->fullId();
|
result.itemId = part.item->fullId();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -372,6 +395,9 @@ auto GroupedMedia::getBubbleSelectionIntervals(
|
||||||
last = BubbleSelectionInterval{ newTop, newHeight };
|
last = BubbleSelectionInterval{ newTop, newHeight };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (IsGroupItemSelection(selection, _parts.size() - 1)) {
|
||||||
|
result.back().height = height() - result.back().top;
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -465,6 +491,10 @@ HistoryMessageEdited *GroupedMedia::displayedEditBadge() const {
|
||||||
|
|
||||||
void GroupedMedia::updateNeedBubbleState() {
|
void GroupedMedia::updateNeedBubbleState() {
|
||||||
const auto captionItem = [&]() -> HistoryItem* {
|
const auto captionItem = [&]() -> HistoryItem* {
|
||||||
|
if (_mode == Mode::Column) {
|
||||||
|
const auto last = _parts.back().item.get();
|
||||||
|
return last->emptyText() ? nullptr : last;
|
||||||
|
}
|
||||||
auto result = (HistoryItem*)nullptr;
|
auto result = (HistoryItem*)nullptr;
|
||||||
for (const auto &part : _parts) {
|
for (const auto &part : _parts) {
|
||||||
if (!part.item->emptyText()) {
|
if (!part.item->emptyText()) {
|
||||||
|
@ -519,7 +549,7 @@ bool GroupedMedia::needsBubble() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GroupedMedia::computeNeedBubble() const {
|
bool GroupedMedia::computeNeedBubble() const {
|
||||||
if (!_caption.isEmpty() || _mode == Mode::Playlist) {
|
if (!_caption.isEmpty() || _mode == Mode::Column) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (const auto item = _parent->data()) {
|
if (const auto item = _parent->data()) {
|
||||||
|
@ -537,7 +567,7 @@ bool GroupedMedia::computeNeedBubble() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GroupedMedia::needInfoDisplay() const {
|
bool GroupedMedia::needInfoDisplay() const {
|
||||||
return (_mode != Mode::Playlist)
|
return (_mode != Mode::Column)
|
||||||
&& (_parent->data()->id < 0
|
&& (_parent->data()->id < 0
|
||||||
|| _parent->isUnderCursor()
|
|| _parent->isUnderCursor()
|
||||||
|| _parent->isLastAndSelfMessage());
|
|| _parent->isLastAndSelfMessage());
|
||||||
|
|
|
@ -86,7 +86,7 @@ public:
|
||||||
void updateNeedBubbleState() override;
|
void updateNeedBubbleState() override;
|
||||||
bool needsBubble() const override;
|
bool needsBubble() const override;
|
||||||
bool customInfoLayout() const override {
|
bool customInfoLayout() const override {
|
||||||
return _caption.isEmpty() && (_mode != Mode::Playlist);
|
return _caption.isEmpty() && (_mode != Mode::Column);
|
||||||
}
|
}
|
||||||
bool allowsFastShare() const override {
|
bool allowsFastShare() const override {
|
||||||
return true;
|
return true;
|
||||||
|
@ -102,7 +102,7 @@ public:
|
||||||
private:
|
private:
|
||||||
enum class Mode : char {
|
enum class Mode : char {
|
||||||
Grid,
|
Grid,
|
||||||
Playlist,
|
Column,
|
||||||
};
|
};
|
||||||
struct Part {
|
struct Part {
|
||||||
Part(
|
Part(
|
||||||
|
|
|
@ -465,12 +465,16 @@ TextState Photo::textState(QPoint point, StateRequest request) const {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
QSize Photo::sizeForGrouping() const {
|
QSize Photo::sizeForGroupingOptimal(int maxWidth, bool last) const {
|
||||||
const auto width = _data->width();
|
const auto width = _data->width();
|
||||||
const auto height = _data->height();
|
const auto height = _data->height();
|
||||||
return { std::max(width, 1), std::max(height, 1) };
|
return { std::max(width, 1), std::max(height, 1) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QSize Photo::sizeForGrouping(int width, bool last) const {
|
||||||
|
return sizeForGroupingOptimal(width, last);
|
||||||
|
}
|
||||||
|
|
||||||
void Photo::drawGrouped(
|
void Photo::drawGrouped(
|
||||||
Painter &p,
|
Painter &p,
|
||||||
const QRect &clip,
|
const QRect &clip,
|
||||||
|
@ -480,7 +484,8 @@ void Photo::drawGrouped(
|
||||||
RectParts sides,
|
RectParts sides,
|
||||||
RectParts corners,
|
RectParts corners,
|
||||||
not_null<uint64*> cacheKey,
|
not_null<uint64*> cacheKey,
|
||||||
not_null<QPixmap*> cache) const {
|
not_null<QPixmap*> cache,
|
||||||
|
bool last) const {
|
||||||
ensureDataMediaCreated();
|
ensureDataMediaCreated();
|
||||||
_dataMedia->automaticLoad(_realParent->fullId(), _parent->data());
|
_dataMedia->automaticLoad(_realParent->fullId(), _parent->data());
|
||||||
|
|
||||||
|
@ -580,7 +585,8 @@ TextState Photo::getStateGrouped(
|
||||||
const QRect &geometry,
|
const QRect &geometry,
|
||||||
RectParts sides,
|
RectParts sides,
|
||||||
QPoint point,
|
QPoint point,
|
||||||
StateRequest request) const {
|
StateRequest request,
|
||||||
|
bool last) const {
|
||||||
if (!geometry.contains(point)) {
|
if (!geometry.contains(point)) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ struct Information;
|
||||||
|
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
|
|
||||||
class Photo : public File {
|
class Photo final : public File {
|
||||||
public:
|
public:
|
||||||
Photo(
|
Photo(
|
||||||
not_null<Element*> parent,
|
not_null<Element*> parent,
|
||||||
|
@ -58,7 +58,8 @@ public:
|
||||||
return _data;
|
return _data;
|
||||||
}
|
}
|
||||||
|
|
||||||
QSize sizeForGrouping() const override;
|
QSize sizeForGroupingOptimal(int maxWidth, bool last) const override;
|
||||||
|
QSize sizeForGrouping(int width, bool last) const override;
|
||||||
void drawGrouped(
|
void drawGrouped(
|
||||||
Painter &p,
|
Painter &p,
|
||||||
const QRect &clip,
|
const QRect &clip,
|
||||||
|
@ -68,12 +69,14 @@ public:
|
||||||
RectParts sides,
|
RectParts sides,
|
||||||
RectParts corners,
|
RectParts corners,
|
||||||
not_null<uint64*> cacheKey,
|
not_null<uint64*> cacheKey,
|
||||||
not_null<QPixmap*> cache) const override;
|
not_null<QPixmap*> cache,
|
||||||
|
bool last) const override;
|
||||||
TextState getStateGrouped(
|
TextState getStateGrouped(
|
||||||
const QRect &geometry,
|
const QRect &geometry,
|
||||||
RectParts sides,
|
RectParts sides,
|
||||||
QPoint point,
|
QPoint point,
|
||||||
StateRequest request) const override;
|
StateRequest request,
|
||||||
|
bool last) const override;
|
||||||
|
|
||||||
TextWithEntities getCaption() const override {
|
TextWithEntities getCaption() const override {
|
||||||
return _caption.toTextWithEntities();
|
return _caption.toTextWithEntities();
|
||||||
|
|
Loading…
Add table
Reference in a new issue