From bf31722931b6cae2ac56bc52de53679c98ef19fd Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Wed, 24 Oct 2018 17:09:31 +0400
Subject: [PATCH] Show collage/slideshow as an album in MediaView.

---
 .../media/view/media_view_group_thumbs.cpp    |  60 +++++++-
 .../media/view/media_view_group_thumbs.h      |  30 +++-
 Telegram/SourceFiles/mediaview.cpp            | 145 ++++++++++++++++--
 Telegram/SourceFiles/mediaview.h              |  10 ++
 4 files changed, 231 insertions(+), 14 deletions(-)

diff --git a/Telegram/SourceFiles/media/view/media_view_group_thumbs.cpp b/Telegram/SourceFiles/media/view/media_view_group_thumbs.cpp
index 0c5dae0ab..bccb729f4 100644
--- a/Telegram/SourceFiles/media/view/media_view_group_thumbs.cpp
+++ b/Telegram/SourceFiles/media/view/media_view_group_thumbs.cpp
@@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_document.h"
 #include "data/data_media_types.h"
 #include "data/data_session.h"
+#include "data/data_web_page.h"
 #include "history/history.h"
 #include "history/history_media.h"
 #include "ui/image/image.h"
@@ -38,11 +39,17 @@ Data::FileOrigin ComputeFileOrigin(const Key &key, const Context &context) {
 			return peerIsUser(peerId)
 				? Data::FileOriginUserPhoto(peerToUser(peerId), photoId)
 				: Data::FileOrigin(Data::FileOriginPeerPhoto(peerId));
-		}, [&](auto&&) {
+		}, [](auto&&) {
 			return Data::FileOrigin();
 		});
 	}, [](FullMsgId itemId) {
 		return Data::FileOrigin(itemId);
+	}, [&](GroupThumbs::CollageKey) {
+		return context.match([](const GroupThumbs::CollageSlice &slice) {
+			return Data::FileOrigin(slice.context);
+		}, [](auto&&) {
+			return Data::FileOrigin();
+		});
 	});
 }
 
@@ -72,6 +79,10 @@ Context ComputeContext(const UserPhotosSlice &slice, int index) {
 	return peerFromUser(slice.key().userId);
 }
 
+Context ComputeContext(const GroupThumbs::CollageSlice &slice, int index) {
+	return slice.context;
+}
+
 Key ComputeKey(const SharedMediaWithLastSlice &slice, int index) {
 	Expects(index >= 0 && index < slice.size());
 
@@ -88,6 +99,10 @@ Key ComputeKey(const UserPhotosSlice &slice, int index) {
 	return slice[index];
 }
 
+Key ComputeKey(const GroupThumbs::CollageSlice &slice, int index) {
+	return GroupThumbs::CollageKey{ index };
+}
+
 int ComputeThumbsLimit(int availableWidth) {
 	const auto singleWidth = st::mediaviewGroupWidth
 		+ 2 * st::mediaviewGroupSkip;
@@ -351,6 +366,10 @@ ClickHandlerPtr GroupThumbs::Thumb::getState(QPoint point) const {
 		: nullptr;
 }
 
+int GroupThumbs::CollageSlice::size() const {
+	return data->items.size();
+}
+
 GroupThumbs::GroupThumbs(Context context)
 : _context(context) {
 }
@@ -510,10 +529,41 @@ auto GroupThumbs::createThumb(Key key)
 			}
 		}
 		return createThumb(key, ImagePtr());
+	} else if (const auto collageKey = base::get_if<CollageKey>(&key)) {
+		if (const auto itemId = base::get_if<FullMsgId>(&_context)) {
+			if (const auto item = App::histItemById(*itemId)) {
+				if (const auto media = item->media()) {
+					if (const auto page = media->webpage()) {
+						return createThumb(
+							key,
+							page->collage,
+							collageKey->index);
+					}
+				}
+			}
+		}
+		return createThumb(key, ImagePtr());
 	}
 	Unexpected("Value of Key in GroupThumbs::createThumb()");
 }
 
