Show collage/slideshow as an album in web page.

This commit is contained in:
John Preston 2018-10-24 15:52:31 +04:00
parent e8722e1cb2
commit 251f51ca1b
9 changed files with 228 additions and 44 deletions

View File

@ -957,18 +957,18 @@ WebPageData *MediaWebPage::webpage() const {
} }
bool MediaWebPage::hasReplyPreview() const { bool MediaWebPage::hasReplyPreview() const {
if (const auto document = _page->document) { if (const auto document = MediaWebPage::document()) {
return !document->thumb->isNull(); return !document->thumb->isNull();
} else if (const auto photo = _page->photo) { } else if (const auto photo = MediaWebPage::photo()) {
return !photo->thumb->isNull(); return !photo->thumb->isNull();
} }
return false; return false;
} }
ImagePtr MediaWebPage::replyPreview() const { ImagePtr MediaWebPage::replyPreview() const {
if (const auto document = _page->document) { if (const auto document = MediaWebPage::document()) {
return document->makeReplyPreview(parent()->fullId()); 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 photo->makeReplyPreview(parent()->fullId());
} }
return ImagePtr(); return ImagePtr();

View File

@ -1267,6 +1267,7 @@ not_null<WebPageData*> Session::webpage(const MTPDwebPagePending &data) {
TextWithEntities(), TextWithEntities(),
nullptr, nullptr,
nullptr, nullptr,
WebPageCollage(),
0, 0,
QString(), QString(),
data.vdate.v data.vdate.v
@ -1289,6 +1290,7 @@ not_null<WebPageData*> Session::webpage(
content, content,
nullptr, nullptr,
nullptr, nullptr,
WebPageCollage(),
0, 0,
QString(), QString(),
TimeId(0)); TimeId(0));
@ -1304,6 +1306,7 @@ not_null<WebPageData*> Session::webpage(
const TextWithEntities &description, const TextWithEntities &description,
PhotoData *photo, PhotoData *photo,
DocumentData *document, DocumentData *document,
WebPageCollage &&collage,
int duration, int duration,
const QString &author, const QString &author,
TimeId pendingTill) { TimeId pendingTill) {
@ -1318,6 +1321,7 @@ not_null<WebPageData*> Session::webpage(
description, description,
photo, photo,
document, document,
std::move(collage),
duration, duration,
author, author,
pendingTill); pendingTill);
@ -1351,6 +1355,7 @@ void Session::webpageApplyFields(
description, description,
data.has_photo() ? photo(data.vphoto).get() : nullptr, data.has_photo() ? photo(data.vphoto).get() : nullptr,
data.has_document() ? document(data.vdocument).get() : nullptr, data.has_document() ? document(data.vdocument).get() : nullptr,
WebPageCollage(data),
data.has_duration() ? data.vduration.v : 0, data.has_duration() ? data.vduration.v : 0,
data.has_author() ? qs(data.vauthor) : QString(), data.has_author() ? qs(data.vauthor) : QString(),
pendingTill); pendingTill);
@ -1366,6 +1371,7 @@ void Session::webpageApplyFields(
const TextWithEntities &description, const TextWithEntities &description,
PhotoData *photo, PhotoData *photo,
DocumentData *document, DocumentData *document,
WebPageCollage &&collage,
int duration, int duration,
const QString &author, const QString &author,
TimeId pendingTill) { TimeId pendingTill) {
@ -1379,6 +1385,7 @@ void Session::webpageApplyFields(
description, description,
photo, photo,
document, document,
std::move(collage),
duration, duration,
author, author,
pendingTill); pendingTill);

View File

@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class HistoryItem; class HistoryItem;
class BoxContent; class BoxContent;
struct WebPageCollage;
namespace HistoryView { namespace HistoryView {
struct Group; struct Group;
@ -309,6 +310,7 @@ public:
const TextWithEntities &description, const TextWithEntities &description,
PhotoData *photo, PhotoData *photo,
DocumentData *document, DocumentData *document,
WebPageCollage &&collage,
int duration, int duration,
const QString &author, const QString &author,
TimeId pendingTill); TimeId pendingTill);
@ -490,6 +492,7 @@ private:
const TextWithEntities &description, const TextWithEntities &description,
PhotoData *photo, PhotoData *photo,
DocumentData *document, DocumentData *document,
WebPageCollage &&collage,
int duration, int duration,
const QString &author, const QString &author,
TimeId pendingTill); TimeId pendingTill);

