From 9b9ea336bec2ca3490251515e48a73e6b65d7d86 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Tue, 17 Dec 2019 14:11:10 +0300
Subject: [PATCH] Use HistoryView::Gif for video files.

---
 .../SourceFiles/data/data_media_types.cpp     |   6 +-
 .../history/view/media/history_view_gif.cpp   | 275 +++++++++++++++++-
 .../history/view/media/history_view_gif.h     |  35 ++-
 .../view/media/history_view_media_common.cpp  |   9 +-
 4 files changed, 305 insertions(+), 20 deletions(-)

diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp
index 2937c8b47..d5df18e86 100644
--- a/Telegram/SourceFiles/data/data_media_types.cpp
+++ b/Telegram/SourceFiles/data/data_media_types.cpp
@@ -775,10 +775,8 @@ std::unique_ptr<HistoryView::Media> MediaFile::createView(
 		return std::make_unique<HistoryView::UnwrappedMedia>(
 			message,
 			std::make_unique<HistoryView::Sticker>(message, _document));
-	} else if (_document->isAnimation()) {
-		return std::make_unique<HistoryView::Gif>(message, _document);
-	} else if (_document->isVideoFile()) {
-		return std::make_unique<HistoryView::Video>(
+	} else if (_document->isAnimation() || _document->isVideoFile()) {
+		return std::make_unique<HistoryView::Gif>(
 			message,
 			realParent,
 			_document);
diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp
index 98e6cf08d..f966ca66a 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp
@@ -23,9 +23,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "history/history.h"
 #include "history/view/history_view_element.h"
 #include "history/view/history_view_cursor_state.h"
+#include "history/view/media/history_view_media_common.h"
 #include "window/window_session_controller.h"
 #include "core/application.h" // Application::showDocument.
 #include "ui/image/image.h"
+#include "ui/grouped_layout.h"
 #include "data/data_session.h"
 #include "data/data_document.h"
 #include "data/data_file_origin.h"
@@ -47,23 +49,38 @@ int gifMaxStatusWidth(DocumentData *document) {
 
 Gif::Gif(
 	not_null<Element*> parent,
+	not_null<HistoryItem*> realParent,
 	not_null<DocumentData*> document)
-: File(parent, parent->data())
+: File(parent, realParent)
 , _data(document)
-, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) {
-	const auto item = parent->data();
-	setDocumentLinks(_data, item);
+, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right())
+, _downloadSize(formatSizeText(_data->size)) {
+	setDocumentLinks(_data, realParent);
 
 	setStatusSize(FileStatusSizeReady);
 
-	_caption = createCaption(item);
-	_data->loadThumbnail(item->fullId());
+	_caption = createCaption(realParent);
+	_data->loadThumbnail(realParent->fullId());
 }
 
 Gif::~Gif() {
 	setStreamed(nullptr);
 }
 
+QSize Gif::sizeForAspectRatio() const {
+	// We use size only for aspect ratio and we want to have it
+	// as close to the thumbnail as possible.
+	//if (!_data->dimensions.isEmpty()) {
+	//	return _data->dimensions;
+	//}
+	if (const auto thumb = _data->thumbnail()) {
+		if (!thumb->size().isEmpty()) {
+			return thumb->size();
+		}
+	}
+	return { 1, 1 };
+}
+
 QSize Gif::countOptimalSize() {
 	if (_parent->media() != this) {
 		_caption = Ui::Text::String();
@@ -196,6 +213,13 @@ QSize Gif::videoSize() const {
 	}
 }
 
+bool Gif::downloadInCorner() const {
+	return _data->isVideoFile()
+		&& !autoplayEnabled()
+		&& _data->canBeStreamed()
+		&& !_data->inappPlaybackFailed()
+		&& IsServerMsgId(_parent->data()->id);
+}
 
 bool Gif::autoplayEnabled() const {
 	return history()->session().settings().autoplayGifs();
@@ -209,6 +233,7 @@ void Gif::draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms
 	//auto loaded = _data->loaded();
 	auto displayLoading = (item->id < 0) || _data->displayLoading();
 	auto selected = (selection == FullSelection);
+	const auto cornerDownload = downloadInCorner();
 	const auto canBePlayed = _data->canBePlayed();
 	const auto activeRoundPlaying = activeRoundStreamed();
 	const auto autoplay = autoplayEnabled() && canBePlayed;
@@ -404,7 +429,7 @@ void Gif::draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms
 					width(),
 					fg,
 					st::msgFileRadialLine);
-			} else {
+			} else if (!cornerDownload) {
 				_animation->radial.draw(
 					p,
 					rinner,
@@ -433,6 +458,8 @@ void Gif::draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms
 		(selected ? st::historyVideoMessageMuteSelected : st::historyVideoMessageMute).paintInCenter(p, muteRect);
 	}
 
+	drawCornerStatus(p, selected);
+
 	if (!inWebPage && isRound) {
 		auto mediaUnread = item->hasUnreadMediaFlag();
 		auto statusW = st::normalFont->width(_statusText) + 2 * st::msgDateImgPadding.x();
@@ -534,6 +561,59 @@ void Gif::draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms
 	}
 }
 
+void Gif::drawCornerStatus(Painter &p, bool selected) const {
+	const auto padding = st::msgDateImgPadding;
+	const auto radial = _animation && _animation->radial.animating();
+	const auto cornerDownload = downloadInCorner() && !_data->loaded() && !_data->loadedInMediaCache();
+	const auto addWidth = cornerDownload ? (st::historyVideoDownloadSize + 2 * padding.y()) : 0;
+	const auto downloadWidth = cornerDownload ? st::normalFont->width(_downloadSize) : 0;
+	const auto statusW = std::max(downloadWidth, st::normalFont->width(_statusText)) + 2 * padding.x() + addWidth;
+	const auto statusH = cornerDownload ? (st::historyVideoDownloadSize + 2 * padding.y()) : (st::normalFont->height + 2 * padding.y());
+	const auto statusX = st::msgDateImgDelta + padding.x();
+	const auto statusY = st::msgDateImgDelta + padding.y();
+	const auto around = style::rtlrect(statusX - padding.x(), statusY - padding.y(), statusW, statusH, width());
+	const auto statusTextTop = statusY + (cornerDownload ? (((statusH - 2 * st::normalFont->height) / 3)  - padding.y()) : 0);
+	App::roundRect(p, around, selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners);
+	p.setFont(st::normalFont);
+	p.setPen(st::msgDateImgFg);
+	p.drawTextLeft(statusX + addWidth, statusTextTop, width(), _statusText, statusW - 2 * padding.x());
+	if (cornerDownload) {
+		const auto downloadTextTop = statusY + st::normalFont->height + (2 * (statusH - 2 * st::normalFont->height) / 3)  - padding.y();
+		p.drawTextLeft(statusX + addWidth, downloadTextTop, width(), _downloadSize, statusW - 2 * padding.x());
+		const auto inner = QRect(statusX + padding.y() - padding.x(), statusY, st::historyVideoDownloadSize, st::historyVideoDownloadSize);
+		const auto icon = [&]() -> const style::icon * {
+			if (_data->loading()) {
+				return &(selected ? st::historyVideoCancelSelected : st::historyVideoCancel);
+			}
+			return &(selected ? st::historyVideoDownloadSelected : st::historyVideoDownload);
+		}();
+		if (icon) {
+			icon->paintInCenter(p, inner);
+		}
+		if (radial) {
+			QRect rinner(inner.marginsRemoved(QMargins(st::historyVideoRadialLine, st::historyVideoRadialLine, st::historyVideoRadialLine, st::historyVideoRadialLine)));
+			_animation->radial.draw(p, rinner, st::historyVideoRadialLine, selected ? st::historyFileThumbRadialFgSelected : st::historyFileThumbRadialFg);
+		}
+	}
+}
+
+TextState Gif::cornerStatusTextState(
+		QPoint point,
+		StateRequest request) const {
+	auto result = TextState(_parent);
+	if (!downloadInCorner() || _data->loaded()) {
+		return result;
+	}
+	const auto padding = st::msgDateImgPadding;
+	const auto addWidth = st::historyVideoDownloadSize + 2 * padding.y() - padding.x();
+	const auto statusX = st::msgDateImgDelta + padding.x(), statusY = st::msgDateImgDelta + padding.y();
+	const auto inner = QRect(statusX + padding.y() - padding.x(), statusY, st::historyVideoDownloadSize, st::historyVideoDownloadSize);
+	if (inner.contains(point)) {
+		result.link = _data->loading() ? _cancell : _savel;
+	}
+	return result;
+}
+
 TextState Gif::textState(QPoint point, StateRequest request) const {
 	auto result = TextState(_parent);
 
@@ -632,6 +712,9 @@ TextState Gif::textState(QPoint point, StateRequest request) const {
 			}
 		}
 	}
+	if (const auto state = cornerStatusTextState(point, request); state.link) {
+		return state;
+	}
 	if (QRect(usex + paintx, painty, usew, painth).contains(point)) {
 		if (_data->uploading()) {
 			result.link = _cancell;
@@ -686,6 +769,128 @@ TextForMimeData Gif::selectedText(TextSelection selection) const {
 	return _caption.toTextForMimeData(selection);
 }
 
+QSize Gif::sizeForGrouping() const {
+	return sizeForAspectRatio();
+}
+
+void Gif::drawGrouped(
+		Painter &p,
+		const QRect &clip,
+		TextSelection selection,
+		crl::time ms,
+		const QRect &geometry,
+		RectParts corners,
+		not_null<uint64*> cacheKey,
+		not_null<QPixmap*> cache) const {
+	_data->automaticLoad(_realParent->fullId(), _parent->data());
+
+	validateGroupedCache(geometry, corners, cacheKey, cache);
+
+	const auto selected = (selection == FullSelection);
+	const auto loaded = _data->loaded();
+	const auto displayLoading = _data->displayLoading();
+	const auto bubble = _parent->hasBubble();
+
+	if (displayLoading) {
+		ensureAnimation();
+		if (!_animation->radial.animating()) {
+			_animation->radial.start(_data->progress());
+		}
+	}
+	const auto radial = isRadialAnimation();
+
+	if (!bubble) {
+//		App::roundShadow(p, 0, 0, paintw, painth, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners);
+	}
+	p.drawPixmap(geometry.topLeft(), *cache);
+	if (selected) {
+		const auto roundRadius = ImageRoundRadius::Large;
+		App::complexOverlayRect(p, geometry, roundRadius, corners);
+	}
+
+	const auto radialOpacity = radial
+		? _animation->radial.opacity()
+		: 1.;
+	const auto backOpacity = (loaded && !_data->uploading())
+		? radialOpacity
+		: 1.;
+	const auto radialSize = st::historyGroupRadialSize;
+	const auto inner = QRect(
+		geometry.x() + (geometry.width() - radialSize) / 2,
+		geometry.y() + (geometry.height() - radialSize) / 2,
+		radialSize,
+		radialSize);
+	p.setPen(Qt::NoPen);
+	if (selected) {
+		p.setBrush(st::msgDateImgBgSelected);
+	} else if (isThumbAnimation()) {
+		auto over = _animation->a_thumbOver.value(1.);
+		p.setBrush(anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, over));
+	} else {
+		auto over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel);
+		p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg);
+	}
+
+	p.setOpacity(backOpacity * p.opacity());
+
+	{
+		PainterHighQualityEnabler hq(p);
+		p.drawEllipse(inner);
+	}
+
+	auto icon = [&]() -> const style::icon * {
+		if (_data->waitingForAlbum()) {
+			return &(selected ? st::historyFileThumbWaitingSelected : st::historyFileThumbWaiting);
+		} else if (_data->loading() || _data->uploading()) {
+			return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel);
+		} else if (!IsServerMsgId(_realParent->id)) {
+			return nullptr;
+		} else if (loaded || _data->canBePlayed()) {
+			return &(selected ? st::historyFileThumbPlaySelected : st::historyFileThumbPlay);
+		}
+		return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload);
+	}();
+	const auto previous = [&]() -> const style::icon* {
+		if (_data->waitingForAlbum()) {
+			return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel);
+		}
+		return nullptr;
+	}();
+	p.setOpacity(backOpacity);
+	if (icon) {
+		if (previous && radialOpacity > 0. && radialOpacity < 1.) {
+			PaintInterpolatedIcon(p, *icon, *previous, radialOpacity, inner);
+		} else {
+			icon->paintInCenter(p, inner);
+		}
+	}
+	p.setOpacity(1);
+	if (radial) {
+		const auto line = st::historyGroupRadialLine;
+		const auto rinner = inner.marginsRemoved({ line, line, line, line });
+		const auto color = selected
+			? st::historyFileThumbRadialFgSelected
+			: st::historyFileThumbRadialFg;
+		_animation->radial.draw(p, rinner, line, color);
+	}
+}
+
+TextState Gif::getStateGrouped(
+		const QRect &geometry,
+		QPoint point,
+		StateRequest request) const {
+	if (!geometry.contains(point)) {
+		return {};
+	}
+	return TextState(_parent, (_data->loading() || _data->uploading())
+		? _cancell
+		: !IsServerMsgId(_realParent->id)
+		? nullptr
+		: (_data->loaded() || _data->canBePlayed())
+		? _openl
+		: _savel);
+}
+
 bool Gif::uploading() const {
 	return _data->uploading();
 }