+auto GroupThumbs::createThumb(
+	Key key,
+	const WebPageCollage &collage,
+	int index)
+-> std::unique_ptr<Thumb> {
+	if (index < 0 || index >= collage.items.size()) {
+		return createThumb(key, ImagePtr());
+	}
+	const auto &item = collage.items[index];
+	if (const auto photo = base::get_if<PhotoData*>(&item)) {
+		return createThumb(key, (*photo)->thumb);
+	} else if (const auto document = base::get_if<DocumentData*>(&item)) {
+		return createThumb(key, (*document)->thumb);
+	}
+	return createThumb(key, ImagePtr());
+}
+
 auto GroupThumbs::createThumb(Key key, ImagePtr image)
 -> std::unique_ptr<Thumb> {
 	const auto weak = base::make_weak(this);
@@ -556,6 +606,14 @@ void GroupThumbs::Refresh(
 	RefreshFromSlice(instance, slice, index, availableWidth);
 }
 
+void GroupThumbs::Refresh(
+		std::unique_ptr<GroupThumbs> &instance,
+		const CollageSlice &slice,
+		int index,
+		int availableWidth) {
+	RefreshFromSlice(instance, slice, index, availableWidth);
+}
+
 void GroupThumbs::clear() {
 	if (_items.empty()) {
 		return;
diff --git a/Telegram/SourceFiles/media/view/media_view_group_thumbs.h b/Telegram/SourceFiles/media/view/media_view_group_thumbs.h
index 4d27ee0fb..74851202b 100644
--- a/Telegram/SourceFiles/media/view/media_view_group_thumbs.h
+++ b/Telegram/SourceFiles/media/view/media_view_group_thumbs.h
@@ -12,13 +12,27 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 class SharedMediaWithLastSlice;
 class UserPhotosSlice;
+struct WebPageCollage;
 
 namespace Media {
 namespace View {
 
 class GroupThumbs : public base::has_weak_ptr {
 public:
-	using Key = base::variant<PhotoId, FullMsgId>;
+	struct CollageKey {
+		int index = 0;
+
+		inline bool operator<(const CollageKey &other) const {
+			return index < other.index;
+		}
+	};
+	struct CollageSlice {
+		FullMsgId context;
+		not_null<const WebPageCollage*> data;
+
+		int size() const;
+	};
+	using Key = base::variant<PhotoId, FullMsgId, CollageKey>;
 
 	static void Refresh(
 		std::unique_ptr<GroupThumbs> &instance,
@@ -30,6 +44,11 @@ public:
 		const UserPhotosSlice &slice,
 		int index,
 		int availableWidth);
+	static void Refresh(
+		std::unique_ptr<GroupThumbs> &instance,
+		const CollageSlice &slice,
+		int index,
+		int availableWidth);
 	void clear();
 
 	void resizeToWidth(int newWidth);
@@ -53,7 +72,10 @@ public:
 		return _lifetime;
 	}
 
-	using Context = base::optional_variant<PeerId, MessageGroupId>;
+	using Context = base::optional_variant<
+		PeerId,
+		MessageGroupId,
+		FullMsgId>;
 
 	GroupThumbs(Context context);
 	~GroupThumbs();
@@ -73,6 +95,10 @@ private:
 	void markCacheStale();
 	not_null<Thumb*> validateCacheEntry(Key key);
 	std::unique_ptr<Thumb> createThumb(Key key);
+	std::unique_ptr<Thumb> createThumb(
+		Key key,
+		const WebPageCollage &collage,
+		int index);
 	std::unique_ptr<Thumb> createThumb(Key key, ImagePtr image);
 
 	void update();
diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp
index 8c53b898f..1b9ba4c75 100644
--- a/Telegram/SourceFiles/mediaview.cpp
+++ b/Telegram/SourceFiles/mediaview.cpp
@@ -57,21 +57,28 @@ Images::Options VideoThumbOptions(not_null<DocumentData*> document) {
 } // namespace
 
 struct MediaView::SharedMedia {
-	SharedMedia(SharedMediaWithLastSlice::Key key) : key(key) {
+	SharedMedia(SharedMediaKey key) : key(key) {
 	}
 
-	SharedMediaWithLastSlice::Key key;
+	SharedMediaKey key;
 	rpl::lifetime lifetime;
 };
 
 struct MediaView::UserPhotos {
-	UserPhotos(UserPhotosSlice::Key key) : key(key) {
+	UserPhotos(UserPhotosKey key) : key(key) {
 	}
 
-	UserPhotosSlice::Key key;
+	UserPhotosKey key;
 	rpl::lifetime lifetime;
 };
 
+struct MediaView::Collage {
+	Collage(CollageKey key) : key(key) {
+	}
+
+	CollageKey key;
+};
+
 MediaView::MediaView()
 : TWidget(nullptr)
 , _transparentBrush(style::transparentPlaceholderBrush())
@@ -121,6 +128,7 @@ MediaView::MediaView()
 		} else {
 			_sharedMedia = nullptr;
 			_userPhotos = nullptr;
+			_collage = nullptr;
 		}
 	};
 	subscribe(Messenger::Instance().authSessionChanged(), [handleAuthSessionChange] {
@@ -299,6 +307,9 @@ void MediaView::refreshNavVisibility() {
 	} else if (_userPhotosData) {
 		_leftNavVisible = _index && (*_index > 0);
 		_rightNavVisible = _index && (*_index + 1 < _userPhotosData->size());
+	} else if (_collageData) {
+		_leftNavVisible = _index && (*_index > 0);
+		_rightNavVisible = _index && (*_index + 1 < _collageData->items.size());
 	} else {
 		_leftNavVisible = false;
 		_rightNavVisible = false;
@@ -1092,7 +1103,12 @@ void MediaView::onCopy() {
 
 std::optional<MediaView::SharedMediaType> MediaView::sharedMediaType() const {
 	using Type = SharedMediaType;
-	if (auto item = App::histItemById(_msgid)) {
+	if (const auto item = App::histItemById(_msgid)) {
+		if (const auto media = item->media()) {
+			if (media->webpage()) {
+				return std::nullopt;
+			}
+		}
 		if (_photo) {
 			if (item->toHistoryMessage()) {
 				return Type::PhotoVideo;
@@ -1225,17 +1241,17 @@ std::optional<MediaView::UserPhotosKey> MediaView::userPhotosKey() const {
 }
 
 bool MediaView::validUserPhotos() const {
-	if (auto key = userPhotosKey()) {
+	if (const auto key = userPhotosKey()) {
 		if (!_userPhotos) {
 			return false;
 		}
-		auto countDistanceInData = [](const auto &a, const auto &b) {
+		const auto countDistanceInData = [](const auto &a, const auto &b) {
 			return [&](const UserPhotosSlice &data) {
 				return data.distance(a, b);
 			};
 		};
 
-		auto distance = (key == _userPhotos->key) ? 0 :
+		const auto distance = (key == _userPhotos->key) ? 0 :
 			_userPhotosData
 			| countDistanceInData(*key, _userPhotos->key)
 			| func::abs;
@@ -1247,7 +1263,7 @@ bool MediaView::validUserPhotos() const {
 }
 
 void MediaView::validateUserPhotos() {
-	if (auto key = userPhotosKey()) {
+	if (const auto key = userPhotosKey()) {
 		_userPhotos = std::make_unique<UserPhotos>(*key);
 		UserPhotosReversedViewer(
 			*key,
@@ -1274,6 +1290,66 @@ void MediaView::handleUserPhotosUpdate(UserPhotosSlice &&update) {
 	preloadData(0);
 }
 
+std::optional<MediaView::CollageKey> MediaView::collageKey() const {
+	if (const auto item = App::histItemById(_msgid)) {
+		if (const auto media = item->media()) {
+			if (const auto page = media->webpage()) {
+				for (const auto item : page->collage.items) {
+					if (item == _photo || item == _doc) {
+						return item;
+					}
+				}
+			}
+		}
+	}
+	return std::nullopt;
+}
+
+bool MediaView::validCollage() const {
+	if (const auto key = collageKey()) {
+		if (!_collage) {
+			return false;
+		}
+		const auto countDistanceInData = [](const auto &a, const auto &b) {
+			return [&](const WebPageCollage &data) {
+				const auto i = ranges::find(data.items, a);
+				const auto j = ranges::find(data.items, b);
+				return (i != end(data.items) && j != end(data.items))
+					? std::make_optional(i - j)
+					: std::nullopt;
+			};
+		};
+
+		if (key == _collage->key) {
+			return true;
+		} else if (_collageData) {
+			const auto &items = _collageData->items;
+			if (ranges::find(items, *key) != end(items)
+				&& ranges::find(items, _collage->key) != end(items)) {
+				return true;
+			}
+		}
+	}
+	return (_collage == nullptr);
+}
+
+void MediaView::validateCollage() {
+	if (const auto key = collageKey()) {
+		_collage = std::make_unique<Collage>(*key);
+		_collageData = WebPageCollage();
+		if (const auto item = App::histItemById(_msgid)) {
+			if (const auto media = item->media()) {
+				if (const auto page = media->webpage()) {
+					_collageData = page->collage;
+				}
+			}
+		}
+	} else {
+		_collage = nullptr;
+		_collageData = std::nullopt;
+	}
+}
+
 void MediaView::refreshMediaViewer() {
 	if (!validSharedMedia()) {
 		validateSharedMedia();
@@ -1281,6 +1357,9 @@ void MediaView::refreshMediaViewer() {
 	if (!validUserPhotos()) {
 		validateUserPhotos();
 	}
+	if (!validCollage()) {
+		validateCollage();
+	}
 	findCurrent();
 	updateControls();
 	preloadData(0);
@@ -1290,6 +1369,10 @@ void MediaView::refreshCaption(HistoryItem *item) {
 	_caption = Text();
 	if (!item) {
 		return;
+	} else if (const auto media = item->media()) {
+		if (media->webpage()) {
+			return;
+		}
 	}
 	const auto caption = item->originalText();
 	if (caption.text.isEmpty()) {
@@ -1322,6 +1405,12 @@ void MediaView::refreshGroupThumbs() {
 			*_userPhotosData,
 			*_index,
 			_groupThumbsAvailableWidth);
+	} else if (_index && _collageData) {
+		Media::View::GroupThumbs::Refresh(
+			_groupThumbs,
+			{ _msgid, &*_collageData },
+			*_index,
+			_groupThumbsAvailableWidth);
 	} else if (_groupThumbs) {
 		_groupThumbs->clear();
 		_groupThumbs->resizeToWidth(_groupThumbsAvailableWidth);
@@ -1347,11 +1436,16 @@ void MediaView::initGroupThumbs() {
 
 	_groupThumbs->activateRequests(
 	) | rpl::start_with_next([this](Media::View::GroupThumbs::Key key) {
+		using CollageKey = Media::View::GroupThumbs::CollageKey;
 		if (const auto photoId = base::get_if<PhotoId>(&key)) {
 			const auto photo = Auth().data().photo(*photoId);
 			moveToEntity({ photo, nullptr });
 		} else if (const auto itemId = base::get_if<FullMsgId>(&key)) {
 			moveToEntity(entityForItemId(*itemId));
+		} else if (const auto collageKey = base::get_if<CollageKey>(&key)) {
+			if (_collageData) {
+				moveToEntity(entityForCollage(collageKey->index));
+			}
 		}
 	}, _groupThumbs->lifetime());
 
@@ -2464,7 +2558,7 @@ void MediaView::setZoomLevel(int newZoom) {
 }
 
 MediaView::Entity MediaView::entityForUserPhotos(int index) const {
-	Expects(!!_userPhotosData);
+	Expects(_userPhotosData.has_value());
 
 	if (index < 0 || index >= _userPhotosData->size()) {
 		return { std::nullopt, nullptr };
@@ -2476,7 +2570,7 @@ MediaView::Entity MediaView::entityForUserPhotos(int index) const {
 }
 
 MediaView::Entity MediaView::entityForSharedMedia(int index) const {
-	Expects(!!_sharedMediaData);
+	Expects(_sharedMediaData.has_value());
 
 	if (index < 0 || index >= _sharedMediaData->size()) {
 		return { std::nullopt, nullptr };
@@ -2491,6 +2585,22 @@ MediaView::Entity MediaView::entityForSharedMedia(int index) const {
 	return { std::nullopt, nullptr };
 }
 
+MediaView::Entity MediaView::entityForCollage(int index) const {
+	Expects(_collageData.has_value());
+
+	const auto item = App::histItemById(_msgid);
+	const auto &items = _collageData->items;
+	if (!item || index < 0 || index >= items.size()) {
+		return { std::nullopt, nullptr };
+	}
+	if (const auto document = base::get_if<DocumentData*>(&items[index])) {
+		return { *document, item };
+	} else if (const auto photo = base::get_if<PhotoData*>(&items[index])) {
+		return { *photo, item };
+	}
+	return { std::nullopt, nullptr };
+}
+
 MediaView::Entity MediaView::entityForItemId(const FullMsgId &itemId) const {
 	if (const auto item = App::histItemById(itemId)) {
 		if (const auto media = item->media()) {
@@ -2510,6 +2620,8 @@ MediaView::Entity MediaView::entityByIndex(int index) const {
 		return entityForSharedMedia(index);
 	} else if (_userPhotosData) {
 		return entityForUserPhotos(index);
+	} else if (_collageData) {
+		return entityForCollage(index);
 	}
 	return { std::nullopt, nullptr };
 }
@@ -3008,6 +3120,8 @@ void MediaView::setVisible(bool visible) {
 		_sharedMediaDataKey = std::nullopt;
 		_userPhotos = nullptr;
 		_userPhotosData = std::nullopt;
+		_collage = nullptr;
+		_collageData = std::nullopt;
 		if (_menu) _menu->hideMenu(true);
 		_controlsHideTimer.stop();
 		_controlsState = ControlsShown;
@@ -3075,6 +3189,15 @@ void MediaView::findCurrent() {
 			? (_index | func::add(*_userPhotosData->skippedBefore()))
 			: std::nullopt;
 		_fullCount = _userPhotosData->fullCount();
+	} else if (_collageData) {
+		const auto item = _photo ? WebPageCollage::Item(_photo) : _doc;
+		const auto &items = _collageData->items;
+		const auto i = ranges::find(items, item);
+		_index = (i != end(items))
+			? std::make_optional(int(i - begin(items)))
+			: std::nullopt;
+		_fullIndex = _index;
+		_fullCount = items.size();
 	} else {
 		_index = _fullIndex = _fullCount = std::nullopt;
 	}
diff --git a/Telegram/SourceFiles/mediaview.h b/Telegram/SourceFiles/mediaview.h
index a03e1b6ce..66940d955 100644
--- a/Telegram/SourceFiles/mediaview.h
+++ b/Telegram/SourceFiles/mediaview.h
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/effects/radial_animation.h"
 #include "data/data_shared_media.h"
 #include "data/data_user_photos.h"
+#include "data/data_web_page.h"
 
 namespace Media {
 namespace Player {
@@ -147,6 +148,7 @@ private:
 	};
 	Entity entityForUserPhotos(int index) const;
 	Entity entityForSharedMedia(int index) const;
+	Entity entityForCollage(int index) const;
 	Entity entityByIndex(int index) const;
 	Entity entityForItemId(const FullMsgId &itemId) const;
 	bool moveToEntity(const Entity &entity, int preloadDelta = 0);
@@ -175,6 +177,12 @@ private:
 	void validateUserPhotos();
 	void handleUserPhotosUpdate(UserPhotosSlice &&update);
 
+	struct Collage;
+	using CollageKey = WebPageCollage::Item;
+	std::optional<CollageKey> collageKey() const;
+	bool validCollage() const;
+	void validateCollage();
+
 	Data::FileOrigin fileOrigin() const;
 
 	void refreshCaption(HistoryItem *item);
@@ -252,6 +260,8 @@ private:
 	std::optional<SharedMediaWithLastSlice::Key> _sharedMediaDataKey;
 	std::unique_ptr<UserPhotos> _userPhotos;
 	std::optional<UserPhotosSlice> _userPhotosData;
+	std::unique_ptr<Collage> _collage;
+	std::optional<WebPageCollage> _collageData;
 
 	QRect _closeNav, _closeNavIcon;
 	QRect _leftNav, _leftNavIcon, _rightNav, _rightNavIcon;