View File

@ -10,6 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "auth_session.h" #include "auth_session.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "mainwidget.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" #include "ui/text/text_entity.h"
namespace { namespace {
@ -29,8 +33,94 @@ QString SiteNameFromUrl(const QString &url) {
return QString(); return QString();
} }
WebPageCollage ExtractCollage(
const QVector<MTPPageBlock> &items,
const QVector<MTPPhoto> &photos,
const QVector<MTPDocument> &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 } // namespace
WebPageCollage::WebPageCollage(const MTPDwebPage &data)
: WebPageCollage(ExtractCollage(data)) {
}
bool WebPageData::applyChanges( bool WebPageData::applyChanges(
const QString &newType, const QString &newType,
const QString &newUrl, const QString &newUrl,
@ -40,6 +130,7 @@ bool WebPageData::applyChanges(
const TextWithEntities &newDescription, const TextWithEntities &newDescription,
PhotoData *newPhoto, PhotoData *newPhoto,
DocumentData *newDocument, DocumentData *newDocument,
WebPageCollage &&newCollage,
int newDuration, int newDuration,
const QString &newAuthor, const QString &newAuthor,
int newPendingTill) { int newPendingTill) {
@ -83,6 +174,7 @@ bool WebPageData::applyChanges(
&& description.text == newDescription.text && description.text == newDescription.text
&& photo == newPhoto && photo == newPhoto
&& document == newDocument && document == newDocument
&& collage.items == newCollage.items
&& duration == newDuration && duration == newDuration
&& author == resultAuthor && author == resultAuthor
&& pendingTill == newPendingTill) { && pendingTill == newPendingTill) {
@ -99,6 +191,7 @@ bool WebPageData::applyChanges(
description = newDescription; description = newDescription;
photo = newPhoto; photo = newPhoto;
document = newDocument; document = newDocument;
collage = std::move(newCollage);
duration = newDuration; duration = newDuration;
author = resultAuthor; author = resultAuthor;
pendingTill = newPendingTill; pendingTill = newPendingTill;

View File

@ -24,6 +24,16 @@ inline WebPageType toWebPageType(const QString &type) {
return WebPageArticle; return WebPageArticle;
} }
struct WebPageCollage {
using Item = base::variant<PhotoData*, DocumentData*>;
WebPageCollage() = default;
explicit WebPageCollage(const MTPDwebPage &data);
std::vector<Item> items;
};
struct WebPageData { struct WebPageData {
WebPageData(const WebPageId &id) : id(id) { WebPageData(const WebPageId &id) : id(id) {
} }
@ -35,8 +45,9 @@ struct WebPageData {
const QString &siteName, const QString &siteName,
const QString &title, const QString &title,
const TextWithEntities &description, const TextWithEntities &description,
DocumentData *document,
PhotoData *photo, PhotoData *photo,
DocumentData *document,
WebPageCollage &&collage,
int duration, int duration,
const QString &author, const QString &author,
int pendingTill) int pendingTill)
@ -51,6 +62,7 @@ struct WebPageData {
, author(author) , author(author)
, photo(photo) , photo(photo)
, document(document) , document(document)
, collage(std::move(collage))
, pendingTill(pendingTill) { , pendingTill(pendingTill) {
} }
@ -63,6 +75,7 @@ struct WebPageData {
const TextWithEntities &newDescription, const TextWithEntities &newDescription,
PhotoData *newPhoto, PhotoData *newPhoto,
DocumentData *newDocument, DocumentData *newDocument,
WebPageCollage &&newCollage,
int newDuration, int newDuration,
const QString &newAuthor, const QString &newAuthor,
int newPendingTill); int newPendingTill);
@ -78,6 +91,7 @@ struct WebPageData {
QString author; QString author;
PhotoData *photo = nullptr; PhotoData *photo = nullptr;
DocumentData *document = nullptr; DocumentData *document = nullptr;
WebPageCollage collage;
int pendingTill = 0; int pendingTill = 0;
int version = 0; int version = 0;

View File

@ -27,12 +27,29 @@ namespace {
using TextState = HistoryView::TextState; using TextState = HistoryView::TextState;
using PointState = HistoryView::PointState; using PointState = HistoryView::PointState;
constexpr auto kMaxDisplayedGroupSize = 10;
} // namespace } // namespace
HistoryGroupedMedia::Part::Part(not_null<HistoryItem*> item) HistoryGroupedMedia::Part::Part(
: item(item) { not_null<HistoryView::Element*> parent,
not_null<Data::Media*> media)
: item(media->parent())
, content(media->createView(parent, item)) {
Assert(media->canBeGrouped());
}
HistoryGroupedMedia::HistoryGroupedMedia(
not_null<Element*> parent,
const std::vector<std::unique_ptr<Data::Media>> &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<Data::Media> &v) {
return not_null<Data::Media*>(v.get());
}) | ranges::view::take(kMaxSize);
const auto result = applyGroup(truncated);
Ensures(result);
} }
HistoryGroupedMedia::HistoryGroupedMedia( HistoryGroupedMedia::HistoryGroupedMedia(
@ -40,11 +57,12 @@ HistoryGroupedMedia::HistoryGroupedMedia(
const std::vector<not_null<HistoryItem*>> &items) const std::vector<not_null<HistoryItem*>> &items)
: HistoryMedia(parent) : HistoryMedia(parent)
, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) { , _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) {
const auto result = (items.size() <= kMaxDisplayedGroupSize) const auto medias = ranges::view::all(
? applyGroup(items) items
: applyGroup(std::vector<not_null<HistoryItem*>>( ) | ranges::view::transform([](not_null<HistoryItem*> item) {
begin(items), return item->media();
begin(items) + kMaxDisplayedGroupSize)); }) | ranges::view::take(kMaxSize);
const auto result = applyGroup(medias);
Ensures(result); Ensures(result);
} }
@ -312,38 +330,35 @@ void HistoryGroupedMedia::clickHandlerPressedChanged(
} }
} }
bool HistoryGroupedMedia::applyGroup( template <typename DataMediaRange>
const std::vector<not_null<HistoryItem*>> &items) { bool HistoryGroupedMedia::applyGroup(const DataMediaRange &medias) {
Expects(items.size() <= kMaxDisplayedGroupSize); if (validateGroupParts(medias)) {
if (items.empty()) {
return false;
}
if (validateGroupParts(items)) {
return true; return true;
} }
for (const auto item : items) { for (const auto media : medias) {
const auto media = item->media(); _parts.push_back(Part(_parent, media));
Assert(media != nullptr && media->canBeGrouped()); }
if (_parts.empty()) {
return false;
}
_parts.push_back(Part(item)); Ensures(_parts.size() <= kMaxSize);
_parts.back().content = media->createView(_parent, item);
};
return true; return true;
} }
template <typename DataMediaRange>
bool HistoryGroupedMedia::validateGroupParts( bool HistoryGroupedMedia::validateGroupParts(
const std::vector<not_null<HistoryItem*>> &items) const { const DataMediaRange &medias) const {
if (_parts.size() != items.size()) { auto i = 0;
return false; const auto count = _parts.size();
} for (const auto media : medias) {
for (auto i = 0, count = int(items.size()); i != count; ++i) { if (i >= count || _parts[i].item != media->parent()) {
if (_parts[i].item != items[i]) {
return false; return false;
} }
++i;
} }
return true; return (i == count);
} }
not_null<HistoryMedia*> HistoryGroupedMedia::main() const { not_null<HistoryMedia*> HistoryGroupedMedia::main() const {

View File

@ -11,8 +11,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_document.h" #include "data/data_document.h"
#include "data/data_photo.h" #include "data/data_photo.h"
namespace Data {
class Media;
} // namespace Data
class HistoryGroupedMedia : public HistoryMedia { class HistoryGroupedMedia : public HistoryMedia {
public: public:
static constexpr auto kMaxSize = 10;
HistoryGroupedMedia(
not_null<Element*> parent,
const std::vector<std::unique_ptr<Data::Media>> &medias);
HistoryGroupedMedia( HistoryGroupedMedia(
not_null<Element*> parent, not_null<Element*> parent,
const std::vector<not_null<HistoryItem*>> &items); const std::vector<not_null<HistoryItem*>> &items);
@ -83,7 +92,9 @@ public:
private: private:
struct Part { struct Part {
Part(not_null<HistoryItem*> item); Part(
not_null<HistoryView::Element*> parent,
not_null<Data::Media*> media);
not_null<HistoryItem*> item; not_null<HistoryItem*> item;
std::unique_ptr<HistoryMedia> content; std::unique_ptr<HistoryMedia> content;
@ -96,15 +107,18 @@ private:
}; };
bool applyGroup(const std::vector<not_null<HistoryItem*>> &items); template <typename DataMediaRange>
bool applyGroup(const DataMediaRange &medias);
template <typename DataMediaRange>
bool validateGroupParts(const DataMediaRange &medias) const;
QSize countOptimalSize() override; QSize countOptimalSize() override;
QSize countCurrentSize(int newWidth) override; QSize countCurrentSize(int newWidth) override;
bool needInfoDisplay() const; bool needInfoDisplay() const;
bool computeNeedBubble() const; bool computeNeedBubble() const;
not_null<HistoryMedia*> main() const; not_null<HistoryMedia*> main() const;
bool validateGroupParts(
const std::vector<not_null<HistoryItem*>> &items) const;
TextState getPartState( TextState getPartState(
QPoint point, QPoint point,
StateRequest request) const; StateRequest request) const;

View File

@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_item_components.h" #include "history/history_item_components.h"
#include "history/history_location_manager.h" #include "history/history_location_manager.h"
#include "history/history_message.h" #include "history/history_message.h"
#include "history/history_media_grouped.h"
#include "history/view/history_view_element.h" #include "history/view/history_view_element.h"
#include "history/view/history_view_cursor_state.h" #include "history/view/history_view_cursor_state.h"
#include "window/main_window.h" #include "window/main_window.h"
@ -71,8 +72,11 @@ int gifMaxStatusWidth(DocumentData *document) {
std::unique_ptr<HistoryMedia> CreateAttach( std::unique_ptr<HistoryMedia> CreateAttach(
not_null<HistoryView::Element*> parent, not_null<HistoryView::Element*> parent,
DocumentData *document, DocumentData *document,
PhotoData *photo) { PhotoData *photo,
if (document) { const std::vector<std::unique_ptr<Data::Media>> &collage = {}) {
if (!collage.empty()) {
return std::make_unique<HistoryGroupedMedia>(parent, collage);
} else if (document) {
if (document->sticker()) { if (document->sticker()) {
return std::make_unique<HistorySticker>(parent, document); return std::make_unique<HistorySticker>(parent, document);
} else if (document->isAnimation()) { } else if (document->isAnimation()) {
@ -97,6 +101,30 @@ std::unique_ptr<HistoryMedia> CreateAttach(
return nullptr; return nullptr;
} }
std::vector<std::unique_ptr<Data::Media>> PrepareCollageMedia(
not_null<HistoryItem*> parent,
const WebPageCollage &data) {
auto result = std::vector<std::unique_ptr<Data::Media>>();
result.reserve(data.items.size());
for (const auto item : data.items) {
if (const auto document = base::get_if<DocumentData*>(&item)) {
result.push_back(std::make_unique<Data::MediaFile>(
parent,
*document));
} else if (const auto photo = base::get_if<PhotoData*>(&item)) {
result.push_back(std::make_unique<Data::MediaPhoto>(
parent,
*photo));
} else {
return {};
}
if (!result.back()->canBeGrouped()) {
return {};
}
}
return result;
}
} // namespace } // namespace
QString FillAmountAndCurrency(uint64 amount, const QString &currency) { QString FillAmountAndCurrency(uint64 amount, const QString &currency) {
@ -3354,6 +3382,7 @@ QSize HistoryWebPage::countOptimalSize() {
_dataVersion = _data->version; _dataVersion = _data->version;
_openl = nullptr; _openl = nullptr;
_attach = nullptr; _attach = nullptr;
_collage = PrepareCollageMedia(_parent->data(), _data->collage);
_title = Text(st::msgMinWidth - st::webPageLeft); _title = Text(st::msgMinWidth - st::webPageLeft);
_description = Text(st::msgMinWidth - st::webPageLeft); _description = Text(st::msgMinWidth - st::webPageLeft);
_siteNameWidth = 0; _siteNameWidth = 0;
@ -3366,7 +3395,9 @@ QSize HistoryWebPage::countOptimalSize() {
// init layout // init layout
auto title = TextUtilities::SingleLine(_data->title.isEmpty() ? _data->author : _data->title); 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) { if (_data->type == WebPageProfile) {
_asArticle = true; _asArticle = true;
} else if (_data->siteName == qstr("Twitter") || _data->siteName == qstr("Facebook")) { } else if (_data->siteName == qstr("Twitter") || _data->siteName == qstr("Facebook")) {
@ -3383,7 +3414,11 @@ QSize HistoryWebPage::countOptimalSize() {
// init attach // init attach
if (!_attach && !_asArticle) { if (!_attach && !_asArticle) {
_attach = CreateAttach(_parent, _data->document, _data->photo); _attach = CreateAttach(
_parent,
_data->document,
_data->photo,
_collage);
} }
auto textFloatsAroundInfo = !_asArticle && !_attach && isBubbleBottom(); auto textFloatsAroundInfo = !_asArticle && !_attach && isBubbleBottom();
@ -3804,7 +3839,7 @@ TextState HistoryWebPage::textState(QPoint point, StateRequest request) const {
if (rtl()) attachLeft = width() - attachLeft - _attach->width(); if (rtl()) attachLeft = width() - attachLeft - _attach->width();
result = _attach->textState(point - QPoint(attachLeft, attachTop), request); 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) { if (_data->type == WebPageProfile || _data->type == WebPageVideo) {
result.link = _openl; result.link = _openl;
} else if (_data->type == WebPagePhoto || _data->siteName == qstr("Twitter") || _data->siteName == qstr("Facebook")) { } else if (_data->type == WebPagePhoto || _data->siteName == qstr("Twitter") || _data->siteName == qstr("Facebook")) {

View File

@ -20,11 +20,13 @@ struct HistoryDocumentNamed;
struct HistoryMessageVia; struct HistoryMessageVia;
struct HistoryMessageReply; struct HistoryMessageReply;
struct HistoryMessageForwarded; struct HistoryMessageForwarded;
struct WebPageCollage;
namespace Data { namespace Data {
enum class CallFinishReason : char; enum class CallFinishReason : char;
struct Invoice; struct Invoice;
struct Call; struct Call;
class Media;
} // namespace Data } // namespace Data
namespace Media { namespace Media {
@ -730,6 +732,7 @@ private:
bool isLogEntryOriginal() const; bool isLogEntryOriginal() const;
not_null<WebPageData*> _data; not_null<WebPageData*> _data;
std::vector<std::unique_ptr<Data::Media>> _collage;
ClickHandlerPtr _openl; ClickHandlerPtr _openl;
std::unique_ptr<HistoryMedia> _attach; std::unique_ptr<HistoryMedia> _attach;