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 {
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();

View File

@ -1267,6 +1267,7 @@ not_null<WebPageData*> Session::webpage(const MTPDwebPagePending &data) {
TextWithEntities(),
nullptr,
nullptr,
WebPageCollage(),
0,
QString(),
data.vdate.v
@ -1289,6 +1290,7 @@ not_null<WebPageData*> Session::webpage(
content,
nullptr,
nullptr,
WebPageCollage(),
0,
QString(),
TimeId(0));
@ -1304,6 +1306,7 @@ not_null<WebPageData*> Session::webpage(
const TextWithEntities &description,
PhotoData *photo,
DocumentData *document,
WebPageCollage &&collage,
int duration,
const QString &author,
TimeId pendingTill) {
@ -1318,6 +1321,7 @@ not_null<WebPageData*> 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);

View File

@ -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);

View File

@ -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<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
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;

View File

@ -24,6 +24,16 @@ inline WebPageType toWebPageType(const QString &type) {
return WebPageArticle;
}
struct WebPageCollage {
using Item = base::variant<PhotoData*, DocumentData*>;
WebPageCollage() = default;
explicit WebPageCollage(const MTPDwebPage &data);
std::vector<Item> 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;

View File

@ -27,12 +27,29 @@ namespace {
using TextState = HistoryView::TextState;
using PointState = HistoryView::PointState;
constexpr auto kMaxDisplayedGroupSize = 10;
} // namespace
HistoryGroupedMedia::Part::Part(not_null<HistoryItem*> item)
: item(item) {
HistoryGroupedMedia::Part::Part(
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(
@ -40,11 +57,12 @@ HistoryGroupedMedia::HistoryGroupedMedia(
const std::vector<not_null<HistoryItem*>> &items)
: HistoryMedia(parent)
, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) {
const auto result = (items.size() <= kMaxDisplayedGroupSize)
? applyGroup(items)
: applyGroup(std::vector<not_null<HistoryItem*>>(
begin(items),
begin(items) + kMaxDisplayedGroupSize));
const auto medias = ranges::view::all(
items
) | ranges::view::transform([](not_null<HistoryItem*> 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<not_null<HistoryItem*>> &items) {
Expects(items.size() <= kMaxDisplayedGroupSize);
if (items.empty()) {
return false;
}
if (validateGroupParts(items)) {
template <typename DataMediaRange>
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 <typename DataMediaRange>
bool HistoryGroupedMedia::validateGroupParts(
const std::vector<not_null<HistoryItem*>> &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<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_photo.h"
namespace Data {
class Media;
} // namespace Data
class HistoryGroupedMedia : public HistoryMedia {
public:
static constexpr auto kMaxSize = 10;
HistoryGroupedMedia(
not_null<Element*> parent,
const std::vector<std::unique_ptr<Data::Media>> &medias);
HistoryGroupedMedia(
not_null<Element*> parent,
const std::vector<not_null<HistoryItem*>> &items);
@ -83,7 +92,9 @@ public:
private:
struct Part {
Part(not_null<HistoryItem*> item);
Part(
not_null<HistoryView::Element*> parent,
not_null<Data::Media*> media);
not_null<HistoryItem*> item;
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 countCurrentSize(int newWidth) override;
bool needInfoDisplay() const;
bool computeNeedBubble() const;
not_null<HistoryMedia*> main() const;
bool validateGroupParts(
const std::vector<not_null<HistoryItem*>> &items) const;
TextState getPartState(
QPoint point,
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_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<HistoryMedia> CreateAttach(
not_null<HistoryView::Element*> parent,
DocumentData *document,
PhotoData *photo) {
if (document) {
PhotoData *photo,
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()) {
return std::make_unique<HistorySticker>(parent, document);
} else if (document->isAnimation()) {
@ -97,6 +101,30 @@ std::unique_ptr<HistoryMedia> CreateAttach(
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
QString FillAmountAndCurrency(uint64 amount, const QString &currency) {
@ -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")) {

View File

@ -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<WebPageData*> _data;
std::vector<std::unique_ptr<Data::Media>> _collage;
ClickHandlerPtr _openl;
std::unique_ptr<HistoryMedia> _attach;