diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 54695ea3ce..9ff6b5c1fd 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -957,18 +957,18 @@ WebPageData *MediaWebPage::webpage() const { } bool MediaWebPage::hasReplyPreview() const { - if (const auto document = _page->document) { + if (const auto document = MediaWebPage::document()) { return !document->thumb->isNull(); - } else if (const auto photo = _page->photo) { + } else if (const auto photo = MediaWebPage::photo()) { return !photo->thumb->isNull(); } return false; } ImagePtr MediaWebPage::replyPreview() const { - if (const auto document = _page->document) { + if (const auto document = MediaWebPage::document()) { return document->makeReplyPreview(parent()->fullId()); - } else if (const auto photo = _page->photo) { + } else if (const auto photo = MediaWebPage::photo()) { return photo->makeReplyPreview(parent()->fullId()); } return ImagePtr(); diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 7ef6ece7ba..507670e46a 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -1267,6 +1267,7 @@ not_null Session::webpage(const MTPDwebPagePending &data) { TextWithEntities(), nullptr, nullptr, + WebPageCollage(), 0, QString(), data.vdate.v @@ -1289,6 +1290,7 @@ not_null Session::webpage( content, nullptr, nullptr, + WebPageCollage(), 0, QString(), TimeId(0)); @@ -1304,6 +1306,7 @@ not_null Session::webpage( const TextWithEntities &description, PhotoData *photo, DocumentData *document, + WebPageCollage &&collage, int duration, const QString &author, TimeId pendingTill) { @@ -1318,6 +1321,7 @@ not_null Session::webpage( description, photo, document, + std::move(collage), duration, author, pendingTill); @@ -1351,6 +1355,7 @@ void Session::webpageApplyFields( description, data.has_photo() ? photo(data.vphoto).get() : nullptr, data.has_document() ? document(data.vdocument).get() : nullptr, + WebPageCollage(data), data.has_duration() ? data.vduration.v : 0, data.has_author() ? qs(data.vauthor) : QString(), pendingTill); @@ -1366,6 +1371,7 @@ void Session::webpageApplyFields( const TextWithEntities &description, PhotoData *photo, DocumentData *document, + WebPageCollage &&collage, int duration, const QString &author, TimeId pendingTill) { @@ -1379,6 +1385,7 @@ void Session::webpageApplyFields( description, photo, document, + std::move(collage), duration, author, pendingTill); diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index 4f11fc91c0..0fcf7a782a 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class HistoryItem; class BoxContent; +struct WebPageCollage; namespace HistoryView { struct Group; @@ -309,6 +310,7 @@ public: const TextWithEntities &description, PhotoData *photo, DocumentData *document, + WebPageCollage &&collage, int duration, const QString &author, TimeId pendingTill); @@ -490,6 +492,7 @@ private: const TextWithEntities &description, PhotoData *photo, DocumentData *document, + WebPageCollage &&collage, int duration, const QString &author, TimeId pendingTill); diff --git a/Telegram/SourceFiles/data/data_web_page.cpp b/Telegram/SourceFiles/data/data_web_page.cpp index 430e460c5e..6a21153759 100644 --- a/Telegram/SourceFiles/data/data_web_page.cpp +++ b/Telegram/SourceFiles/data/data_web_page.cpp @@ -10,6 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "auth_session.h" #include "apiwrap.h" #include "mainwidget.h" +#include "data/data_session.h" +#include "data/data_photo.h" +#include "data/data_document.h" +#include "ui/image/image.h" #include "ui/text/text_entity.h" namespace { @@ -29,8 +33,94 @@ QString SiteNameFromUrl(const QString &url) { return QString(); } +WebPageCollage ExtractCollage( + const QVector &items, + const QVector &photos, + const QVector &documents) { + const auto count = items.size(); + if (count < 2) { + return {}; + } + const auto bad = ranges::find_if(items, [](mtpTypeId type) { + return (type != mtpc_pageBlockPhoto && type != mtpc_pageBlockVideo); + }, [](const MTPPageBlock &item) { + return item.type(); + }); + if (bad != items.end()) { + return {}; + } + + auto &storage = Auth().data(); + for (const auto &photo : photos) { + storage.photo(photo); + } + for (const auto &document : documents) { + storage.document(document); + } + auto result = WebPageCollage(); + result.items.reserve(count); + for (const auto &item : items) { + const auto good = item.match([&](const MTPDpageBlockPhoto &data) { + const auto photo = storage.photo(data.vphoto_id.v); + if (photo->full->isNull()) { + return false; + } + result.items.push_back(photo); + return true; + }, [&](const MTPDpageBlockVideo &data) { + const auto document = storage.document(data.vvideo_id.v); + if (!document->isVideoFile()) { + return false; + } + result.items.push_back(document); + return true; + }, [](const auto &) -> bool { + Unexpected("Type of block in Collage."); + }); + if (!good) { + return {}; + } + } + return result; +} + +WebPageCollage ExtractCollage(const MTPDwebPage &data) { + if (!data.has_cached_page()) { + return {}; + } + return data.vcached_page.match([&](const auto &page) { + for (const auto &block : page.vblocks.v) { + switch (block.type()) { + case mtpc_pageBlockPhoto: + case mtpc_pageBlockVideo: + case mtpc_pageBlockCover: + case mtpc_pageBlockEmbed: + case mtpc_pageBlockEmbedPost: + case mtpc_pageBlockAudio: + return WebPageCollage(); + case mtpc_pageBlockSlideshow: + return ExtractCollage( + block.c_pageBlockSlideshow().vitems.v, + page.vphotos.v, + page.vdocuments.v); + case mtpc_pageBlockCollage: + return ExtractCollage( + block.c_pageBlockCollage().vitems.v, + page.vphotos.v, + page.vdocuments.v); + default: break; + } + } + return WebPageCollage(); + }); +} + } // namespace +WebPageCollage::WebPageCollage(const MTPDwebPage &data) +: WebPageCollage(ExtractCollage(data)) { +} + bool WebPageData::applyChanges( const QString &newType, const QString &newUrl, @@ -40,6 +130,7 @@ bool WebPageData::applyChanges( const TextWithEntities &newDescription, PhotoData *newPhoto, DocumentData *newDocument, + WebPageCollage &&newCollage, int newDuration, const QString &newAuthor, int newPendingTill) { @@ -83,6 +174,7 @@ bool WebPageData::applyChanges( && description.text == newDescription.text && photo == newPhoto && document == newDocument + && collage.items == newCollage.items && duration == newDuration && author == resultAuthor && pendingTill == newPendingTill) { @@ -99,6 +191,7 @@ bool WebPageData::applyChanges( description = newDescription; photo = newPhoto; document = newDocument; + collage = std::move(newCollage); duration = newDuration; author = resultAuthor; pendingTill = newPendingTill; diff --git a/Telegram/SourceFiles/data/data_web_page.h b/Telegram/SourceFiles/data/data_web_page.h index 164dbc20fb..16756d2848 100644 --- a/Telegram/SourceFiles/data/data_web_page.h +++ b/Telegram/SourceFiles/data/data_web_page.h @@ -24,6 +24,16 @@ inline WebPageType toWebPageType(const QString &type) { return WebPageArticle; } +struct WebPageCollage { + using Item = base::variant; + + WebPageCollage() = default; + explicit WebPageCollage(const MTPDwebPage &data); + + std::vector items; + +}; + struct WebPageData { WebPageData(const WebPageId &id) : id(id) { } @@ -35,8 +45,9 @@ struct WebPageData { const QString &siteName, const QString &title, const TextWithEntities &description, - DocumentData *document, PhotoData *photo, + DocumentData *document, + WebPageCollage &&collage, int duration, const QString &author, int pendingTill) @@ -51,6 +62,7 @@ struct WebPageData { , author(author) , photo(photo) , document(document) + , collage(std::move(collage)) , pendingTill(pendingTill) { } @@ -63,6 +75,7 @@ struct WebPageData { const TextWithEntities &newDescription, PhotoData *newPhoto, DocumentData *newDocument, + WebPageCollage &&newCollage, int newDuration, const QString &newAuthor, int newPendingTill); @@ -78,6 +91,7 @@ struct WebPageData { QString author; PhotoData *photo = nullptr; DocumentData *document = nullptr; + WebPageCollage collage; int pendingTill = 0; int version = 0; diff --git a/Telegram/SourceFiles/history/history_media_grouped.cpp b/Telegram/SourceFiles/history/history_media_grouped.cpp index 0c4da81924..0bdc58281d 100644 --- a/Telegram/SourceFiles/history/history_media_grouped.cpp +++ b/Telegram/SourceFiles/history/history_media_grouped.cpp @@ -27,12 +27,29 @@ namespace { using TextState = HistoryView::TextState; using PointState = HistoryView::PointState; -constexpr auto kMaxDisplayedGroupSize = 10; - } // namespace -HistoryGroupedMedia::Part::Part(not_null item) -: item(item) { +HistoryGroupedMedia::Part::Part( + not_null parent, + not_null media) +: item(media->parent()) +, content(media->createView(parent, item)) { + Assert(media->canBeGrouped()); +} + +HistoryGroupedMedia::HistoryGroupedMedia( + not_null parent, + const std::vector> &medias) +: HistoryMedia(parent) +, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) { + const auto truncated = ranges::view::all( + medias + ) | ranges::view::transform([](const std::unique_ptr &v) { + return not_null(v.get()); + }) | ranges::view::take(kMaxSize); + const auto result = applyGroup(truncated); + + Ensures(result); } HistoryGroupedMedia::HistoryGroupedMedia( @@ -40,11 +57,12 @@ HistoryGroupedMedia::HistoryGroupedMedia( const std::vector> &items) : HistoryMedia(parent) , _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) { - const auto result = (items.size() <= kMaxDisplayedGroupSize) - ? applyGroup(items) - : applyGroup(std::vector>( - begin(items), - begin(items) + kMaxDisplayedGroupSize)); + const auto medias = ranges::view::all( + items + ) | ranges::view::transform([](not_null item) { + return item->media(); + }) | ranges::view::take(kMaxSize); + const auto result = applyGroup(medias); Ensures(result); } @@ -312,38 +330,35 @@ void HistoryGroupedMedia::clickHandlerPressedChanged( } } -bool HistoryGroupedMedia::applyGroup( - const std::vector> &items) { - Expects(items.size() <= kMaxDisplayedGroupSize); - - if (items.empty()) { - return false; - } - if (validateGroupParts(items)) { +template +bool HistoryGroupedMedia::applyGroup(const DataMediaRange &medias) { + if (validateGroupParts(medias)) { return true; } - for (const auto item : items) { - const auto media = item->media(); - Assert(media != nullptr && media->canBeGrouped()); + for (const auto media : medias) { + _parts.push_back(Part(_parent, media)); + } + if (_parts.empty()) { + return false; + } - _parts.push_back(Part(item)); - _parts.back().content = media->createView(_parent, item); - }; + Ensures(_parts.size() <= kMaxSize); return true; } +template bool HistoryGroupedMedia::validateGroupParts( - const std::vector> &items) const { - if (_parts.size() != items.size()) { - return false; - } - for (auto i = 0, count = int(items.size()); i != count; ++i) { - if (_parts[i].item != items[i]) { + const DataMediaRange &medias) const { + auto i = 0; + const auto count = _parts.size(); + for (const auto media : medias) { + if (i >= count || _parts[i].item != media->parent()) { return false; } + ++i; } - return true; + return (i == count); } not_null HistoryGroupedMedia::main() const { diff --git a/Telegram/SourceFiles/history/history_media_grouped.h b/Telegram/SourceFiles/history/history_media_grouped.h index dc398d772d..8320312f02 100644 --- a/Telegram/SourceFiles/history/history_media_grouped.h +++ b/Telegram/SourceFiles/history/history_media_grouped.h @@ -11,8 +11,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_document.h" #include "data/data_photo.h" +namespace Data { +class Media; +} // namespace Data + class HistoryGroupedMedia : public HistoryMedia { public: + static constexpr auto kMaxSize = 10; + + HistoryGroupedMedia( + not_null parent, + const std::vector> &medias); HistoryGroupedMedia( not_null parent, const std::vector> &items); @@ -83,7 +92,9 @@ public: private: struct Part { - Part(not_null item); + Part( + not_null parent, + not_null media); not_null item; std::unique_ptr content; @@ -96,15 +107,18 @@ private: }; - bool applyGroup(const std::vector> &items); + template + bool applyGroup(const DataMediaRange &medias); + + template + bool validateGroupParts(const DataMediaRange &medias) const; + QSize countOptimalSize() override; QSize countCurrentSize(int newWidth) override; bool needInfoDisplay() const; bool computeNeedBubble() const; not_null main() const; - bool validateGroupParts( - const std::vector> &items) const; TextState getPartState( QPoint point, StateRequest request) const; diff --git a/Telegram/SourceFiles/history/history_media_types.cpp b/Telegram/SourceFiles/history/history_media_types.cpp index 4b74336d91..82f5e6177e 100644 --- a/Telegram/SourceFiles/history/history_media_types.cpp +++ b/Telegram/SourceFiles/history/history_media_types.cpp @@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_item_components.h" #include "history/history_location_manager.h" #include "history/history_message.h" +#include "history/history_media_grouped.h" #include "history/view/history_view_element.h" #include "history/view/history_view_cursor_state.h" #include "window/main_window.h" @@ -71,8 +72,11 @@ int gifMaxStatusWidth(DocumentData *document) { std::unique_ptr CreateAttach( not_null parent, DocumentData *document, - PhotoData *photo) { - if (document) { + PhotoData *photo, + const std::vector> &collage = {}) { + if (!collage.empty()) { + return std::make_unique(parent, collage); + } else if (document) { if (document->sticker()) { return std::make_unique(parent, document); } else if (document->isAnimation()) { @@ -97,6 +101,30 @@ std::unique_ptr CreateAttach( return nullptr; } +std::vector> PrepareCollageMedia( + not_null parent, + const WebPageCollage &data) { + auto result = std::vector>(); + result.reserve(data.items.size()); + for (const auto item : data.items) { + if (const auto document = base::get_if(&item)) { + result.push_back(std::make_unique( + parent, + *document)); + } else if (const auto photo = base::get_if(&item)) { + result.push_back(std::make_unique( + parent, + *photo)); + } else { + return {}; + } + if (!result.back()->canBeGrouped()) { + return {}; + } + } + return result; +} + } // namespace QString FillAmountAndCurrency(uint64 amount, const QString ¤cy) { @@ -3354,6 +3382,7 @@ QSize HistoryWebPage::countOptimalSize() { _dataVersion = _data->version; _openl = nullptr; _attach = nullptr; + _collage = PrepareCollageMedia(_parent->data(), _data->collage); _title = Text(st::msgMinWidth - st::webPageLeft); _description = Text(st::msgMinWidth - st::webPageLeft); _siteNameWidth = 0; @@ -3366,7 +3395,9 @@ QSize HistoryWebPage::countOptimalSize() { // init layout auto title = TextUtilities::SingleLine(_data->title.isEmpty() ? _data->author : _data->title); - if (!_data->document && _data->photo && _data->type != WebPagePhoto && _data->type != WebPageVideo) { + if (!_collage.empty()) { + _asArticle = false; + } else if (!_data->document && _data->photo && _data->type != WebPagePhoto && _data->type != WebPageVideo) { if (_data->type == WebPageProfile) { _asArticle = true; } else if (_data->siteName == qstr("Twitter") || _data->siteName == qstr("Facebook")) { @@ -3383,7 +3414,11 @@ QSize HistoryWebPage::countOptimalSize() { // init attach if (!_attach && !_asArticle) { - _attach = CreateAttach(_parent, _data->document, _data->photo); + _attach = CreateAttach( + _parent, + _data->document, + _data->photo, + _collage); } auto textFloatsAroundInfo = !_asArticle && !_attach && isBubbleBottom(); @@ -3804,7 +3839,7 @@ TextState HistoryWebPage::textState(QPoint point, StateRequest request) const { if (rtl()) attachLeft = width() - attachLeft - _attach->width(); result = _attach->textState(point - QPoint(attachLeft, attachTop), request); - if (result.link && !_data->document && _data->photo && _attach->isReadyForOpen()) { + if (result.link && !_data->document && _data->photo && _collage.empty() && _attach->isReadyForOpen()) { if (_data->type == WebPageProfile || _data->type == WebPageVideo) { result.link = _openl; } else if (_data->type == WebPagePhoto || _data->siteName == qstr("Twitter") || _data->siteName == qstr("Facebook")) { diff --git a/Telegram/SourceFiles/history/history_media_types.h b/Telegram/SourceFiles/history/history_media_types.h index 4e2a898ce6..29be982b61 100644 --- a/Telegram/SourceFiles/history/history_media_types.h +++ b/Telegram/SourceFiles/history/history_media_types.h @@ -20,11 +20,13 @@ struct HistoryDocumentNamed; struct HistoryMessageVia; struct HistoryMessageReply; struct HistoryMessageForwarded; +struct WebPageCollage; namespace Data { enum class CallFinishReason : char; struct Invoice; struct Call; +class Media; } // namespace Data namespace Media { @@ -730,6 +732,7 @@ private: bool isLogEntryOriginal() const; not_null _data; + std::vector> _collage; ClickHandlerPtr _openl; std::unique_ptr _attach;