@@ -725,6 +930,62 @@ bool Gif::isSeparateRoundVideo() const {
 		&& !_parent->hasBubble();
 }
 
+void Gif::validateGroupedCache(
+		const QRect &geometry,
+		RectParts corners,
+		not_null<uint64*> cacheKey,
+		not_null<QPixmap*> cache) const {
+	using Option = Images::Option;
+	const auto good = _data->goodThumbnail();
+	const auto useGood = (good && good->loaded());
+	const auto thumb = _data->thumbnail();
+	const auto useThumb = (thumb && thumb->loaded());
+	const auto image = useGood
+		? good
+		: useThumb
+		? thumb
+		: _data->thumbnailInline();
+	if (good && !useGood) {
+		good->load({});
+	}
+
+	const auto loadLevel = useGood ? 3 : useThumb ? 2 : image ? 1 : 0;
+	const auto width = geometry.width();
+	const auto height = geometry.height();
+	const auto options = Option::Smooth
+		| Option::RoundedLarge
+		| (useGood ? Option(0) : Option::Blurred)
+		| ((corners & RectPart::TopLeft) ? Option::RoundedTopLeft : Option::None)
+		| ((corners & RectPart::TopRight) ? Option::RoundedTopRight : Option::None)
+		| ((corners & RectPart::BottomLeft) ? Option::RoundedBottomLeft : Option::None)
+		| ((corners & RectPart::BottomRight) ? Option::RoundedBottomRight : Option::None);
+	const auto key = (uint64(width) << 48)
+		| (uint64(height) << 32)
+		| (uint64(options) << 16)
+		| (uint64(loadLevel));
+	if (*cacheKey == key) {
+		return;
+	}
+
+	const auto original = sizeForAspectRatio();
+	const auto originalWidth = style::ConvertScale(original.width());
+	const auto originalHeight = style::ConvertScale(original.height());
+	const auto pixSize = Ui::GetImageScaleSizeForGeometry(
+		{ originalWidth, originalHeight },
+		{ width, height });
+	const auto pixWidth = pixSize.width() * cIntRetinaFactor();
+	const auto pixHeight = pixSize.height() * cIntRetinaFactor();
+
+	*cacheKey = key;
+	*cache = (image ? image : Image::BlankMedia().get())->pixNoCache(
+		_realParent->fullId(),
+		pixWidth,
+		pixHeight,
+		options,
+		width,
+		height);
+}
+
 void Gif::setStatusSize(int newSize) const {
 	if (_data->isVideoMessage()) {
 		_statusSize = newSize;
diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.h b/Telegram/SourceFiles/history/view/media/history_view_gif.h
index f10767f5e..f0e95945a 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_gif.h
+++ b/Telegram/SourceFiles/history/view/media/history_view_gif.h
@@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 struct HistoryMessageVia;
 struct HistoryMessageReply;
 struct HistoryMessageForwarded;
+class Painter;
 
 namespace Media {
 namespace View {
@@ -35,6 +36,7 @@ class Gif final : public File {
 public:
 	Gif(
 		not_null<Element*> parent,
+		not_null<HistoryItem*> realParent,
 		not_null<DocumentData*> document);
 	~Gif();
 
@@ -61,6 +63,21 @@ public:
 		return _data;
 	}
 
+	QSize sizeForGrouping() const override;
+	void drawGrouped(
+		Painter &p,
+		const QRect &clip,
+		TextSelection selection,
+		crl::time ms,
+		const QRect &geometry,
+		RectParts corners,
+		not_null<uint64*> cacheKey,
+		not_null<QPixmap*> cache) const override;
+	TextState getStateGrouped(
+		const QRect &geometry,
+		QPoint point,
+		StateRequest request) const override;
+
 	void stopAnimation() override;
 	void checkAnimation() override;
 
@@ -116,14 +133,28 @@ private:
 	QString mediaTypeString() const;
 	bool isSeparateRoundVideo() const;
 
+	void validateGroupedCache(
+		const QRect &geometry,
+		RectParts corners,
+		not_null<uint64*> cacheKey,
+		not_null<QPixmap*> cache) const;
+	void setStatusSize(int newSize) const;
+	void updateStatusText() const;
+	QSize sizeForAspectRatio() const;
+
+	[[nodiscard]] bool downloadInCorner() const;
+	void drawCornerStatus(Painter &p, bool selected) const;
+	[[nodiscard]] TextState cornerStatusTextState(
+		QPoint point,
+		StateRequest request) const;
+
 	not_null<DocumentData*> _data;
 	int _thumbw = 1;
 	int _thumbh = 1;
 	Ui::Text::String _caption;
 	std::unique_ptr<::Media::Streaming::Instance> _streamed;
 
-	void setStatusSize(int newSize) const;
-	void updateStatusText() const;
+	QString _downloadSize;
 
 };
 
diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp
index 8d16dcee4..bf949d31d 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp
@@ -73,13 +73,8 @@ std::unique_ptr<Media> CreateAttach(
 			return std::make_unique<UnwrappedMedia>(
 				parent,
 				std::make_unique<Sticker>(parent, document));
-		} else if (document->isAnimation()) {
-			return std::make_unique<Gif>(parent, document);
-		} else if (document->isVideoFile()) {
-			return std::make_unique<Video>(
-				parent,
-				parent->data(),
-				document);
+		} else if (document->isAnimation() || document->isVideoFile()) {
+			return std::make_unique<Gif>(parent, parent->data(), document);
 		} else if (document->isWallPaper() || document->isTheme()) {
 			return std::make_unique<ThemeDocument>(
 				parent,