From 6d08394adccf3ea0d4e1ca4c24027385624863f2 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Tue, 18 Dec 2018 14:45:06 +0400
Subject: [PATCH] Divide history_media_types to several modules.

---
 Telegram/SourceFiles/apiwrap.cpp              |    1 -
 Telegram/SourceFiles/app.cpp                  |   20 +-
 Telegram/SourceFiles/boxes/send_files_box.cpp |    2 +-
 Telegram/SourceFiles/boxes/share_box.cpp      |    2 +-
 Telegram/SourceFiles/data/data_document.cpp   |    1 -
 .../SourceFiles/data/data_media_types.cpp     |   16 +-
 Telegram/SourceFiles/data/data_photo.cpp      |    1 -
 Telegram/SourceFiles/data/data_session.cpp    |    2 +-
 .../SourceFiles/data/data_shared_media.cpp    |    2 +-
 Telegram/SourceFiles/facades.cpp              |    2 +-
 .../admin_log/history_admin_log_inner.cpp     |    5 +-
 Telegram/SourceFiles/history/history.cpp      |    2 +-
 .../history/history_inner_widget.cpp          |    6 +-
 Telegram/SourceFiles/history/history_item.cpp |    3 +-
 .../history/history_item_components.cpp       |    3 +-
 .../history/history_media_types.cpp           | 5134 -----------------
 .../SourceFiles/history/history_media_types.h |  990 ----
 .../SourceFiles/history/history_message.cpp   |    2 +-
 .../SourceFiles/history/history_service.cpp   |    3 +-
 .../SourceFiles/history/history_widget.cpp    |    5 +-
 .../history/{ => media}/history_media.cpp     |    2 +-
 .../history/{ => media}/history_media.h       |    0
 .../history/media/history_media_call.cpp      |  111 +
 .../history/media/history_media_call.h        |   59 +
 .../history/media/history_media_common.cpp    |   92 +
 .../history/media/history_media_common.h      |   36 +
 .../history/media/history_media_contact.cpp   |  216 +
 .../history/media/history_media_contact.h     |   71 +
 .../history/media/history_media_document.cpp  |  770 +++
 .../history/media/history_media_document.h    |   77 +
 .../history/media/history_media_file.cpp      |  126 +
 .../history/media/history_media_file.h        |  101 +
 .../history/media/history_media_game.cpp      |  437 ++
 .../history/media/history_media_game.h        |  104 +
 .../history/media/history_media_gif.cpp       |  924 +++
 .../history/media/history_media_gif.h         |  120 +
 .../{ => media}/history_media_grouped.cpp     |    3 +-
 .../{ => media}/history_media_grouped.h       |    2 +-
 .../history/media/history_media_invoice.cpp   |  447 ++
 .../history/media/history_media_invoice.h     |   98 +
 .../history/media/history_media_location.cpp  |  311 +
 .../history/media/history_media_location.h    |   72 +
 .../history/media/history_media_photo.cpp     |  530 ++
 .../history/media/history_media_photo.h       |  102 +
 .../history/media/history_media_sticker.cpp   |  273 +
 .../history/media/history_media_sticker.h     |   67 +
 .../history/media/history_media_video.cpp     |  525 ++
 .../history/media/history_media_video.h       |   96 +
 .../history/media/history_media_web_page.cpp  |  696 +++
 .../history/media/history_media_web_page.h    |  118 +
 .../view/history_view_context_menu.cpp        |    3 +-
 .../history/view/history_view_element.cpp     |    4 +-
 .../history/view/history_view_list_widget.cpp |    3 +-
 .../history/view/history_view_message.cpp     |    4 +-
 .../view/history_view_service_message.cpp     |    2 +-
 Telegram/SourceFiles/mainwidget.cpp           |    2 +-
 .../media/player/media_player_float.cpp       |    2 +-
 .../media/view/media_view_group_thumbs.cpp    |    2 +-
 Telegram/SourceFiles/mediaview.cpp            |    1 -
 Telegram/gyp/telegram_sources.txt             |   36 +-
 60 files changed, 6673 insertions(+), 6174 deletions(-)
 delete mode 100644 Telegram/SourceFiles/history/history_media_types.cpp
 delete mode 100644 Telegram/SourceFiles/history/history_media_types.h
 rename Telegram/SourceFiles/history/{ => media}/history_media.cpp (97%)
 rename Telegram/SourceFiles/history/{ => media}/history_media.h (100%)
 create mode 100644 Telegram/SourceFiles/history/media/history_media_call.cpp
 create mode 100644 Telegram/SourceFiles/history/media/history_media_call.h
 create mode 100644 Telegram/SourceFiles/history/media/history_media_common.cpp
 create mode 100644 Telegram/SourceFiles/history/media/history_media_common.h
 create mode 100644 Telegram/SourceFiles/history/media/history_media_contact.cpp
 create mode 100644 Telegram/SourceFiles/history/media/history_media_contact.h
 create mode 100644 Telegram/SourceFiles/history/media/history_media_document.cpp
 create mode 100644 Telegram/SourceFiles/history/media/history_media_document.h
 create mode 100644 Telegram/SourceFiles/history/media/history_media_file.cpp
 create mode 100644 Telegram/SourceFiles/history/media/history_media_file.h
 create mode 100644 Telegram/SourceFiles/history/media/history_media_game.cpp
 create mode 100644 Telegram/SourceFiles/history/media/history_media_game.h
 create mode 100644 Telegram/SourceFiles/history/media/history_media_gif.cpp
 create mode 100644 Telegram/SourceFiles/history/media/history_media_gif.h
 rename Telegram/SourceFiles/history/{ => media}/history_media_grouped.cpp (99%)
 rename Telegram/SourceFiles/history/{ => media}/history_media_grouped.h (98%)
 create mode 100644 Telegram/SourceFiles/history/media/history_media_invoice.cpp
 create mode 100644 Telegram/SourceFiles/history/media/history_media_invoice.h
 create mode 100644 Telegram/SourceFiles/history/media/history_media_location.cpp
 create mode 100644 Telegram/SourceFiles/history/media/history_media_location.h
 create mode 100644 Telegram/SourceFiles/history/media/history_media_photo.cpp
 create mode 100644 Telegram/SourceFiles/history/media/history_media_photo.h
 create mode 100644 Telegram/SourceFiles/history/media/history_media_sticker.cpp
 create mode 100644 Telegram/SourceFiles/history/media/history_media_sticker.h
 create mode 100644 Telegram/SourceFiles/history/media/history_media_video.cpp
 create mode 100644 Telegram/SourceFiles/history/media/history_media_video.h
 create mode 100644 Telegram/SourceFiles/history/media/history_media_web_page.cpp
 create mode 100644 Telegram/SourceFiles/history/media/history_media_web_page.h

diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp
index e81de07fc..1b7c7c851 100644
--- a/Telegram/SourceFiles/apiwrap.cpp
+++ b/Telegram/SourceFiles/apiwrap.cpp
@@ -30,7 +30,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "boxes/add_contact_box.h"
 #include "history/history.h"
 #include "history/history_message.h"
-#include "history/history_media_types.h"
 #include "history/history_item_components.h"
 #include "history/feed/history_feed_section.h"
 #include "storage/localstorage.h"
diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp
index 5dfea1f6e..bd63c469a 100644
--- a/Telegram/SourceFiles/app.cpp
+++ b/Telegram/SourceFiles/app.cpp
@@ -17,31 +17,31 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_abstract_structure.h"
 #include "data/data_media_types.h"
 #include "data/data_session.h"
+#include "data/data_document.h"
 #include "history/history.h"
 #include "history/history_location_manager.h"
-#include "history/history_media_types.h"
 #include "history/history_item_components.h"
 #include "history/view/history_view_service_message.h"
 #include "media/media_audio.h"
 #include "ui/image/image.h"
 #include "inline_bots/inline_bot_layout_item.h"
+#include "core/crash_reports.h"
+#include "core/update_checker.h"
+#include "window/themes/window_theme.h"
+#include "window/notifications_manager.h"
+#include "platform/platform_notifications_manager.h"
+#include "storage/file_upload.h"
+#include "storage/localstorage.h"
+#include "storage/storage_facade.h"
+#include "storage/storage_shared_media.h"
 #include "messenger.h"
 #include "application.h"
-#include "storage/file_upload.h"
 #include "mainwindow.h"
 #include "mainwidget.h"
-#include "storage/localstorage.h"
 #include "apiwrap.h"
 #include "numbers.h"
 #include "observer_peer.h"
 #include "auth_session.h"
-#include "core/crash_reports.h"
-#include "core/update_checker.h"
-#include "storage/storage_facade.h"
-#include "storage/storage_shared_media.h"
-#include "window/themes/window_theme.h"
-#include "window/notifications_manager.h"
-#include "platform/platform_notifications_manager.h"
 
 #ifdef OS_MAC_OLD
 #include <libexif/exif-data.h>
diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp
index 734f2ca3e..f6cbcf807 100644
--- a/Telegram/SourceFiles/boxes/send_files_box.cpp
+++ b/Telegram/SourceFiles/boxes/send_files_box.cpp
@@ -11,7 +11,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "storage/localstorage.h"
 #include "storage/storage_media_prepare.h"
 #include "mainwidget.h"
-#include "history/history_media_types.h"
 #include "chat_helpers/message_field.h"
 #include "chat_helpers/emoji_suggestions_widget.h"
 #include "chat_helpers/tabbed_panel.h"
@@ -27,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/grouped_layout.h"
 #include "ui/text_options.h"
 #include "ui/special_buttons.h"
+#include "data/data_document.h"
 #include "media/media_clip_reader.h"
 #include "window/window_controller.h"
 #include "layout.h"
diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp
index bf7060c92..12fd6dff1 100644
--- a/Telegram/SourceFiles/boxes/share_box.cpp
+++ b/Telegram/SourceFiles/boxes/share_box.cpp
@@ -25,7 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/text_options.h"
 #include "chat_helpers/message_field.h"
 #include "history/history.h"
-#include "history/history_media_types.h"
+//#include "history/media/history_media_types.h"
 #include "history/history_message.h"
 #include "window/themes/window_theme.h"
 #include "boxes/peer_list_box.h"
diff --git a/Telegram/SourceFiles/data/data_document.cpp b/Telegram/SourceFiles/data/data_document.cpp
index 8c964ec60..1c607bd58 100644
--- a/Telegram/SourceFiles/data/data_document.cpp
+++ b/Telegram/SourceFiles/data/data_document.cpp
@@ -20,7 +20,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "platform/platform_specific.h"
 #include "history/history.h"
 #include "history/history_item.h"
-#include "history/history_media_types.h"
 #include "window/window_controller.h"
 #include "storage/cache/storage_cache_database.h"
 #include "boxes/confirm_box.h"
diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp
index a407b34ba..5220ade16 100644
--- a/Telegram/SourceFiles/data/data_media_types.cpp
+++ b/Telegram/SourceFiles/data/data_media_types.cpp
@@ -7,10 +7,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "data/data_media_types.h"
 
-#include "history/history_media_types.h"
 #include "history/history_item.h"
 #include "history/history_location_manager.h"
 #include "history/view/history_view_element.h"
+#include "history/media/history_media_photo.h"
+#include "history/media/history_media_sticker.h"
+#include "history/media/history_media_gif.h"
+#include "history/media/history_media_video.h"
+#include "history/media/history_media_document.h"
+#include "history/media/history_media_contact.h"
+#include "history/media/history_media_location.h"
+#include "history/media/history_media_game.h"
+#include "history/media/history_media_invoice.h"
+#include "history/media/history_media_call.h"
+#include "history/media/history_media_web_page.h"
 #include "ui/image/image.h"
 #include "ui/image/image_source.h"
 #include "ui/text_options.h"
@@ -18,6 +28,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "storage/storage_shared_media.h"
 #include "storage/localstorage.h"
 #include "data/data_session.h"
+#include "data/data_photo.h"
+#include "data/data_document.h"
+#include "data/data_game.h"
+#include "data/data_web_page.h"
 #include "lang/lang_keys.h"
 #include "auth_session.h"
 #include "layout.h"
diff --git a/Telegram/SourceFiles/data/data_photo.cpp b/Telegram/SourceFiles/data/data_photo.cpp
index 77b575a1d..ecf8965da 100644
--- a/Telegram/SourceFiles/data/data_photo.cpp
+++ b/Telegram/SourceFiles/data/data_photo.cpp
@@ -11,7 +11,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/image/image.h"
 #include "ui/image/image_source.h"
 #include "mainwidget.h"
-#include "history/history_media_types.h"
 #include "auth_session.h"
 #include "messenger.h"
 
diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp
index 20a2baaf9..058d5ac73 100644
--- a/Telegram/SourceFiles/data/data_session.cpp
+++ b/Telegram/SourceFiles/data/data_session.cpp
@@ -17,7 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "window/notifications_manager.h"
 #include "history/history.h"
 #include "history/history_item_components.h"
-#include "history/history_media.h"
+#include "history/media/history_media.h"
 #include "history/view/history_view_element.h"
 #include "inline_bots/inline_bot_layout_item.h"
 #include "storage/localstorage.h"
diff --git a/Telegram/SourceFiles/data/data_shared_media.cpp b/Telegram/SourceFiles/data/data_shared_media.cpp
index a824a86e2..cf1749f1e 100644
--- a/Telegram/SourceFiles/data/data_shared_media.cpp
+++ b/Telegram/SourceFiles/data/data_shared_media.cpp
@@ -14,8 +14,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "storage/storage_shared_media.h"
 #include "history/history.h"
 #include "history/history_item.h"
-#include "history/history_media_types.h"
 #include "data/data_media_types.h"
+#include "data/data_photo.h"
 #include "data/data_sparse_ids.h"
 #include "data/data_session.h"
 #include "info/info_memento.h"
diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp
index 499e2afff..2de246475 100644
--- a/Telegram/SourceFiles/facades.cpp
+++ b/Telegram/SourceFiles/facades.cpp
@@ -24,7 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "base/observer.h"
 #include "history/history.h"
 #include "history/history_item.h"
-#include "history/history_media.h"
+#include "history/media/history_media.h"
 #include "styles/style_history.h"
 #include "data/data_session.h"
 
diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp
index 5fab1052f..47309847b 100644
--- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp
+++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp
@@ -9,7 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 #include "styles/style_history.h"
 #include "history/history.h"
-#include "history/history_media_types.h"
+#include "history/media/history_media.h"
+#include "history/media/history_media_web_page.h"
 #include "history/history_message.h"
 #include "history/history_item_components.h"
 #include "history/history_item_text.h"
@@ -35,6 +36,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "lang/lang_keys.h"
 #include "boxes/edit_participant_box.h"
 #include "data/data_session.h"
+#include "data/data_photo.h"
+#include "data/data_document.h"
 #include "data/data_media_types.h"
 
 namespace AdminLog {
diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp
index 5cc9c41ef..79630b9a9 100644
--- a/Telegram/SourceFiles/history/history.cpp
+++ b/Telegram/SourceFiles/history/history.cpp
@@ -10,7 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "history/view/history_view_element.h"
 #include "history/admin_log/history_admin_log_section.h"
 #include "history/history_message.h"
-#include "history/history_media_types.h"
 #include "history/history_service.h"
 #include "history/history_item_components.h"
 #include "history/history_inner_widget.h"
@@ -33,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "storage/storage_feed_messages.h"
 #include "data/data_channel_admins.h"
 #include "data/data_feed.h"
+#include "data/data_photo.h"
 #include "ui/image/image.h"
 #include "ui/text_options.h"
 #include "core/crash_reports.h"
diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp
index 2880e34e2..c40245f26 100644
--- a/Telegram/SourceFiles/history/history_inner_widget.cpp
+++ b/Telegram/SourceFiles/history/history_inner_widget.cpp
@@ -13,7 +13,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "core/crash_reports.h"
 #include "history/history.h"
 #include "history/history_message.h"
-#include "history/history_media_types.h"
+#include "history/media/history_media.h"
+#include "history/media/history_media_sticker.h"
+#include "history/media/history_media_web_page.h"
 #include "history/history_item_components.h"
 #include "history/history_item_text.h"
 #include "history/view/history_view_message.h"
@@ -39,6 +41,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "lang/lang_keys.h"
 #include "data/data_session.h"
 #include "data/data_media_types.h"
+#include "data/data_document.h"
+#include "data/data_photo.h"
 
 namespace {
 
diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp
index b90954c17..029dd61e4 100644
--- a/Telegram/SourceFiles/history/history_item.cpp
+++ b/Telegram/SourceFiles/history/history_item.cpp
@@ -13,8 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "history/view/history_view_element.h"
 #include "history/view/history_view_service_message.h"
 #include "history/history_item_components.h"
-#include "history/history_media_types.h"
-#include "history/history_media_grouped.h"
+#include "history/media/history_media_grouped.h"
 #include "history/history_service.h"
 #include "history/history_message.h"
 #include "history/history.h"
diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp
index bd38a55d4..a496ac051 100644
--- a/Telegram/SourceFiles/history/history_item_components.cpp
+++ b/Telegram/SourceFiles/history/history_item_components.cpp
@@ -12,9 +12,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/image/image.h"
 #include "ui/text_options.h"
 #include "history/history_message.h"
-#include "history/history_media.h"
-#include "history/history_media_types.h"
 #include "history/view/history_view_service_message.h"
+#include "history/media/history_media_document.h"
 #include "media/media_audio.h"
 #include "media/player/media_player_instance.h"
 #include "auth_session.h"
diff --git a/Telegram/SourceFiles/history/history_media_types.cpp b/Telegram/SourceFiles/history/history_media_types.cpp
deleted file mode 100644
index 1867613eb..000000000
--- a/Telegram/SourceFiles/history/history_media_types.cpp
+++ /dev/null
@@ -1,5134 +0,0 @@
-/*
-This file is part of Telegram Desktop,
-the official desktop application for the Telegram messaging service.
-
-For license and copyright information please follow this link:
-https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
-*/
-#include "history/history_media_types.h"
-
-#include "lang/lang_keys.h"
-#include "mainwidget.h"
-#include "layout.h"
-#include "mainwindow.h"
-#include "storage/localstorage.h"
-#include "storage/storage_shared_media.h"
-#include "media/media_audio.h"
-#include "media/media_clip_reader.h"
-#include "media/player/media_player_instance.h"
-#include "media/player/media_player_round_controller.h"
-#include "media/view/media_clip_playback.h"
-#include "boxes/confirm_box.h"
-#include "boxes/add_contact_box.h"
-#include "boxes/sticker_set_box.h"
-#include "core/click_handler_types.h"
-#include "history/history.h"
-#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"
-#include "window/window_controller.h"
-#include "styles/style_history.h"
-#include "calls/calls_instance.h"
-#include "ui/image/image.h"
-#include "ui/empty_userpic.h"
-#include "ui/grouped_layout.h"
-#include "ui/text_options.h"
-#include "ui/emoji_config.h"
-#include "data/data_session.h"
-#include "data/data_media_types.h"
-
-namespace {
-
-constexpr auto kMaxGifForwardedBarLines = 4;
-constexpr auto kMaxOriginalEntryLines = 8192;
-
-using TextState = HistoryView::TextState;
-
-int documentMaxStatusWidth(DocumentData *document) {
-	auto result = st::normalFont->width(formatDownloadText(document->size, document->size));
-	if (const auto song = document->song()) {
-		accumulate_max(result, st::normalFont->width(formatPlayedText(song->duration, song->duration)));
-		accumulate_max(result, st::normalFont->width(formatDurationAndSizeText(song->duration, document->size)));
-	} else if (const auto voice = document->voice()) {
-		accumulate_max(result, st::normalFont->width(formatPlayedText(voice->duration, voice->duration)));
-		accumulate_max(result, st::normalFont->width(formatDurationAndSizeText(voice->duration, document->size)));
-	} else if (document->isVideoFile()) {
-		accumulate_max(result, st::normalFont->width(formatDurationAndSizeText(document->duration(), document->size)));
-	} else {
-		accumulate_max(result, st::normalFont->width(formatSizeText(document->size)));
-	}
-	return result;
-}
-
-int gifMaxStatusWidth(DocumentData *document) {
-	auto result = st::normalFont->width(formatDownloadText(document->size, document->size));
-	accumulate_max(result, st::normalFont->width(formatGifAndSizeText(document->size)));
-	return result;
-}
-
-std::unique_ptr<HistoryMedia> CreateAttach(
-		not_null<HistoryView::Element*> parent,
-		DocumentData *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()) {
-			return std::make_unique<HistoryGif>(
-				parent,
-				document);
-		} else if (document->isVideoFile()) {
-			return std::make_unique<HistoryVideo>(
-				parent,
-				parent->data(),
-				document);
-		}
-		return std::make_unique<HistoryDocument>(
-			parent,
-			document);
-	} else if (photo) {
-		return std::make_unique<HistoryPhoto>(
-			parent,
-			parent->data(),
-			photo);
-	}
-	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;
-}
-
-void PaintInterpolatedIcon(
-		Painter &p,
-		const style::icon &a,
-		const style::icon &b,
-		float64 b_ratio,
-		QRect rect) {
-	PainterHighQualityEnabler hq(p);
-	p.save();
-	p.translate(rect.center());
-	p.setOpacity(b_ratio);
-	p.scale(b_ratio, b_ratio);
-	b.paintInCenter(p, rect.translated(-rect.center()));
-	p.restore();
-
-	p.save();
-	p.translate(rect.center());
-	p.setOpacity(1. - b_ratio);
-	p.scale(1. - b_ratio, 1. - b_ratio);
-	a.paintInCenter(p, rect.translated(-rect.center()));
-	p.restore();
-}
-
-} // namespace
-
-QString FillAmountAndCurrency(uint64 amount, const QString &currency) {
-	static const auto ShortCurrencyNames = QMap<QString, QString> {
-		{ qsl("USD"), QString::fromUtf8("\x24") },
-		{ qsl("GBP"), QString::fromUtf8("\xC2\xA3") },
-		{ qsl("EUR"), QString::fromUtf8("\xE2\x82\xAC") },
-		{ qsl("JPY"), QString::fromUtf8("\xC2\xA5") },
-	};
-	static const auto Denominators = QMap<QString, int> {
-		{ qsl("CLF"), 10000 },
-		{ qsl("BHD"), 1000 },
-		{ qsl("IQD"), 1000 },
-		{ qsl("JOD"), 1000 },
-		{ qsl("KWD"), 1000 },
-		{ qsl("LYD"), 1000 },
-		{ qsl("OMR"), 1000 },
-		{ qsl("TND"), 1000 },
-		{ qsl("BIF"), 1 },
-		{ qsl("BYR"), 1 },
-		{ qsl("CLP"), 1 },
-		{ qsl("CVE"), 1 },
-		{ qsl("DJF"), 1 },
-		{ qsl("GNF"), 1 },
-		{ qsl("ISK"), 1 },
-		{ qsl("JPY"), 1 },
-		{ qsl("KMF"), 1 },
-		{ qsl("KRW"), 1 },
-		{ qsl("MGA"), 1 },
-		{ qsl("PYG"), 1 },
-		{ qsl("RWF"), 1 },
-		{ qsl("UGX"), 1 },
-		{ qsl("UYI"), 1 },
-		{ qsl("VND"), 1 },
-		{ qsl("VUV"), 1 },
-		{ qsl("XAF"), 1 },
-		{ qsl("XOF"), 1 },
-		{ qsl("XPF"), 1 },
-		{ qsl("MRO"), 10 },
-	};
-	const auto currencyText = ShortCurrencyNames.value(currency, currency);
-	const auto denominator = Denominators.value(currency, 100);
-	const auto currencyValue = amount / float64(denominator);
-	const auto digits = [&] {
-		auto result = 0;
-		for (auto test = 1; test < denominator; test *= 10) {
-			++result;
-		}
-		return result;
-	}();
-	return QLocale::system().toCurrencyString(currencyValue, currencyText);
-	//auto amountBucks = amount / 100;
-	//auto amountCents = amount % 100;
-	//auto amountText = qsl("%1,%2").arg(amountBucks).arg(amountCents, 2, 10, QChar('0'));
-	//return currencyText + amountText;
-}
-
-void HistoryFileMedia::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
-	if (p == _savel || p == _cancell) {
-		if (active && !dataLoaded()) {
-			ensureAnimation();
-			_animation->a_thumbOver.start([this] { thumbAnimationCallback(); }, 0., 1., st::msgFileOverDuration);
-		} else if (!active && _animation && !dataLoaded()) {
-			_animation->a_thumbOver.start([this] { thumbAnimationCallback(); }, 1., 0., st::msgFileOverDuration);
-		}
-	}
-}
-
-void HistoryFileMedia::thumbAnimationCallback() {
-	Auth().data().requestViewRepaint(_parent);
-}
-
-void HistoryFileMedia::clickHandlerPressedChanged(
-		const ClickHandlerPtr &handler,
-		bool pressed) {
-	Auth().data().requestViewRepaint(_parent);
-}
-
-void HistoryFileMedia::setLinks(
-		FileClickHandlerPtr &&openl,
-		FileClickHandlerPtr &&savel,
-		FileClickHandlerPtr &&cancell) {
-	_openl = std::move(openl);
-	_savel = std::move(savel);
-	_cancell = std::move(cancell);
-}
-
-void HistoryFileMedia::refreshParentId(not_null<HistoryItem*> realParent) {
-	const auto contextId = realParent->fullId();
-	_openl->setMessageId(contextId);
-	_savel->setMessageId(contextId);
-	_cancell->setMessageId(contextId);
-}
-
-void HistoryFileMedia::setStatusSize(int newSize, int fullSize, int duration, qint64 realDuration) const {
-	_statusSize = newSize;
-	if (_statusSize == FileStatusSizeReady) {
-		_statusText = (duration >= 0) ? formatDurationAndSizeText(duration, fullSize) : (duration < -1 ? formatGifAndSizeText(fullSize) : formatSizeText(fullSize));
-	} else if (_statusSize == FileStatusSizeLoaded) {
-		_statusText = (duration >= 0) ? formatDurationText(duration) : (duration < -1 ? qsl("GIF") : formatSizeText(fullSize));
-	} else if (_statusSize == FileStatusSizeFailed) {
-		_statusText = lang(lng_attach_failed);
-	} else if (_statusSize >= 0) {
-		_statusText = formatDownloadText(_statusSize, fullSize);
-	} else {
-		_statusText = formatPlayedText(-_statusSize - 1, realDuration);
-	}
-}
-
-void HistoryFileMedia::step_radial(TimeMs ms, bool timer) {
-	const auto updateRadial = [&] {
-		return _animation->radial.update(
-			dataProgress(),
-			dataFinished(),
-			ms);
-	};
-	if (timer) {
-		if (!anim::Disabled() || updateRadial()) {
-			Auth().data().requestViewRepaint(_parent);
-		}
-	} else {
-		updateRadial();
-		if (!_animation->radial.animating()) {
-			checkAnimationFinished();
-		}
-	}
-}
-
-void HistoryFileMedia::ensureAnimation() const {
-	if (!_animation) {
-		_animation = std::make_unique<AnimationData>(animation(const_cast<HistoryFileMedia*>(this), &HistoryFileMedia::step_radial));
-	}
-}
-
-void HistoryFileMedia::checkAnimationFinished() const {
-	if (_animation && !_animation->a_thumbOver.animating() && !_animation->radial.animating()) {
-		if (dataLoaded()) {
-			_animation.reset();
-		}
-	}
-}
-void HistoryFileMedia::setDocumentLinks(
-		not_null<DocumentData*> document,
-		not_null<HistoryItem*> realParent,
-		bool inlinegif) {
-	FileClickHandlerPtr open, save;
-	const auto context = realParent->fullId();
-	if (inlinegif) {
-		open = std::make_shared<GifOpenClickHandler>(document, context);
-	} else {
-		open = std::make_shared<DocumentOpenClickHandler>(document, context);
-	}
-	if (inlinegif) {
-		save = std::make_shared<GifOpenClickHandler>(document, context);
-	} else if (document->isVoiceMessage()) {
-		save = std::make_shared<DocumentOpenClickHandler>(document, context);
-	} else {
-		save = std::make_shared<DocumentSaveClickHandler>(document, context);
-	}
-	setLinks(
-		std::move(open),
-		std::move(save),
-		std::make_shared<DocumentCancelClickHandler>(document, context));
-}
-
-HistoryFileMedia::~HistoryFileMedia() = default;
-
-HistoryPhoto::HistoryPhoto(
-	not_null<Element*> parent,
-	not_null<HistoryItem*> realParent,
-	not_null<PhotoData*> photo)
-: HistoryFileMedia(parent, realParent)
-, _data(photo)
-, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) {
-	const auto fullId = realParent->fullId();
-	setLinks(
-		std::make_shared<PhotoOpenClickHandler>(_data, fullId),
-		std::make_shared<PhotoSaveClickHandler>(_data, fullId),
-		std::make_shared<PhotoCancelClickHandler>(_data, fullId));
-	_caption = createCaption(realParent);
-	create(realParent->fullId());
-}
-
-HistoryPhoto::HistoryPhoto(
-	not_null<Element*> parent,
-	not_null<PeerData*> chat,
-	not_null<PhotoData*> photo,
-	int width)
-: HistoryFileMedia(parent, parent->data())
-, _data(photo)
-, _serviceWidth(width) {
-	create(parent->data()->fullId(), chat);
-}
-
-void HistoryPhoto::create(FullMsgId contextId, PeerData *chat) {
-	setLinks(
-		std::make_shared<PhotoOpenClickHandler>(_data, contextId, chat),
-		std::make_shared<PhotoSaveClickHandler>(_data, contextId, chat),
-		std::make_shared<PhotoCancelClickHandler>(_data, contextId, chat));
-	_data->thumb->load(contextId);
-}
-
-QSize HistoryPhoto::countOptimalSize() {
-	if (_parent->media() != this) {
-		_caption = Text();
-	} else if (_caption.hasSkipBlock()) {
-		_caption.updateSkipBlock(
-			_parent->skipBlockWidth(),
-			_parent->skipBlockHeight());
-	}
-
-	auto maxWidth = 0;
-	auto minHeight = 0;
-
-	auto tw = ConvertScale(_data->full->width());
-	auto th = ConvertScale(_data->full->height());
-	if (!tw || !th) {
-		tw = th = 1;
-	}
-	if (tw > st::maxMediaSize) {
-		th = (st::maxMediaSize * th) / tw;
-		tw = st::maxMediaSize;
-	}
-	if (th > st::maxMediaSize) {
-		tw = (st::maxMediaSize * tw) / th;
-		th = st::maxMediaSize;
-	}
-
-	if (_serviceWidth > 0) {
-		return { _serviceWidth, _serviceWidth };
-	}
-	const auto minWidth = qMax(st::minPhotoSize, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x()));
-	const auto maxActualWidth = qMax(tw, minWidth);
-	maxWidth = qMax(maxActualWidth, th);
-	minHeight = qMax(th, st::minPhotoSize);
-	if (_parent->hasBubble() && !_caption.isEmpty()) {
-		auto captionw = maxActualWidth - st::msgPadding.left() - st::msgPadding.right();
-		minHeight += st::mediaCaptionSkip + _caption.countHeight(captionw);
-		if (isBubbleBottom()) {
-			minHeight += st::msgPadding.bottom();
-		}
-	}
-	return { maxWidth, minHeight };
-}
-
-QSize HistoryPhoto::countCurrentSize(int newWidth) {
-	int tw = ConvertScale(_data->full->width()), th = ConvertScale(_data->full->height());
-	if (tw > st::maxMediaSize) {
-		th = (st::maxMediaSize * th) / tw;
-		tw = st::maxMediaSize;
-	}
-	if (th > st::maxMediaSize) {
-		tw = (st::maxMediaSize * tw) / th;
-		th = st::maxMediaSize;
-	}
-
-	_pixw = qMin(newWidth, maxWidth());
-	_pixh = th;
-	if (tw > _pixw) {
-		_pixh = (_pixw * _pixh / tw);
-	} else {
-		_pixw = tw;
-	}
-	if (_pixh > newWidth) {
-		_pixw = (_pixw * newWidth) / _pixh;
-		_pixh = newWidth;
-	}
-	if (_pixw < 1) _pixw = 1;
-	if (_pixh < 1) _pixh = 1;
-
-	auto minWidth = qMax(st::minPhotoSize, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x()));
-	newWidth = qMax(_pixw, minWidth);
-	auto newHeight = qMax(_pixh, st::minPhotoSize);
-	if (_parent->hasBubble() && !_caption.isEmpty()) {
-		const auto captionw = newWidth
-			- st::msgPadding.left()
-			- st::msgPadding.right();
-		newHeight += st::mediaCaptionSkip + _caption.countHeight(captionw);
-		if (isBubbleBottom()) {
-			newHeight += st::msgPadding.bottom();
-		}
-	}
-	return { newWidth, newHeight };
-}
-
-void HistoryPhoto::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const {
-	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
-
-	_data->automaticLoad(_realParent->fullId(), _parent->data());
-	auto selected = (selection == FullSelection);
-	auto loaded = _data->loaded();
-	auto displayLoading = _data->displayLoading();
-
-	auto inWebPage = (_parent->media() != this);
-	auto paintx = 0, painty = 0, paintw = width(), painth = height();
-	auto bubble = _parent->hasBubble();
-
-	auto captionw = paintw - st::msgPadding.left() - st::msgPadding.right();
-
-	if (displayLoading) {
-		ensureAnimation();
-		if (!_animation->radial.animating()) {
-			_animation->radial.start(_data->progress());
-		}
-	}
-	bool radial = isRadialAnimation(ms);
-
-	auto rthumb = rtlrect(paintx, painty, paintw, painth, width());
-	if (_serviceWidth > 0) {
-		const auto pix = loaded
-			? _data->full->pixCircled(_realParent->fullId(), _pixw, _pixh)
-			: _data->thumb->pixBlurredCircled(_realParent->fullId(), _pixw, _pixh);
-		p.drawPixmap(rthumb.topLeft(), pix);
-	} else {
-		if (bubble) {
-			if (!_caption.isEmpty()) {
-				painth -= st::mediaCaptionSkip + _caption.countHeight(captionw);
-				if (isBubbleBottom()) {
-					painth -= st::msgPadding.bottom();
-				}
-				rthumb = rtlrect(paintx, painty, paintw, painth, width());
-			}
-		} else {
-			App::roundShadow(p, 0, 0, paintw, painth, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners);
-		}
-		auto inWebPage = (_parent->media() != this);
-		auto roundRadius = inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large;
-		auto roundCorners = inWebPage ? RectPart::AllCorners : ((isBubbleTop() ? (RectPart::TopLeft | RectPart::TopRight) : RectPart::None)
-			| ((isBubbleBottom() && _caption.isEmpty()) ? (RectPart::BottomLeft | RectPart::BottomRight) : RectPart::None));
-		const auto pix = loaded
-			? _data->full->pixSingle(_realParent->fullId(), _pixw, _pixh, paintw, painth, roundRadius, roundCorners)
-			: _data->thumb->pixBlurredSingle(_realParent->fullId(), _pixw, _pixh, paintw, painth, roundRadius, roundCorners);
-		p.drawPixmap(rthumb.topLeft(), pix);
-		if (selected) {
-			App::complexOverlayRect(p, rthumb, roundRadius, roundCorners);
-		}
-	}
-	if (radial || (!loaded && !_data->loading())) {
-		const auto radialOpacity = (radial && loaded && !_data->uploading())
-			? _animation->radial.opacity() :
-			1.;
-		QRect inner(rthumb.x() + (rthumb.width() - st::msgFileSize) / 2, rthumb.y() + (rthumb.height() - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize);
-		p.setPen(Qt::NoPen);
-		if (selected) {
-			p.setBrush(st::msgDateImgBgSelected);
-		} else if (isThumbAnimation(ms)) {
-			auto over = _animation->a_thumbOver.current();
-			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(radialOpacity * p.opacity());
-
-		{
-			PainterHighQualityEnabler hq(p);
-			p.drawEllipse(inner);
-		}
-
-		p.setOpacity(radialOpacity);
-		auto icon = ([radial, this, selected]() -> const style::icon* {
-			if (radial || _data->loading()) {
-				if (_data->uploading()
-					|| !_data->full->location().isNull()) {
-					return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel);
-				}
-				return nullptr;
-			}
-			return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload);
-		})();
-		if (icon) {
-			icon->paintInCenter(p, inner);
-		}
-		p.setOpacity(1);
-		if (radial) {
-			QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine)));
-			_animation->radial.draw(p, rinner, st::msgFileRadialLine, selected ? st::historyFileThumbRadialFgSelected : st::historyFileThumbRadialFg);
-		}
-	}
-
-	// date
-	if (!_caption.isEmpty()) {
-		auto outbg = _parent->hasOutLayout();
-		p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg));
-		_caption.draw(p, st::msgPadding.left(), painty + painth + st::mediaCaptionSkip, captionw, style::al_left, 0, -1, selection);
-	} else if (!inWebPage) {
-		auto fullRight = paintx + paintw;
-		auto fullBottom = painty + painth;
-		if (needInfoDisplay()) {
-			_parent->drawInfo(p, fullRight, fullBottom, 2 * paintx + paintw, selected, InfoDisplayType::Image);
-		}
-		if (!bubble && _parent->displayRightAction()) {
-			auto fastShareLeft = (fullRight + st::historyFastShareLeft);
-			auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize);
-			_parent->drawRightAction(p, fastShareLeft, fastShareTop, 2 * paintx + paintw);
-		}
-	}
-}
-
-TextState HistoryPhoto::textState(QPoint point, StateRequest request) const {
-	auto result = TextState(_parent);
-
-	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) {
-		return result;
-	}
-	auto paintx = 0, painty = 0, paintw = width(), painth = height();
-	auto bubble = _parent->hasBubble();
-
-	if (bubble && !_caption.isEmpty()) {
-		const auto captionw = paintw
-			- st::msgPadding.left()
-			- st::msgPadding.right();
-		painth -= _caption.countHeight(captionw);
-		if (isBubbleBottom()) {
-			painth -= st::msgPadding.bottom();
-		}
-		if (QRect(st::msgPadding.left(), painth, captionw, height() - painth).contains(point)) {
-			result = TextState(_parent, _caption.getState(
-				point - QPoint(st::msgPadding.left(), painth),
-				captionw,
-				request.forText()));
-			return result;
-		}
-		painth -= st::mediaCaptionSkip;
-	}
-	if (QRect(paintx, painty, paintw, painth).contains(point)) {
-		if (_data->uploading()) {
-			result.link = _cancell;
-		} else if (_data->loaded()) {
-			result.link = _openl;
-		} else if (_data->loading()) {
-			if (!_data->full->location().isNull()) {
-				result.link = _cancell;
-			}
-		} else {
-			result.link = _savel;
-		}
-	}
-	if (_caption.isEmpty() && _parent->media() == this) {
-		auto fullRight = paintx + paintw;
-		auto fullBottom = painty + painth;
-		if (_parent->pointInTime(fullRight, fullBottom, point, InfoDisplayType::Image)) {
-			result.cursor = CursorState::Date;
-		}
-		if (!bubble && _parent->displayRightAction()) {
-			auto fastShareLeft = (fullRight + st::historyFastShareLeft);
-			auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize);
-			if (QRect(fastShareLeft, fastShareTop, st::historyFastShareSize, st::historyFastShareSize).contains(point)) {
-				result.link = _parent->rightActionLink();
-			}
-		}
-	}
-	return result;
-}
-
-QSize HistoryPhoto::sizeForGrouping() const {
-	const auto width = _data->full->width();
-	const auto height = _data->full->height();
-	return { std::max(width, 1), std::max(height, 1) };
-}
-
-void HistoryPhoto::drawGrouped(
-		Painter &p,
-		const QRect &clip,
-		TextSelection selection,
-		TimeMs 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(ms);
-
-	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 displayState = radial
-		|| (!loaded && !_data->loading())
-		|| _data->waitingForAlbum();
-	if (displayState) {
-		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(ms)) {
-			auto over = _animation->a_thumbOver.current();
-			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);
-		}
-
-		const auto icon = [&]() -> const style::icon* {
-			if (_data->waitingForAlbum()) {
-				return &(selected ? st::historyFileThumbWaitingSelected : st::historyFileThumbWaiting);
-			} else if (radial || _data->loading()) {
-				if (_data->uploading() || !_data->full->location().isNull()) {
-					return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel);
-				}
-				return nullptr;
-			}
-			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 HistoryPhoto::getStateGrouped(
-		const QRect &geometry,
-		QPoint point,
-		StateRequest request) const {
-	if (!geometry.contains(point)) {
-		return {};
-	}
-	return TextState(_parent, _data->uploading()
-		? _cancell
-		: _data->loaded()
-		? _openl
-		: _data->loading()
-		? (_data->full->location().isNull()
-			? ClickHandlerPtr()
-			: _cancell)
-		: _savel);
-}
-
-float64 HistoryPhoto::dataProgress() const {
-	return _data->progress();
-}
-
-bool HistoryPhoto::dataFinished() const {
-	return !_data->loading()
-		&& (!_data->uploading() || _data->waitingForAlbum());
-}
-
-bool HistoryPhoto::dataLoaded() const {
-	return _data->loaded();
-}
-
-bool HistoryPhoto::needInfoDisplay() const {
-	return (_parent->data()->id < 0 || _parent->isUnderCursor());
-}
-
-void HistoryPhoto::validateGroupedCache(
-		const QRect &geometry,
-		RectParts corners,
-		not_null<uint64*> cacheKey,
-		not_null<QPixmap*> cache) const {
-	using Option = Images::Option;
-	const auto loaded = _data->loaded();
-	const auto loadLevel = loaded ? 2 : _data->thumb->loaded() ? 1 : 0;
-	const auto width = geometry.width();
-	const auto height = geometry.height();
-	const auto options = Option::Smooth
-		| Option::RoundedLarge
-		| (loaded ? Option::None : 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 originalWidth = ConvertScale(_data->full->width());
-	const auto originalHeight = ConvertScale(_data->full->height());
-	const auto pixSize = Ui::GetImageScaleSizeForGeometry(
-		{ originalWidth, originalHeight },
-		{ width, height });
-	const auto pixWidth = pixSize.width() * cIntRetinaFactor();
-	const auto pixHeight = pixSize.height() * cIntRetinaFactor();
-	const auto &image = loaded ? _data->full : _data->thumb;
-
-	*cacheKey = key;
-	*cache = image->pixNoCache(_realParent->fullId(), pixWidth, pixHeight, options, width, height);
-}
-
-TextWithEntities HistoryPhoto::selectedText(TextSelection selection) const {
-	return _caption.originalTextWithEntities(selection, ExpandLinksAll);
-}
-
-bool HistoryPhoto::needsBubble() const {
-	if (!_caption.isEmpty()) {
-		return true;
-	}
-	const auto item = _parent->data();
-	if (item->toHistoryMessage()) {
-		return item->viaBot()
-			|| item->Has<HistoryMessageReply>()
-			|| _parent->displayForwardedFrom()
-			|| _parent->displayFromName();
-	}
-	return false;
-}
-
-void HistoryPhoto::parentTextUpdated() {
-	_caption = (_parent->media() == this)
-		? createCaption(_parent->data())
-		: Text();
-	Auth().data().requestViewResize(_parent);
-}
-
-HistoryVideo::HistoryVideo(
-	not_null<Element*> parent,
-	not_null<HistoryItem*> realParent,
-	not_null<DocumentData*> document)
-: HistoryFileMedia(parent, realParent)
-, _data(document)
-, _thumbw(1)
-, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) {
-	_caption = createCaption(realParent);
-
-	setDocumentLinks(_data, realParent);
-
-	setStatusSize(FileStatusSizeReady);
-
-	_data->thumb->load(realParent->fullId());
-}
-
-QSize HistoryVideo::countOptimalSize() {
-	if (_parent->media() != this) {
-		_caption = Text();
-	} else if (_caption.hasSkipBlock()) {
-		_caption.updateSkipBlock(
-			_parent->skipBlockWidth(),
-			_parent->skipBlockHeight());
-	}
-
-	auto tw = ConvertScale(_data->thumb->width());
-	auto th = ConvertScale(_data->thumb->height());
-	if (!tw || !th) {
-		tw = th = 1;
-	}
-	if (tw * st::msgVideoSize.height() > th * st::msgVideoSize.width()) {
-		th = qRound((st::msgVideoSize.width() / float64(tw)) * th);
-		tw = st::msgVideoSize.width();
-	} else {
-		tw = qRound((st::msgVideoSize.height() / float64(th)) * tw);
-		th = st::msgVideoSize.height();
-	}
-
-	_thumbw = qMax(tw, 1);
-	_thumbh = qMax(th, 1);
-
-	auto minWidth = qMax(st::minPhotoSize, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x()));
-	minWidth = qMax(minWidth, documentMaxStatusWidth(_data) + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x()));
-	auto maxWidth = qMax(_thumbw, minWidth);
-	auto minHeight = qMax(th, st::minPhotoSize);
-	if (_parent->hasBubble() && !_caption.isEmpty()) {
-		const auto captionw = maxWidth
-			- st::msgPadding.left()
-			- st::msgPadding.right();
-		minHeight += st::mediaCaptionSkip + _caption.countHeight(captionw);
-		if (isBubbleBottom()) {
-			minHeight += st::msgPadding.bottom();
-		}
-	}
-	return { maxWidth, minHeight };
-}
-
-QSize HistoryVideo::countCurrentSize(int newWidth) {
-	int tw = ConvertScale(_data->thumb->width()), th = ConvertScale(_data->thumb->height());
-	if (!tw || !th) {
-		tw = th = 1;
-	}
-	if (tw * st::msgVideoSize.height() > th * st::msgVideoSize.width()) {
-		th = qRound((st::msgVideoSize.width() / float64(tw)) * th);
-		tw = st::msgVideoSize.width();
-	} else {
-		tw = qRound((st::msgVideoSize.height() / float64(th)) * tw);
-		th = st::msgVideoSize.height();
-	}
-
-	if (newWidth < tw) {
-		th = qRound((newWidth / float64(tw)) * th);
-		tw = newWidth;
-	}
-
-	_thumbw = qMax(tw, 1);
-	_thumbh = qMax(th, 1);
-	auto minWidth = qMax(st::minPhotoSize, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x()));
-	minWidth = qMax(minWidth, documentMaxStatusWidth(_data) + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x()));
-	newWidth = qMax(_thumbw, minWidth);
-	auto newHeight = qMax(th, st::minPhotoSize);
-	if (_parent->hasBubble() && !_caption.isEmpty()) {
-		const auto captionw = newWidth
-			- st::msgPadding.left()
-			- st::msgPadding.right();
-		newHeight += st::mediaCaptionSkip + _caption.countHeight(captionw);
-		if (isBubbleBottom()) {
-			newHeight += st::msgPadding.bottom();
-		}
-	}
-	return { newWidth, newHeight };
-}
-
-void HistoryVideo::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const {
-	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
-
-	_data->automaticLoad(_realParent->fullId(), _parent->data());
-	bool loaded = _data->loaded(), displayLoading = _data->displayLoading();
-	bool selected = (selection == FullSelection);
-
-	auto paintx = 0, painty = 0, paintw = width(), painth = height();
-	bool bubble = _parent->hasBubble();
-
-	int captionw = paintw - st::msgPadding.left() - st::msgPadding.right();
-
-	if (displayLoading) {
-		ensureAnimation();
-		if (!_animation->radial.animating()) {
-			_animation->radial.start(_data->progress());
-		}
-	}
-	updateStatusText();
-	bool radial = isRadialAnimation(ms);
-
-	if (bubble) {
-		if (!_caption.isEmpty()) {
-			painth -= st::mediaCaptionSkip + _caption.countHeight(captionw);
-			if (isBubbleBottom()) {
-				painth -= st::msgPadding.bottom();
-			}
-		}
-	} else {
-		App::roundShadow(p, 0, 0, paintw, painth, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners);
-	}
-
-	auto inWebPage = (_parent->media() != this);
-	auto roundRadius = inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large;
-	auto roundCorners = inWebPage ? RectPart::AllCorners : ((isBubbleTop() ? (RectPart::TopLeft | RectPart::TopRight) : RectPart::None)
-		| ((isBubbleBottom() && _caption.isEmpty()) ? (RectPart::BottomLeft | RectPart::BottomRight) : RectPart::None));
-	QRect rthumb(rtlrect(paintx, painty, paintw, painth, width()));
-
-	const auto good = _data->goodThumbnail();
-	if (good && good->loaded()) {
-		p.drawPixmap(rthumb.topLeft(), good->pixSingle({}, _thumbw, _thumbh, paintw, painth, roundRadius, roundCorners));
-	} else {
-		if (good) {
-			good->load({});
-		}
-		p.drawPixmap(rthumb.topLeft(), _data->thumb->pixBlurredSingle(_realParent->fullId(), _thumbw, _thumbh, paintw, painth, roundRadius, roundCorners));
-	}
-	if (selected) {
-		App::complexOverlayRect(p, rthumb, roundRadius, roundCorners);
-	}
-
-	QRect inner(rthumb.x() + (rthumb.width() - st::msgFileSize) / 2, rthumb.y() + (rthumb.height() - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize);
-	p.setPen(Qt::NoPen);
-	if (selected) {
-		p.setBrush(st::msgDateImgBgSelected);
-	} else if (isThumbAnimation(ms)) {
-		auto over = _animation->a_thumbOver.current();
-		p.setBrush(anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, over));
-	} else {
-		bool over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel);
-		p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg);
-	}
-
-	{
-		PainterHighQualityEnabler hq(p);
-		p.drawEllipse(inner);
-	}
-
-	if (!selected && _animation) {
-		p.setOpacity(1);
-	}
-
-	auto icon = ([this, radial, selected, loaded]() -> const style::icon * {
-		if (loaded && !radial) {
-			return &(selected ? st::historyFileThumbPlaySelected : st::historyFileThumbPlay);
-		} else if (radial || _data->loading()) {
-			if (_parent->data()->id > 0 || _data->uploading()) {
-				return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel);
-			}
-			return nullptr;
-		}
-		return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload);
-	})();
-	if (icon) {
-		icon->paintInCenter(p, inner);
-	}
-	if (radial) {
-		QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine)));
-		_animation->radial.draw(p, rinner, st::msgFileRadialLine, selected ? st::historyFileThumbRadialFgSelected : st::historyFileThumbRadialFg);
-	}
-
-	auto statusX = paintx + st::msgDateImgDelta + st::msgDateImgPadding.x(), statusY = painty + st::msgDateImgDelta + st::msgDateImgPadding.y();
-	auto statusW = st::normalFont->width(_statusText) + 2 * st::msgDateImgPadding.x();
-	auto statusH = st::normalFont->height + 2 * st::msgDateImgPadding.y();
-	App::roundRect(p, rtlrect(statusX - st::msgDateImgPadding.x(), statusY - st::msgDateImgPadding.y(), statusW, statusH, width()), selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners);
-	p.setFont(st::normalFont);
-	p.setPen(st::msgDateImgFg);
-	p.drawTextLeft(statusX, statusY, width(), _statusText, statusW - 2 * st::msgDateImgPadding.x());
-
-	// date
-	if (!_caption.isEmpty()) {
-		auto outbg = _parent->hasOutLayout();
-		p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg));
-		_caption.draw(p, st::msgPadding.left(), painty + painth + st::mediaCaptionSkip, captionw, style::al_left, 0, -1, selection);
-	} else if (_parent->media() == this) {
-		auto fullRight = paintx + paintw, fullBottom = painty + painth;
-		_parent->drawInfo(p, fullRight, fullBottom, 2 * paintx + paintw, selected, InfoDisplayType::Image);
-		if (!bubble && _parent->displayRightAction()) {
-			auto fastShareLeft = (fullRight + st::historyFastShareLeft);
-			auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize);
-			_parent->drawRightAction(p, fastShareLeft, fastShareTop, 2 * paintx + paintw);
-		}
-	}
-}
-
-TextState HistoryVideo::textState(QPoint point, StateRequest request) const {
-	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) {
-		return {};
-	}
-
-	auto result = TextState(_parent);
-	bool loaded = _data->loaded();
-
-	auto paintx = 0, painty = 0, paintw = width(), painth = height();
-	bool bubble = _parent->hasBubble();
-
-	if (bubble && !_caption.isEmpty()) {
-		const auto captionw = paintw
-			- st::msgPadding.left()
-			- st::msgPadding.right();
-		painth -= _caption.countHeight(captionw);
-		if (isBubbleBottom()) {
-			painth -= st::msgPadding.bottom();
-		}
-		if (QRect(st::msgPadding.left(), painth, captionw, height() - painth).contains(point)) {
-			result = TextState(_parent, _caption.getState(
-				point - QPoint(st::msgPadding.left(), painth),
-				captionw,
-				request.forText()));
-		}
-		painth -= st::mediaCaptionSkip;
-	}
-	if (QRect(paintx, painty, paintw, painth).contains(point)) {
-		if (_data->uploading()) {
-			result.link = _cancell;
-		} else {
-			result.link = loaded ? _openl : (_data->loading() ? _cancell : _savel);
-		}
-	}
-	if (_caption.isEmpty() && _parent->media() == this) {
-		auto fullRight = paintx + paintw;
-		auto fullBottom = painty + painth;
-		if (_parent->pointInTime(fullRight, fullBottom, point, InfoDisplayType::Image)) {
-			result.cursor = CursorState::Date;
-		}
-		if (!bubble && _parent->displayRightAction()) {
-			auto fastShareLeft = (fullRight + st::historyFastShareLeft);
-			auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize);
-			if (QRect(fastShareLeft, fastShareTop, st::historyFastShareSize, st::historyFastShareSize).contains(point)) {
-				result.link = _parent->rightActionLink();
-			}
-		}
-	}
-	return result;
-}
-
-QSize HistoryVideo::sizeForGrouping() const {
-	const auto width = _data->dimensions.isEmpty()
-		? _data->thumb->width()
-		: _data->dimensions.width();
-	const auto height = _data->dimensions.isEmpty()
-		? _data->thumb->height()
-		: _data->dimensions.height();
-	return { std::max(width, 1), std::max(height, 1) };
-}
-
-void HistoryVideo::drawGrouped(
-		Painter &p,
-		const QRect &clip,
-		TextSelection selection,
-		TimeMs 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(ms);
-
-	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(ms)) {
-		auto over = _animation->a_thumbOver.current();
-		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 (loaded && !radial) {
-			return &(selected ? st::historyFileThumbPlaySelected : st::historyFileThumbPlay);
-		} else if (radial || _data->loading()) {
-			if (_parent->data()->id > 0 || _data->uploading()) {
-				return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel);
-			}
-			return nullptr;
-		}
-		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.) {
-			LOG(("INTERPOLATING: %1").arg(radialOpacity));
-			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 HistoryVideo::getStateGrouped(
-		const QRect &geometry,
-		QPoint point,
-		StateRequest request) const {
-	if (!geometry.contains(point)) {
-		return {};
-	}
-	return TextState(_parent, _data->uploading()
-		? _cancell
-		: _data->loaded()
-		? _openl
-		: _data->loading()
-		? _cancell
-		: _savel);
-}
-
-float64 HistoryVideo::dataProgress() const {
-	return _data->progress();
-}
-
-bool HistoryVideo::dataFinished() const {
-	return !_data->loading()
-		&& (!_data->uploading() || _data->waitingForAlbum());
-}
-
-bool HistoryVideo::dataLoaded() const {
-	return _data->loaded();
-}
-
-void HistoryVideo::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 image = useGood ? good : _data->thumb.get();
-	if (good && !useGood) {
-		good->load({});
-	}
-
-	const auto loaded = useGood ? true : _data->thumb->loaded();
-	const auto loadLevel = loaded ? 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 originalWidth = ConvertScale(_data->thumb->width());
-	const auto originalHeight = ConvertScale(_data->thumb->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->pixNoCache(_realParent->fullId(), pixWidth, pixHeight, options, width, height);
-}
-
-void HistoryVideo::setStatusSize(int newSize) const {
-	HistoryFileMedia::setStatusSize(newSize, _data->size, _data->duration(), 0);
-}
-
-TextWithEntities HistoryVideo::selectedText(TextSelection selection) const {
-	return _caption.originalTextWithEntities(selection, ExpandLinksAll);
-}
-
-bool HistoryVideo::needsBubble() const {
-	if (!_caption.isEmpty()) {
-		return true;
-	}
-	const auto item = _parent->data();
-	return item->viaBot()
-		|| item->Has<HistoryMessageReply>()
-		|| _parent->displayForwardedFrom()
-		|| _parent->displayFromName();
-	return false;
-}
-
-void HistoryVideo::parentTextUpdated() {
-	_caption = (_parent->media() == this)
-		? createCaption(_parent->data())
-		: Text();
-	Auth().data().requestViewResize(_parent);
-}
-
-void HistoryVideo::updateStatusText() const {
-	auto showPause = false;
-	auto statusSize = 0;
-	auto realDuration = 0;
-	if (_data->status == FileDownloadFailed || _data->status == FileUploadFailed) {
-		statusSize = FileStatusSizeFailed;
-	} else if (_data->uploading()) {
-		statusSize = _data->uploadingData->offset;
-	} else if (_data->loading()) {
-		statusSize = _data->loadOffset();
-	} else if (_data->loaded()) {
-		statusSize = FileStatusSizeLoaded;
-	} else {
-		statusSize = FileStatusSizeReady;
-	}
-	if (statusSize != _statusSize) {
-		setStatusSize(statusSize);
-	}
-}
-
-HistoryDocument::HistoryDocument(
-	not_null<Element*> parent,
-	not_null<DocumentData*> document)
-: HistoryFileMedia(parent, parent->data())
-, _data(document) {
-	const auto item = parent->data();
-	auto caption = createCaption(item);
-
-	createComponents(!caption.isEmpty());
-	if (auto named = Get<HistoryDocumentNamed>()) {
-		fillNamedFromData(named);
-	}
-
-	setDocumentLinks(_data, item);
-
-	setStatusSize(FileStatusSizeReady);
-
-	if (auto captioned = Get<HistoryDocumentCaptioned>()) {
-		captioned->_caption = std::move(caption);
-	}
-}
-
-float64 HistoryDocument::dataProgress() const {
-	return _data->progress();
-}
-
-bool HistoryDocument::dataFinished() const {
-	return !_data->loading() && !_data->uploading();
-}
-
-bool HistoryDocument::dataLoaded() const {
-	return _data->loaded();
-}
-
-void HistoryDocument::createComponents(bool caption) {
-	uint64 mask = 0;
-	if (_data->isVoiceMessage()) {
-		mask |= HistoryDocumentVoice::Bit();
-	} else {
-		mask |= HistoryDocumentNamed::Bit();
-		if (!_data->isSong()
-			&& !_data->thumb->isNull()
-			&& _data->thumb->width()
-			&& _data->thumb->height()
-			&& !Data::IsExecutableName(_data->filename())) {
-			mask |= HistoryDocumentThumbed::Bit();
-		}
-	}
-	if (caption) {
-		mask |= HistoryDocumentCaptioned::Bit();
-	}
-	UpdateComponents(mask);
-	if (auto thumbed = Get<HistoryDocumentThumbed>()) {
-		thumbed->_linksavel = std::make_shared<DocumentSaveClickHandler>(
-			_data,
-			_parent->data()->fullId());
-		thumbed->_linkcancell = std::make_shared<DocumentCancelClickHandler>(
-			_data,
-			_parent->data()->fullId());
-	}
-	if (auto voice = Get<HistoryDocumentVoice>()) {
-		voice->_seekl = std::make_shared<VoiceSeekClickHandler>(
-			_data,
-			_parent->data()->fullId());
-	}
-}
-
-void HistoryDocument::fillNamedFromData(HistoryDocumentNamed *named) {
-	auto nameString = named->_name = _data->composeNameString();
-	named->_namew = st::semiboldFont->width(nameString);
-}
-
-QSize HistoryDocument::countOptimalSize() {
-	const auto item = _parent->data();
-
-	auto captioned = Get<HistoryDocumentCaptioned>();
-	if (_parent->media() != this) {
-		if (captioned) {
-			RemoveComponents(HistoryDocumentCaptioned::Bit());
-			captioned = nullptr;
-		}
-	} else if (captioned && captioned->_caption.hasSkipBlock()) {
-		captioned->_caption.updateSkipBlock(
-			_parent->skipBlockWidth(),
-			_parent->skipBlockHeight());
-	}
-	auto thumbed = Get<HistoryDocumentThumbed>();
-	if (thumbed) {
-		_data->thumb->load(_realParent->fullId());
-		auto tw = ConvertScale(_data->thumb->width());
-		auto th = ConvertScale(_data->thumb->height());
-		if (tw > th) {
-			thumbed->_thumbw = (tw * st::msgFileThumbSize) / th;
-		} else {
-			thumbed->_thumbw = st::msgFileThumbSize;
-		}
-	}
-
-	auto maxWidth = st::msgFileMinWidth;
-
-	auto tleft = 0;
-	auto tright = 0;
-	if (thumbed) {
-		tleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right();
-		tright = st::msgFileThumbPadding.left();
-		accumulate_max(maxWidth, tleft + documentMaxStatusWidth(_data) + tright);
-	} else {
-		tleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right();
-		tright = st::msgFileThumbPadding.left();
-		auto unread = _data->isVoiceMessage() ? (st::mediaUnreadSkip + st::mediaUnreadSize) : 0;
-		accumulate_max(maxWidth, tleft + documentMaxStatusWidth(_data) + unread + _parent->skipBlockWidth() + st::msgPadding.right());
-	}
-
-	if (auto named = Get<HistoryDocumentNamed>()) {
-		accumulate_max(maxWidth, tleft + named->_namew + tright);
-		accumulate_min(maxWidth, st::msgMaxWidth);
-	}
-
-	auto minHeight = 0;
-	if (thumbed) {
-		minHeight = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom();
-	} else {
-		minHeight = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom();
-	}
-	if (!captioned && (item->Has<HistoryMessageSigned>()
-		|| item->Has<HistoryMessageViews>()
-		|| _parent->displayEditedBadge())) {
-		minHeight += st::msgDateFont->height - st::msgDateDelta.y();
-	}
-	if (!isBubbleTop()) {
-		minHeight -= st::msgFileTopMinus;
-	}
-
-	if (captioned) {
-		auto captionw = maxWidth
-			- st::msgPadding.left()
-			- st::msgPadding.right();
-		minHeight += captioned->_caption.countHeight(captionw);
-		if (isBubbleBottom()) {
-			minHeight += st::msgPadding.bottom();
-		}
-	}
-	return { maxWidth, minHeight };
-}
-
-QSize HistoryDocument::countCurrentSize(int newWidth) {
-	auto captioned = Get<HistoryDocumentCaptioned>();
-	if (!captioned) {
-		return HistoryFileMedia::countCurrentSize(newWidth);
-	}
-
-	accumulate_min(newWidth, maxWidth());
-	auto newHeight = 0;
-	if (Get<HistoryDocumentThumbed>()) {
-		newHeight = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom();
-	} else {
-		newHeight = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom();
-	}
-	if (!isBubbleTop()) {
-		newHeight -= st::msgFileTopMinus;
-	}
-	auto captionw = newWidth - st::msgPadding.left() - st::msgPadding.right();
-	newHeight += captioned->_caption.countHeight(captionw);
-	if (isBubbleBottom()) {
-		newHeight += st::msgPadding.bottom();
-	}
-
-	return { newWidth, newHeight };
-}
-
-void HistoryDocument::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const {
-	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
-
-	_data->automaticLoad(_realParent->fullId(), _parent->data());
-	bool loaded = _data->loaded(), displayLoading = _data->displayLoading();
-	bool selected = (selection == FullSelection);
-
-	int captionw = width() - st::msgPadding.left() - st::msgPadding.right();
-	auto outbg = _parent->hasOutLayout();
-
-	if (displayLoading) {
-		ensureAnimation();
-		if (!_animation->radial.animating()) {
-			_animation->radial.start(_data->progress());
-		}
-	}
-	bool showPause = updateStatusText();
-	bool radial = isRadialAnimation(ms);
-
-	auto topMinus = isBubbleTop() ? 0 : st::msgFileTopMinus;
-	int nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0, bottom = 0;
-	if (auto thumbed = Get<HistoryDocumentThumbed>()) {
-		nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right();
-		nametop = st::msgFileThumbNameTop - topMinus;
-		nameright = st::msgFileThumbPadding.left();
-		statustop = st::msgFileThumbStatusTop - topMinus;
-		linktop = st::msgFileThumbLinkTop - topMinus;
-		bottom = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom() - topMinus;
-
-		auto inWebPage = (_parent->media() != this);
-		auto roundRadius = inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large;
-		QRect rthumb(rtlrect(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top() - topMinus, st::msgFileThumbSize, st::msgFileThumbSize, width()));
-		QPixmap thumb;
-		if (loaded) {
-			thumb = _data->thumb->pixSingle(_realParent->fullId(), thumbed->_thumbw, 0, st::msgFileThumbSize, st::msgFileThumbSize, roundRadius);
-		} else {
-			thumb = _data->thumb->pixBlurredSingle(_realParent->fullId(), thumbed->_thumbw, 0, st::msgFileThumbSize, st::msgFileThumbSize, roundRadius);
-		}
-		p.drawPixmap(rthumb.topLeft(), thumb);
-		if (selected) {
-			auto overlayCorners = inWebPage ? SelectedOverlaySmallCorners : SelectedOverlayLargeCorners;
-			App::roundRect(p, rthumb, p.textPalette().selectOverlay, overlayCorners);
-		}
-
-		if (radial || (!loaded && !_data->loading())) {
-			float64 radialOpacity = (radial && loaded && !_data->uploading()) ? _animation->radial.opacity() : 1;
-			QRect inner(rthumb.x() + (rthumb.width() - st::msgFileSize) / 2, rthumb.y() + (rthumb.height() - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize);
-			p.setPen(Qt::NoPen);
-			if (selected) {
-				p.setBrush(st::msgDateImgBgSelected);
-			} else if (isThumbAnimation(ms)) {
-				auto over = _animation->a_thumbOver.current();
-				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(radialOpacity * p.opacity());
-
-			{
-				PainterHighQualityEnabler hq(p);
-				p.drawEllipse(inner);
-			}
-
-			p.setOpacity(radialOpacity);
-			auto icon = ([radial, this, selected] {
-				if (radial || _data->loading()) {
-					return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel);
-				}
-				return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload);
-			})();
-			p.setOpacity((radial && loaded) ? _animation->radial.opacity() : 1);
-			icon->paintInCenter(p, inner);
-			if (radial) {
-				p.setOpacity(1);
-
-				QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine)));
-				_animation->radial.draw(p, rinner, st::msgFileRadialLine, selected ? st::historyFileThumbRadialFgSelected : st::historyFileThumbRadialFg);
-			}
-		}
-
-		if (_data->status != FileUploadFailed) {
-			const auto &lnk = (_data->loading() || _data->uploading())
-				? thumbed->_linkcancell
-				: thumbed->_linksavel;
-			bool over = ClickHandler::showAsActive(lnk);
-			p.setFont(over ? st::semiboldFont->underline() : st::semiboldFont);
-			p.setPen(outbg ? (selected ? st::msgFileThumbLinkOutFgSelected : st::msgFileThumbLinkOutFg) : (selected ? st::msgFileThumbLinkInFgSelected : st::msgFileThumbLinkInFg));
-			p.drawTextLeft(nameleft, linktop, width(), thumbed->_link, thumbed->_linkw);
-		}
-	} else {
-		nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right();
-		nametop = st::msgFileNameTop - topMinus;
-		nameright = st::msgFilePadding.left();
-		statustop = st::msgFileStatusTop - topMinus;
-		bottom = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom() - topMinus;
-
-		QRect inner(rtlrect(st::msgFilePadding.left(), st::msgFilePadding.top() - topMinus, st::msgFileSize, st::msgFileSize, width()));
-		p.setPen(Qt::NoPen);
-		if (selected) {
-			p.setBrush(outbg ? st::msgFileOutBgSelected : st::msgFileInBgSelected);
-		} else if (isThumbAnimation(ms)) {
-			auto over = _animation->a_thumbOver.current();
-			p.setBrush(anim::brush(outbg ? st::msgFileOutBg : st::msgFileInBg, outbg ? st::msgFileOutBgOver : st::msgFileInBgOver, over));
-		} else {
-			auto over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel);
-			p.setBrush(outbg ? (over ? st::msgFileOutBgOver : st::msgFileOutBg) : (over ? st::msgFileInBgOver : st::msgFileInBg));
-		}
-
-		{
-			PainterHighQualityEnabler hq(p);
-			p.drawEllipse(inner);
-		}
-
-		if (radial) {
-			QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine)));
-			auto fg = outbg ? (selected ? st::historyFileOutRadialFgSelected : st::historyFileOutRadialFg) : (selected ? st::historyFileInRadialFgSelected : st::historyFileInRadialFg);
-			_animation->radial.draw(p, rinner, st::msgFileRadialLine, fg);
-		}
-
-		auto icon = ([showPause, radial, this, loaded, outbg, selected] {
-			if (showPause) {
-				return &(outbg ? (selected ? st::historyFileOutPauseSelected : st::historyFileOutPause) : (selected ? st::historyFileInPauseSelected : st::historyFileInPause));
-			} else if (radial || _data->loading()) {
-				return &(outbg ? (selected ? st::historyFileOutCancelSelected : st::historyFileOutCancel) : (selected ? st::historyFileInCancelSelected : st::historyFileInCancel));
-			} else if (loaded) {
-				if (_data->isAudioFile() || _data->isVoiceMessage()) {
-					return &(outbg ? (selected ? st::historyFileOutPlaySelected : st::historyFileOutPlay) : (selected ? st::historyFileInPlaySelected : st::historyFileInPlay));
-				} else if (_data->isImage()) {
-					return &(outbg ? (selected ? st::historyFileOutImageSelected : st::historyFileOutImage) : (selected ? st::historyFileInImageSelected : st::historyFileInImage));
-				}
-				return &(outbg ? (selected ? st::historyFileOutDocumentSelected : st::historyFileOutDocument) : (selected ? st::historyFileInDocumentSelected : st::historyFileInDocument));
-			}
-			return &(outbg ? (selected ? st::historyFileOutDownloadSelected : st::historyFileOutDownload) : (selected ? st::historyFileInDownloadSelected : st::historyFileInDownload));
-		})();
-		icon->paintInCenter(p, inner);
-	}
-	auto namewidth = width() - nameleft - nameright;
-	auto statuswidth = namewidth;
-
-	auto voiceStatusOverride = QString();
-	if (auto voice = Get<HistoryDocumentVoice>()) {
-		const VoiceWaveform *wf = nullptr;
-		uchar norm_value = 0;
-		if (const auto voiceData = _data->voice()) {
-			wf = &voiceData->waveform;
-			if (wf->isEmpty()) {
-				wf = nullptr;
-				if (loaded) {
-					Local::countVoiceWaveform(_data);
-				}
-			} else if (wf->at(0) < 0) {
-				wf = nullptr;
-			} else {
-				norm_value = voiceData->wavemax;
-			}
-		}
-		auto progress = ([voice] {
-			if (voice->seeking()) {
-				return voice->seekingCurrent();
-			} else if (voice->_playback) {
-				return voice->_playback->a_progress.current();
-			}
-			return 0.;
-		})();
-		if (voice->seeking()) {
-			voiceStatusOverride = formatPlayedText(qRound(progress * voice->_lastDurationMs) / 1000, voice->_lastDurationMs / 1000);
-		}
-
-		// rescale waveform by going in waveform.size * bar_count 1D grid
-		auto active = outbg ? (selected ? st::msgWaveformOutActiveSelected : st::msgWaveformOutActive) : (selected ? st::msgWaveformInActiveSelected : st::msgWaveformInActive);
-		auto inactive = outbg ? (selected ? st::msgWaveformOutInactiveSelected : st::msgWaveformOutInactive) : (selected ? st::msgWaveformInInactiveSelected : st::msgWaveformInInactive);
-		auto wf_size = wf ? wf->size() : Media::Player::kWaveformSamplesCount;
-		auto availw = namewidth + st::msgWaveformSkip;
-		auto activew = qRound(availw * progress);
-		if (!outbg && !voice->_playback && _parent->data()->isMediaUnread()) {
-			activew = availw;
-		}
-		auto bar_count = qMin(availw / (st::msgWaveformBar + st::msgWaveformSkip), wf_size);
-		auto max_value = 0;
-		auto max_delta = st::msgWaveformMax - st::msgWaveformMin;
-		auto bottom = st::msgFilePadding.top() - topMinus + st::msgWaveformMax;
-		p.setPen(Qt::NoPen);
-		for (auto i = 0, bar_x = 0, sum_i = 0; i < wf_size; ++i) {
-			auto value = wf ? wf->at(i) : 0;
-			if (sum_i + bar_count >= wf_size) { // draw bar
-				sum_i = sum_i + bar_count - wf_size;
-				if (sum_i < (bar_count + 1) / 2) {
-					if (max_value < value) max_value = value;
-				}
-				auto bar_value = ((max_value * max_delta) + ((norm_value + 1) / 2)) / (norm_value + 1);
-
-				if (bar_x >= activew) {
-					p.fillRect(nameleft + bar_x, bottom - bar_value, st::msgWaveformBar, st::msgWaveformMin + bar_value, inactive);
-				} else if (bar_x + st::msgWaveformBar <= activew) {
-					p.fillRect(nameleft + bar_x, bottom - bar_value, st::msgWaveformBar, st::msgWaveformMin + bar_value, active);
-				} else {
-					p.fillRect(nameleft + bar_x, bottom - bar_value, activew - bar_x, st::msgWaveformMin + bar_value, active);
-					p.fillRect(nameleft + activew, bottom - bar_value, st::msgWaveformBar - (activew - bar_x), st::msgWaveformMin + bar_value, inactive);
-				}
-				bar_x += st::msgWaveformBar + st::msgWaveformSkip;
-
-				if (sum_i < (bar_count + 1) / 2) {
-					max_value = 0;
-				} else {
-					max_value = value;
-				}
-			} else {
-				if (max_value < value) max_value = value;
-
-				sum_i += bar_count;
-			}
-		}
-	} else if (auto named = Get<HistoryDocumentNamed>()) {
-		p.setFont(st::semiboldFont);
-		p.setPen(outbg ? (selected ? st::historyFileNameOutFgSelected : st::historyFileNameOutFg) : (selected ? st::historyFileNameInFgSelected : st::historyFileNameInFg));
-		if (namewidth < named->_namew) {
-			p.drawTextLeft(nameleft, nametop, width(), st::semiboldFont->elided(named->_name, namewidth, Qt::ElideMiddle));
-		} else {
-			p.drawTextLeft(nameleft, nametop, width(), named->_name, named->_namew);
-		}
-	}
-
-	auto statusText = voiceStatusOverride.isEmpty() ? _statusText : voiceStatusOverride;
-	auto status = outbg ? (selected ? st::mediaOutFgSelected : st::mediaOutFg) : (selected ? st::mediaInFgSelected : st::mediaInFg);
-	p.setFont(st::normalFont);
-	p.setPen(status);
-	p.drawTextLeft(nameleft, statustop, width(), statusText);
-
-	if (_parent->data()->isMediaUnread()) {
-		auto w = st::normalFont->width(statusText);
-		if (w + st::mediaUnreadSkip + st::mediaUnreadSize <= statuswidth) {
-			p.setPen(Qt::NoPen);
-			p.setBrush(outbg ? (selected ? st::msgFileOutBgSelected : st::msgFileOutBg) : (selected ? st::msgFileInBgSelected : st::msgFileInBg));
-
-			{
-				PainterHighQualityEnabler hq(p);
-				p.drawEllipse(rtlrect(nameleft + w + st::mediaUnreadSkip, statustop + st::mediaUnreadTop, st::mediaUnreadSize, st::mediaUnreadSize, width()));
-			}
-		}
-	}
-
-	if (auto captioned = Get<HistoryDocumentCaptioned>()) {
-		p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg));
-		captioned->_caption.draw(p, st::msgPadding.left(), bottom, captionw, style::al_left, 0, -1, selection);
-	}
-}
-
-TextState HistoryDocument::textState(QPoint point, StateRequest request) const {
-	auto result = TextState(_parent);
-
-	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) {
-		return result;
-	}
-
-	bool loaded = _data->loaded();
-
-	bool showPause = updateStatusText();
-
-	auto nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0, bottom = 0;
-	auto topMinus = isBubbleTop() ? 0 : st::msgFileTopMinus;
-	if (auto thumbed = Get<HistoryDocumentThumbed>()) {
-		nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right();
-		nameright = st::msgFileThumbPadding.left();
-		nametop = st::msgFileThumbNameTop - topMinus;
-		linktop = st::msgFileThumbLinkTop - topMinus;
-		bottom = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom() - topMinus;
-
-		QRect rthumb(rtlrect(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top() - topMinus, st::msgFileThumbSize, st::msgFileThumbSize, width()));
-
-		if ((_data->loading() || _data->uploading() || !loaded) && rthumb.contains(point)) {
-			result.link = (_data->loading() || _data->uploading()) ? _cancell : _savel;
-			return result;
-		}
-
-		if (_data->status != FileUploadFailed) {
-			if (rtlrect(nameleft, linktop, thumbed->_linkw, st::semiboldFont->height, width()).contains(point)) {
-				result.link = (_data->loading() || _data->uploading())
-					? thumbed->_linkcancell
-					: thumbed->_linksavel;
-				return result;
-			}
-		}
-	} else {
-		nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right();
-		nameright = st::msgFilePadding.left();
-		nametop = st::msgFileNameTop - topMinus;
-		bottom = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom() - topMinus;
-
-		QRect inner(rtlrect(st::msgFilePadding.left(), st::msgFilePadding.top() - topMinus, st::msgFileSize, st::msgFileSize, width()));
-		if ((_data->loading() || _data->uploading() || !loaded) && inner.contains(point)) {
-			result.link = (_data->loading() || _data->uploading()) ? _cancell : _savel;
-			return result;
-		}
-	}
-
-	if (auto voice = Get<HistoryDocumentVoice>()) {
-		auto namewidth = width() - nameleft - nameright;
-		auto waveformbottom = st::msgFilePadding.top() - topMinus + st::msgWaveformMax + st::msgWaveformMin;
-		if (QRect(nameleft, nametop, namewidth, waveformbottom - nametop).contains(point)) {
-			auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice);
-			if (state.id == AudioMsgId(_data, _parent->data()->fullId())
-				&& !Media::Player::IsStoppedOrStopping(state.state)) {
-				if (!voice->seeking()) {
-					voice->setSeekingStart((point.x() - nameleft) / float64(namewidth));
-				}
-				result.link = voice->_seekl;
-				return result;
-			}
-		}
-	}
-
-	auto painth = height();
-	if (auto captioned = Get<HistoryDocumentCaptioned>()) {
-		if (point.y() >= bottom) {
-			result = TextState(_parent, captioned->_caption.getState(
-				point - QPoint(st::msgPadding.left(), bottom),
-				width() - st::msgPadding.left() - st::msgPadding.right(),
-				request.forText()));
-			return result;
-		}
-		auto captionw = width() - st::msgPadding.left() - st::msgPadding.right();
-		painth -= captioned->_caption.countHeight(captionw);
-		if (isBubbleBottom()) {
-			painth -= st::msgPadding.bottom();
-		}
-	}
-	if (QRect(0, 0, width(), painth).contains(point) && !_data->loading() && !_data->uploading() && _data->isValid()) {
-		result.link = _openl;
-		return result;
-	}
-	return result;
-}
-
-void HistoryDocument::updatePressed(QPoint point) {
-	if (auto voice = Get<HistoryDocumentVoice>()) {
-		if (voice->seeking()) {
-			auto nameleft = 0, nameright = 0;
-			if (auto thumbed = Get<HistoryDocumentThumbed>()) {
-				nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right();
-				nameright = st::msgFileThumbPadding.left();
-			} else {
-				nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right();
-				nameright = st::msgFilePadding.left();
-			}
-			voice->setSeekingCurrent(snap((point.x() - nameleft) / float64(width() - nameleft - nameright), 0., 1.));
-			Auth().data().requestViewRepaint(_parent);
-		}
-	}
-}
-
-TextSelection HistoryDocument::adjustSelection(
-		TextSelection selection,
-		TextSelectType type) const {
-	if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
-		return captioned->_caption.adjustSelection(selection, type);
-	}
-	return selection;
-}
-
-uint16 HistoryDocument::fullSelectionLength() const {
-	if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
-		return captioned->_caption.length();
-	}
-	return 0;
-}
-
-bool HistoryDocument::hasTextForCopy() const {
-	return Has<HistoryDocumentCaptioned>();
-}
-
-TextWithEntities HistoryDocument::selectedText(TextSelection selection) const {
-	if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
-		const auto &caption = captioned->_caption;
-		return caption.originalTextWithEntities(selection, ExpandLinksAll);
-	}
-	return TextWithEntities();
-}
-
-void HistoryDocument::setStatusSize(int newSize, qint64 realDuration) const {
-	auto duration = _data->isSong()
-		? _data->song()->duration
-		: (_data->isVoiceMessage()
-			? _data->voice()->duration
-			: -1);
-	HistoryFileMedia::setStatusSize(newSize, _data->size, duration, realDuration);
-	if (auto thumbed = Get<HistoryDocumentThumbed>()) {
-		if (_statusSize == FileStatusSizeReady) {
-			thumbed->_link = lang(lng_media_download).toUpper();
-		} else if (_statusSize == FileStatusSizeLoaded) {
-			thumbed->_link = lang(lng_media_open_with).toUpper();
-		} else if (_statusSize == FileStatusSizeFailed) {
-			thumbed->_link = lang(lng_media_download).toUpper();
-		} else if (_statusSize >= 0) {
-			thumbed->_link = lang(lng_media_cancel).toUpper();
-		} else {
-			thumbed->_link = lang(lng_media_open_with).toUpper();
-		}
-		thumbed->_linkw = st::semiboldFont->width(thumbed->_link);
-	}
-}
-
-bool HistoryDocument::updateStatusText() const {
-	auto showPause = false;
-	auto statusSize = 0;
-	auto realDuration = 0;
-	if (_data->status == FileDownloadFailed || _data->status == FileUploadFailed) {
-		statusSize = FileStatusSizeFailed;
-	} else if (_data->uploading()) {
-		statusSize = _data->uploadingData->offset;
-	} else if (_data->loading()) {
-		statusSize = _data->loadOffset();
-	} else if (_data->loaded()) {
-		using State = Media::Player::State;
-		statusSize = FileStatusSizeLoaded;
-		if (_data->isVoiceMessage()) {
-			auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice);
-			if (state.id == AudioMsgId(_data, _parent->data()->fullId())
-				&& !Media::Player::IsStoppedOrStopping(state.state)) {
-				if (auto voice = Get<HistoryDocumentVoice>()) {
-					bool was = (voice->_playback != nullptr);
-					voice->ensurePlayback(this);
-					if (!was || state.position != voice->_playback->_position) {
-						auto prg = state.length ? snap(float64(state.position) / state.length, 0., 1.) : 0.;
-						if (voice->_playback->_position < state.position) {
-							voice->_playback->a_progress.start(prg);
-						} else {
-							voice->_playback->a_progress = anim::value(0., prg);
-						}
-						voice->_playback->_position = state.position;
-						voice->_playback->_a_progress.start();
-					}
-					voice->_lastDurationMs = static_cast<int>((state.length * 1000LL) / state.frequency); // Bad :(
-				}
-
-				statusSize = -1 - (state.position / state.frequency);
-				realDuration = (state.length / state.frequency);
-				showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
-			} else {
-				if (auto voice = Get<HistoryDocumentVoice>()) {
-					voice->checkPlaybackFinished();
-				}
-			}
-			if (!showPause && (state.id == AudioMsgId(_data, _parent->data()->fullId()))) {
-				showPause = Media::Player::instance()->isSeeking(AudioMsgId::Type::Voice);
-			}
-		} else if (_data->isAudioFile()) {
-			auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song);
-			if (state.id == AudioMsgId(_data, _parent->data()->fullId())
-				&& !Media::Player::IsStoppedOrStopping(state.state)) {
-				statusSize = -1 - (state.position / state.frequency);
-				realDuration = (state.length / state.frequency);
-				showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
-			} else {
-			}
-			if (!showPause && (state.id == AudioMsgId(_data, _parent->data()->fullId()))) {
-				showPause = Media::Player::instance()->isSeeking(AudioMsgId::Type::Song);
-			}
-		}
-	} else {
-		statusSize = FileStatusSizeReady;
-	}
-	if (statusSize != _statusSize) {
-		setStatusSize(statusSize, realDuration);
-	}
-	return showPause;
-}
-
-QMargins HistoryDocument::bubbleMargins() const {
-	return Get<HistoryDocumentThumbed>() ? QMargins(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top(), st::msgFileThumbPadding.left(), st::msgFileThumbPadding.bottom()) : st::msgPadding;
-}
-
-void HistoryDocument::step_voiceProgress(float64 ms, bool timer) {
-	if (anim::Disabled()) {
-		ms += (2 * AudioVoiceMsgUpdateView);
-	}
-	if (auto voice = Get<HistoryDocumentVoice>()) {
-		if (voice->_playback) {
-			float64 dt = ms / (2 * AudioVoiceMsgUpdateView);
-			if (dt >= 1) {
-				voice->_playback->_a_progress.stop();
-				voice->_playback->a_progress.finish();
-			} else {
-				voice->_playback->a_progress.update(qMin(dt, 1.), anim::linear);
-			}
-			if (timer) {
-				Auth().data().requestViewRepaint(_parent);
-			}
-		}
-	}
-}
-
-void HistoryDocument::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) {
-	if (auto voice = Get<HistoryDocumentVoice>()) {
-		if (pressed && p == voice->_seekl && !voice->seeking()) {
-			voice->startSeeking();
-		} else if (!pressed && voice->seeking()) {
-			auto type = AudioMsgId::Type::Voice;
-			auto state = Media::Player::mixer()->currentState(type);
-			if (state.id == AudioMsgId(_data, _parent->data()->fullId()) && state.length) {
-				auto currentProgress = voice->seekingCurrent();
-				auto currentPosition = state.frequency
-					? qRound(currentProgress * state.length * 1000. / state.frequency)
-					: 0;
-				Media::Player::mixer()->seek(type, currentPosition);
-
-				voice->ensurePlayback(this);
-				voice->_playback->_position = 0;
-				voice->_playback->a_progress = anim::value(currentProgress, currentProgress);
-			}
-			voice->stopSeeking();
-		}
-	}
-	HistoryFileMedia::clickHandlerPressedChanged(p, pressed);
-}
-
-void HistoryDocument::refreshParentId(not_null<HistoryItem*> realParent) {
-	HistoryFileMedia::refreshParentId(realParent);
-
-	const auto fullId = realParent->fullId();
-	if (auto thumbed = Get<HistoryDocumentThumbed>()) {
-		if (thumbed->_linksavel) {
-			thumbed->_linksavel->setMessageId(fullId);
-			thumbed->_linkcancell->setMessageId(fullId);
-		}
-	}
-	if (auto voice = Get<HistoryDocumentVoice>()) {
-		if (voice->_seekl) {
-			voice->_seekl->setMessageId(fullId);
-		}
-	}
-}
-
-void HistoryDocument::parentTextUpdated() {
-	auto caption = (_parent->media() == this)
-		? createCaption(_parent->data())
-		: Text();
-	if (!caption.isEmpty()) {
-		AddComponents(HistoryDocumentCaptioned::Bit());
-		auto captioned = Get<HistoryDocumentCaptioned>();
-		captioned->_caption = std::move(caption);
-	} else {
-		RemoveComponents(HistoryDocumentCaptioned::Bit());
-	}
-	Auth().data().requestViewResize(_parent);
-}
-
-TextWithEntities HistoryDocument::getCaption() const {
-	if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
-		return captioned->_caption.originalTextWithEntities();
-	}
-	return TextWithEntities();
-}
-
-HistoryGif::HistoryGif(
-	not_null<Element*> parent,
-	not_null<DocumentData*> document)
-: HistoryFileMedia(parent, parent->data())
-, _data(document)
-, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) {
-	const auto item = parent->data();
-	setDocumentLinks(_data, item, true);
-
-	setStatusSize(FileStatusSizeReady);
-
-	_caption = createCaption(item);
-	_data->thumb->load(item->fullId());
-}
-
-QSize HistoryGif::countOptimalSize() {
-	if (_parent->media() != this) {
-		_caption = Text();
-	} else if (_caption.hasSkipBlock()) {
-		_caption.updateSkipBlock(
-			_parent->skipBlockWidth(),
-			_parent->skipBlockHeight());
-	}
-	if (!_openInMediaviewLink) {
-		_openInMediaviewLink = std::make_shared<DocumentOpenClickHandler>(
-			_data,
-			_parent->data()->fullId());
-	}
-
-	auto tw = 0;
-	auto th = 0;
-	if (_gif && _gif->state() == Media::Clip::State::Error) {
-		if (!_gif->autoplay()) {
-			Ui::show(Box<InformBox>(lang(lng_gif_error)));
-		}
-		setClipReader(Media::Clip::ReaderPointer::Bad());
-	}
-
-	const auto reader = currentReader();
-	if (reader) {
-		tw = ConvertScale(reader->width());
-		th = ConvertScale(reader->height());
-	} else {
-		tw = ConvertScale(_data->dimensions.width()), th = ConvertScale(_data->dimensions.height());
-		if (!tw || !th) {
-			tw = ConvertScale(_data->thumb->width());
-			th = ConvertScale(_data->thumb->height());
-		}
-	}
-	const auto maxSize = _data->isVideoMessage()
-		? st::maxVideoMessageSize
-		: st::maxGifSize;
-	if (tw > maxSize) {
-		th = (maxSize * th) / tw;
-		tw = maxSize;
-	}
-	if (th > maxSize) {
-		tw = (maxSize * tw) / th;
-		th = maxSize;
-	}
-	if (!tw || !th) {
-		tw = th = 1;
-	}
-	_thumbw = tw;
-	_thumbh = th;
-	auto maxWidth = qMax(tw, st::minPhotoSize);
-	auto minHeight = qMax(th, st::minPhotoSize);
-	accumulate_max(maxWidth, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x()));
-	if (!reader) {
-		accumulate_max(maxWidth, gifMaxStatusWidth(_data) + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x()));
-	}
-	if (_parent->hasBubble()) {
-		if (!_caption.isEmpty()) {
-			auto captionw = maxWidth - st::msgPadding.left() - st::msgPadding.right();
-			minHeight += st::mediaCaptionSkip + _caption.countHeight(captionw);
-			if (isBubbleBottom()) {
-				minHeight += st::msgPadding.bottom();
-			}
-		}
-	} else if (isSeparateRoundVideo()) {
-		const auto item = _parent->data();
-		auto via = item->Get<HistoryMessageVia>();
-		auto reply = item->Get<HistoryMessageReply>();
-		auto forwarded = item->Get<HistoryMessageForwarded>();
-		if (forwarded) {
-			forwarded->create(via);
-		}
-		maxWidth += additionalWidth(via, reply, forwarded);
-	}
-	return { maxWidth, minHeight };
-}
-
-QSize HistoryGif::countCurrentSize(int newWidth) {
-	auto availableWidth = newWidth;
-
-	int tw = 0, th = 0;
-	const auto reader = currentReader();
-	if (reader) {
-		tw = ConvertScale(reader->width());
-		th = ConvertScale(reader->height());
-	} else {
-		tw = ConvertScale(_data->dimensions.width()), th = ConvertScale(_data->dimensions.height());
-		if (!tw || !th) {
-			tw = ConvertScale(_data->thumb->width());
-			th = ConvertScale(_data->thumb->height());
-		}
-	}
-	const auto maxSize = _data->isVideoMessage()
-		? st::maxVideoMessageSize
-		: st::maxGifSize;
-	if (tw > maxSize) {
-		th = (maxSize * th) / tw;
-		tw = maxSize;
-	}
-	if (th > maxSize) {
-		tw = (maxSize * tw) / th;
-		th = maxSize;
-	}
-	if (!tw || !th) {
-		tw = th = 1;
-	}
-
-	if (newWidth < tw) {
-		th = qRound((newWidth / float64(tw)) * th);
-		tw = newWidth;
-	}
-	_thumbw = tw;
-	_thumbh = th;
-
-	newWidth = qMax(tw, st::minPhotoSize);
-	auto newHeight = qMax(th, st::minPhotoSize);
-	accumulate_max(newWidth, _parent->infoWidth() + 2 * st::msgDateImgDelta + st::msgDateImgPadding.x());
-	if (reader) {
-		const auto own = (reader->mode() == Media::Clip::Reader::Mode::Gif);
-		if (own && !reader->started()) {
-			auto isRound = _data->isVideoMessage();
-			auto inWebPage = (_parent->media() != this);
-			auto roundRadius = isRound
-				? ImageRoundRadius::Ellipse
-				: inWebPage
-				? ImageRoundRadius::Small
-				: ImageRoundRadius::Large;
-			auto roundCorners = (isRound || inWebPage)
-				? RectPart::AllCorners
-				: ((isBubbleTop()
-					? (RectPart::TopLeft | RectPart::TopRight)
-					: RectPart::None)
-				| ((isBubbleBottom() && _caption.isEmpty())
-					? (RectPart::BottomLeft | RectPart::BottomRight)
-					: RectPart::None));
-			reader->start(
-				_thumbw,
-				_thumbh,
-				newWidth,
-				newHeight,
-				roundRadius,
-				roundCorners);
-		}
-	} else {
-		accumulate_max(newWidth, gifMaxStatusWidth(_data) + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x()));
-	}
-	if (_parent->hasBubble()) {
-		if (!_caption.isEmpty()) {
-			auto captionw = newWidth - st::msgPadding.left() - st::msgPadding.right();
-			newHeight += st::mediaCaptionSkip + _caption.countHeight(captionw);
-			if (isBubbleBottom()) {
-				newHeight += st::msgPadding.bottom();
-			}
-		}
-	} else if (isSeparateRoundVideo()) {
-		const auto item = _parent->data();
-		auto via = item->Get<HistoryMessageVia>();
-		auto reply = item->Get<HistoryMessageReply>();
-		auto forwarded = item->Get<HistoryMessageForwarded>();
-		if (via || reply || forwarded) {
-			auto additional = additionalWidth(via, reply, forwarded);
-			newWidth += additional;
-			accumulate_min(newWidth, availableWidth);
-			auto usew = maxWidth() - additional;
-			auto availw = newWidth - usew - st::msgReplyPadding.left() - st::msgReplyPadding.left() - st::msgReplyPadding.left();
-			if (!forwarded && via) {
-				via->resize(availw);
-			}
-			if (reply) {
-				reply->resize(availw);
-			}
-		}
-	}
-
-	return { newWidth, newHeight };
-}
-
-void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const {
-	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
-
-	const auto item = _parent->data();
-	_data->automaticLoad(_realParent->fullId(), item);
-	auto loaded = _data->loaded();
-	auto displayLoading = (item->id < 0) || _data->displayLoading();
-	auto selected = (selection == FullSelection);
-
-	if (loaded
-		&& cAutoPlayGif()
-		&& !_gif
-		&& !_gif.isBad()
-		&& !activeRoundVideo()) {
-		_parent->delegate()->elementAnimationAutoplayAsync(_parent);
-	}
-
-	auto paintx = 0, painty = 0, paintw = width(), painth = height();
-	bool bubble = _parent->hasBubble();
-	auto outbg = _parent->hasOutLayout();
-	auto inWebPage = (_parent->media() != this);
-
-	auto captionw = paintw - st::msgPadding.left() - st::msgPadding.right();
-
-	auto isRound = _data->isVideoMessage();
-	auto displayMute = false;
-	const auto reader = currentReader();
-	const auto playingVideo = reader
-		? (reader->mode() == Media::Clip::Reader::Mode::Video)
-		: false;
-	const auto animating = reader && reader->started();
-
-	if ((!animating || item->id < 0) && displayLoading) {
-		ensureAnimation();
-		if (!_animation->radial.animating()) {
-			_animation->radial.start(dataProgress());
-		}
-	}
-	updateStatusText();
-	auto radial = isRadialAnimation(ms);
-
-	if (bubble) {
-		if (!_caption.isEmpty()) {
-			painth -= st::mediaCaptionSkip + _caption.countHeight(captionw);
-			if (isBubbleBottom()) {
-				painth -= st::msgPadding.bottom();
-			}
-		}
-	} else if (!isRound) {
-		App::roundShadow(p, 0, 0, paintw, height(), selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners);
-	}
-
-	auto usex = 0, usew = paintw;
-	auto separateRoundVideo = isSeparateRoundVideo();
-	auto via = separateRoundVideo ? item->Get<HistoryMessageVia>() : nullptr;
-	auto reply = separateRoundVideo ? item->Get<HistoryMessageReply>() : nullptr;
-	auto forwarded = separateRoundVideo ? item->Get<HistoryMessageForwarded>() : nullptr;
-	if (via || reply || forwarded) {
-		usew = maxWidth() - additionalWidth(via, reply, forwarded);
-		if (outbg) {
-			usex = width() - usew;
-		}
-	}
-	if (rtl()) usex = width() - usex - usew;
-
-	QRect rthumb(rtlrect(usex + paintx, painty, usew, painth, width()));
-
-	auto roundRadius = isRound ? ImageRoundRadius::Ellipse : inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large;
-	auto roundCorners = (isRound || inWebPage) ? RectPart::AllCorners : ((isBubbleTop() ? (RectPart::TopLeft | RectPart::TopRight) : RectPart::None)
-		| ((isBubbleBottom() && _caption.isEmpty()) ? (RectPart::BottomLeft | RectPart::BottomRight) : RectPart::None));
-	if (animating) {
-		auto paused = App::wnd()->controller()->isGifPausedAtLeastFor(Window::GifPauseReason::Any);
-		if (isRound) {
-			if (playingVideo) {
-				paused = false;
-			} else {
-				displayMute = true;
-			}
-		}
-		p.drawPixmap(rthumb.topLeft(), reader->current(_thumbw, _thumbh, usew, painth, roundRadius, roundCorners, paused ? 0 : ms));
-
-		if (const auto playback = videoPlayback()) {
-			const auto value = playback->value(ms);
-			if (value > 0.) {
-				auto pen = st::historyVideoMessageProgressFg->p;
-				auto was = p.pen();
-				pen.setWidth(st::radialLine);
-				pen.setCapStyle(Qt::RoundCap);
-				p.setPen(pen);
-				p.setOpacity(st::historyVideoMessageProgressOpacity);
-
-				auto from = QuarterArcLength;
-				auto len = -qRound(FullArcLength * value);
-				auto stepInside = st::radialLine / 2;
-				{
-					PainterHighQualityEnabler hq(p);
-					p.drawArc(rthumb.marginsRemoved(QMargins(stepInside, stepInside, stepInside, stepInside)), from, len);
-				}
-
-				p.setPen(was);
-				p.setOpacity(1.);
-			}
-		}
-	} else {
-		const auto good = _data->goodThumbnail();
-		if (good && good->loaded()) {
-			p.drawPixmap(rthumb.topLeft(), good->pixSingle({}, _thumbw, _thumbh, usew, painth, roundRadius, roundCorners));
-		} else {
-			if (good) {
-				good->load({});
-			}
-			p.drawPixmap(rthumb.topLeft(), _data->thumb->pixBlurredSingle(_realParent->fullId(), _thumbw, _thumbh, usew, painth, roundRadius, roundCorners));
-		}
-	}
-
-	if (selected) {
-		App::complexOverlayRect(p, rthumb, roundRadius, roundCorners);
-	}
-
-	if (radial || (!reader && (_gif.isBad() || (!loaded && !_data->loading()) || !cAutoPlayGif()))) {
-		auto radialOpacity = (radial && loaded && item->id > 0) ? _animation->radial.opacity() : 1.;
-		auto inner = QRect(rthumb.x() + (rthumb.width() - st::msgFileSize) / 2, rthumb.y() + (rthumb.height() - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize);
-		p.setPen(Qt::NoPen);
-		if (selected) {
-			p.setBrush(st::msgDateImgBgSelected);
-		} else if (isThumbAnimation(ms)) {
-			auto over = _animation->a_thumbOver.current();
-			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(radialOpacity * p.opacity());
-
-		{
-			PainterHighQualityEnabler hq(p);
-			p.drawEllipse(inner);
-		}
-
-		p.setOpacity(radialOpacity);
-		auto icon = [&]() -> const style::icon * {
-			if (_data->loaded() && !radial) {
-				return &(selected ? st::historyFileThumbPlaySelected : st::historyFileThumbPlay);
-			} else if (radial || _data->loading()) {
-				if (item->id > 0 || _data->uploading()) {
-					return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel);
-				}
-				return nullptr;
-			}
-			return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload);
-		}();
-		if (icon) {
-			icon->paintInCenter(p, inner);
-		}
-		if (radial) {
-			p.setOpacity(1);
-			QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine)));
-			_animation->radial.draw(p, rinner, st::msgFileRadialLine, selected ? st::historyFileThumbRadialFgSelected : st::historyFileThumbRadialFg);
-		}
-
-		if (!isRound && (!animating || item->id < 0)) {
-			auto statusX = paintx + st::msgDateImgDelta + st::msgDateImgPadding.x();
-			auto statusY = painty + st::msgDateImgDelta + st::msgDateImgPadding.y();
-			auto statusW = st::normalFont->width(_statusText) + 2 * st::msgDateImgPadding.x();
-			auto statusH = st::normalFont->height + 2 * st::msgDateImgPadding.y();
-			App::roundRect(p, rtlrect(statusX - st::msgDateImgPadding.x(), statusY - st::msgDateImgPadding.y(), statusW, statusH, width()), selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners);
-			p.setFont(st::normalFont);
-			p.setPen(st::msgDateImgFg);
-			p.drawTextLeft(statusX, statusY, width(), _statusText, statusW - 2 * st::msgDateImgPadding.x());
-		}
-	}
-	if (displayMute) {
-		auto muteRect = rtlrect(rthumb.x() + (rthumb.width() - st::historyVideoMessageMuteSize) / 2, rthumb.y() + st::msgDateImgDelta, st::historyVideoMessageMuteSize, st::historyVideoMessageMuteSize, width());
-		p.setPen(Qt::NoPen);
-		p.setBrush(selected ? st::msgDateImgBgSelected : st::msgDateImgBg);
-		PainterHighQualityEnabler hq(p);
-		p.drawEllipse(muteRect);
-		(selected ? st::historyVideoMessageMuteSelected : st::historyVideoMessageMute).paintInCenter(p, muteRect);
-	}
-
-	if (!inWebPage && isRound) {
-		auto mediaUnread = item->isMediaUnread();
-		auto statusW = st::normalFont->width(_statusText) + 2 * st::msgDateImgPadding.x();
-		auto statusH = st::normalFont->height + 2 * st::msgDateImgPadding.y();
-		auto statusX = usex + paintx + st::msgDateImgDelta + st::msgDateImgPadding.x();
-		auto statusY = painty + painth - st::msgDateImgDelta - statusH + st::msgDateImgPadding.y();
-		if (item->isMediaUnread()) {
-			statusW += st::mediaUnreadSkip + st::mediaUnreadSize;
-		}
-		App::roundRect(p, rtlrect(statusX - st::msgDateImgPadding.x(), statusY - st::msgDateImgPadding.y(), statusW, statusH, width()), selected ? st::msgServiceBgSelected : st::msgServiceBg, selected ? StickerSelectedCorners : StickerCorners);
-		p.setFont(st::normalFont);
-		p.setPen(st::msgServiceFg);
-		p.drawTextLeft(statusX, statusY, width(), _statusText, statusW - 2 * st::msgDateImgPadding.x());
-		if (mediaUnread) {
-			p.setPen(Qt::NoPen);
-			p.setBrush(st::msgServiceFg);
-
-			{
-				PainterHighQualityEnabler hq(p);
-				p.drawEllipse(rtlrect(statusX - st::msgDateImgPadding.x() + statusW - st::msgDateImgPadding.x() - st::mediaUnreadSize, statusY + st::mediaUnreadTop, st::mediaUnreadSize, st::mediaUnreadSize, width()));
-			}
-		}
-		if (via || reply || forwarded) {
-			auto rectw = width() - usew - st::msgReplyPadding.left();
-			auto innerw = rectw - (st::msgReplyPadding.left() + st::msgReplyPadding.right());
-			auto recth = st::msgReplyPadding.top() + st::msgReplyPadding.bottom();
-			auto forwardedHeightReal = forwarded ? forwarded->text.countHeight(innerw) : 0;
-			auto forwardedHeight = qMin(forwardedHeightReal, kMaxGifForwardedBarLines * st::msgServiceNameFont->height);
-			if (forwarded) {
-				recth += forwardedHeight;
-			} else if (via) {
-				recth += st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0);
-			}
-			if (reply) {
-				recth += st::msgReplyBarSize.height();
-			}
-			int rectx = outbg ? 0 : (usew + st::msgReplyPadding.left());
-			int recty = painty;
-			if (rtl()) rectx = width() - rectx - rectw;
-
-			App::roundRect(p, rectx, recty, rectw, recth, selected ? st::msgServiceBgSelected : st::msgServiceBg, selected ? StickerSelectedCorners : StickerCorners);
-			p.setPen(st::msgServiceFg);
-			rectx += st::msgReplyPadding.left();
-			rectw = innerw;
-			if (forwarded) {
-				p.setTextPalette(st::serviceTextPalette);
-				auto breakEverywhere = (forwardedHeightReal > forwardedHeight);
-				forwarded->text.drawElided(p, rectx, recty + st::msgReplyPadding.top(), rectw, kMaxGifForwardedBarLines, style::al_left, 0, -1, 0, breakEverywhere);
-				p.restoreTextPalette();
-			} else if (via) {
-				p.setFont(st::msgDateFont);
-				p.drawTextLeft(rectx, recty + st::msgReplyPadding.top(), 2 * rectx + rectw, via->text);
-				int skip = st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0);
-				recty += skip;
-			}
-			if (reply) {
-				HistoryMessageReply::PaintFlags flags = 0;
-				if (selected) {
-					flags |= HistoryMessageReply::PaintFlag::Selected;
-				}
-				reply->paint(p, _parent, rectx, recty, rectw, flags);
-			}
-		}
-	}
-	if (!isRound && !_caption.isEmpty()) {
-		p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg));
-		_caption.draw(p, st::msgPadding.left(), painty + painth + st::mediaCaptionSkip, captionw, style::al_left, 0, -1, selection);
-	} else if (!inWebPage) {
-		auto fullRight = paintx + usex + usew;
-		auto fullBottom = painty + painth;
-		auto maxRight = _parent->width() - st::msgMargin.left();
-		if (_parent->hasFromPhoto()) {
-			maxRight -= st::msgMargin.right();
-		} else {
-			maxRight -= st::msgMargin.left();
-		}
-		if (isRound && !outbg) {
-			auto infoWidth = _parent->infoWidth();
-
-			// This is just some arbitrary point,
-			// the main idea is to make info left aligned here.
-			fullRight += infoWidth - st::normalFont->height;
-			if (fullRight > maxRight) {
-				fullRight = maxRight;
-			}
-		}
-		if (isRound || needInfoDisplay()) {
-			_parent->drawInfo(p, fullRight, fullBottom, 2 * paintx + paintw, selected, isRound ? InfoDisplayType::Background : InfoDisplayType::Image);
-		}
-		if (!bubble && _parent->displayRightAction()) {
-			auto fastShareLeft = (fullRight + st::historyFastShareLeft);
-			auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize);
-			if (fastShareLeft + st::historyFastShareSize > maxRight) {
-				fastShareLeft = (fullRight - st::historyFastShareSize - st::msgDateImgDelta);
-				fastShareTop -= (st::msgDateImgDelta + st::msgDateImgPadding.y() + st::msgDateFont->height + st::msgDateImgPadding.y());
-			}
-			_parent->drawRightAction(p, fastShareLeft, fastShareTop, 2 * paintx + paintw);
-		}
-	}
-}
-
-TextState HistoryGif::textState(QPoint point, StateRequest request) const {
-	auto result = TextState(_parent);
-
-	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) {
-		return result;
-	}
-	auto paintx = 0, painty = 0, paintw = width(), painth = height();
-	auto bubble = _parent->hasBubble();
-
-	if (bubble && !_caption.isEmpty()) {
-		auto captionw = paintw - st::msgPadding.left() - st::msgPadding.right();
-		painth -= _caption.countHeight(captionw);
-		if (isBubbleBottom()) {
-			painth -= st::msgPadding.bottom();
-		}
-		if (QRect(st::msgPadding.left(), painth, captionw, height() - painth).contains(point)) {
-			result = TextState(_parent, _caption.getState(
-				point - QPoint(st::msgPadding.left(), painth),
-				captionw,
-				request.forText()));
-			return result;
-		}
-		painth -= st::mediaCaptionSkip;
-	}
-	auto outbg = _parent->hasOutLayout();
-	auto inWebPage = (_parent->media() != this);
-	auto isRound = _data->isVideoMessage();
-	auto usew = paintw, usex = 0;
-	auto separateRoundVideo = isSeparateRoundVideo();
-	const auto item = _parent->data();
-	auto via = separateRoundVideo ? item->Get<HistoryMessageVia>() : nullptr;
-	auto reply = separateRoundVideo ? item->Get<HistoryMessageReply>() : nullptr;
-	auto forwarded = separateRoundVideo ? item->Get<HistoryMessageForwarded>() : nullptr;
-	if (via || reply || forwarded) {
-		usew = maxWidth() - additionalWidth(via, reply, forwarded);
-		if (outbg) {
-			usex = width() - usew;
-		}
-	}
-	if (rtl()) usex = width() - usex - usew;
-
-	if (via || reply || forwarded) {
-		auto rectw = paintw - usew - st::msgReplyPadding.left();
-		auto innerw = rectw - (st::msgReplyPadding.left() + st::msgReplyPadding.right());
-		auto recth = st::msgReplyPadding.top() + st::msgReplyPadding.bottom();
-		auto forwardedHeightReal = forwarded ? forwarded->text.countHeight(innerw) : 0;
-		auto forwardedHeight = qMin(forwardedHeightReal, kMaxGifForwardedBarLines * st::msgServiceNameFont->height);
-		if (forwarded) {
-			recth += forwardedHeight;
-		} else if (via) {
-			recth += st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0);
-		}
-		if (reply) {
-			recth += st::msgReplyBarSize.height();
-		}
-		auto rectx = outbg ? 0 : (usew + st::msgReplyPadding.left());
-		auto recty = painty;
-		if (rtl()) rectx = width() - rectx - rectw;
-
-		if (forwarded) {
-			if (QRect(rectx, recty, rectw, st::msgReplyPadding.top() + forwardedHeight).contains(point)) {
-				auto breakEverywhere = (forwardedHeightReal > forwardedHeight);
-				auto textRequest = request.forText();
-				if (breakEverywhere) {
-					textRequest.flags |= Text::StateRequest::Flag::BreakEverywhere;
-				}
-				result = TextState(_parent, forwarded->text.getState(
-					point - QPoint(rectx + st::msgReplyPadding.left(), recty + st::msgReplyPadding.top()),
-					innerw,
-					textRequest));
-				result.symbol = 0;
-				result.afterSymbol = false;
-				if (breakEverywhere) {
-					result.cursor = CursorState::Forwarded;
-				} else {
-					result.cursor = CursorState::None;
-				}
-				return result;
-			}
-			recty += forwardedHeight;
-			recth -= forwardedHeight;
-		} else if (via) {
-			auto viah = st::msgReplyPadding.top() + st::msgServiceNameFont->height + (reply ? 0 : st::msgReplyPadding.bottom());
-			if (QRect(rectx, recty, rectw, viah).contains(point)) {
-				result.link = via->link;
-				return result;
-			}
-			auto skip = st::msgServiceNameFont->height + (reply ? 2 * st::msgReplyPadding.top() : 0);
-			recty += skip;
-			recth -= skip;
-		}
-		if (reply) {
-			if (QRect(rectx, recty, rectw, recth).contains(point)) {
-				result.link = reply->replyToLink();
-				return result;
-			}
-		}
-	}
-	if (QRect(usex + paintx, painty, usew, painth).contains(point)) {
-		if (_data->uploading()) {
-			result.link = _cancell;
-		} else if (!_gif || !cAutoPlayGif() || _data->isVideoMessage()) {
-			result.link = _data->loaded() ? _openl : (_data->loading() ? _cancell : _savel);
-		} else {
-			result.link = _openInMediaviewLink;
-		}
-	}
-	if (isRound || _caption.isEmpty()) {
-		auto fullRight = usex + paintx + usew;
-		auto fullBottom = painty + painth;
-		auto maxRight = _parent->width() - st::msgMargin.left();
-		if (_parent->hasFromPhoto()) {
-			maxRight -= st::msgMargin.right();
-		} else {
-			maxRight -= st::msgMargin.left();
-		}
-		if (isRound && !outbg) {
-			auto infoWidth = _parent->infoWidth();
-
-			// This is just some arbitrary point,
-			// the main idea is to make info left aligned here.
-			fullRight += infoWidth - st::normalFont->height;
-			if (fullRight > maxRight) {
-				fullRight = maxRight;
-			}
-		}
-		if (!inWebPage) {
-			if (_parent->pointInTime(fullRight, fullBottom, point, isRound ? InfoDisplayType::Background : InfoDisplayType::Image)) {
-				result.cursor = CursorState::Date;
-			}
-		}
-		if (!bubble && _parent->displayRightAction()) {
-			auto fastShareLeft = (fullRight + st::historyFastShareLeft);
-			auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize);
-			if (fastShareLeft + st::historyFastShareSize > maxRight) {
-				fastShareLeft = (fullRight - st::historyFastShareSize - st::msgDateImgDelta);
-				fastShareTop -= st::msgDateImgDelta + st::msgDateImgPadding.y() + st::msgDateFont->height + st::msgDateImgPadding.y();
-			}
-			if (QRect(fastShareLeft, fastShareTop, st::historyFastShareSize, st::historyFastShareSize).contains(point)) {
-				result.link = _parent->rightActionLink();
-			}
-		}
-	}
-	return result;
-}
-
-TextWithEntities HistoryGif::selectedText(TextSelection selection) const {
-	return _caption.originalTextWithEntities(selection, ExpandLinksAll);
-}
-
-bool HistoryGif::needsBubble() const {
-	if (_data->isVideoMessage()) {
-		return false;
-	}
-	if (!_caption.isEmpty()) {
-		return true;
-	}
-	const auto item = _parent->data();
-	return item->viaBot()
-		|| item->Has<HistoryMessageReply>()
-		|| _parent->displayForwardedFrom()
-		|| _parent->displayFromName();
-	return false;
-}
-
-int HistoryGif::additionalWidth() const {
-	const auto item = _parent->data();
-	return additionalWidth(
-		item->Get<HistoryMessageVia>(),
-		item->Get<HistoryMessageReply>(),
-		item->Get<HistoryMessageForwarded>());
-}
-
-QString HistoryGif::mediaTypeString() const {
-	return _data->isVideoMessage()
-		? lang(lng_in_dlg_video_message)
-		: qsl("GIF");
-}
-
-bool HistoryGif::isSeparateRoundVideo() const {
-	return _data->isVideoMessage()
-		&& (_parent->media() == this)
-		&& !_parent->hasBubble();
-}
-
-void HistoryGif::setStatusSize(int newSize) const {
-	if (_data->isVideoMessage()) {
-		_statusSize = newSize;
-		if (newSize < 0) {
-			_statusText = formatDurationText(-newSize - 1);
-		} else {
-			_statusText = formatDurationText(_data->duration());
-		}
-	} else {
-		HistoryFileMedia::setStatusSize(newSize, _data->size, -2, 0);
-	}
-}
-
-void HistoryGif::updateStatusText() const {
-	auto showPause = false;
-	auto statusSize = 0;
-	auto realDuration = 0;
-	if (_data->status == FileDownloadFailed || _data->status == FileUploadFailed) {
-		statusSize = FileStatusSizeFailed;
-	} else if (_data->uploading()) {
-		statusSize = _data->uploadingData->offset;
-	} else if (_data->loading()) {
-		statusSize = _data->loadOffset();
-	} else if (_data->loaded()) {
-		statusSize = FileStatusSizeLoaded;
-		if (const auto video = activeRoundPlayer()) {
-			statusSize = -1 - _data->duration();
-
-			const auto state = Media::Player::mixer()->currentState(
-				AudioMsgId::Type::Voice);
-			if (state.id == video->audioMsgId() && state.length) {
-				auto position = int64(0);
-				if (Media::Player::IsStoppedAtEnd(state.state)) {
-					position = state.length;
-				} else if (!Media::Player::IsStoppedOrStopping(state.state)) {
-					position = state.position;
-				}
-				accumulate_max(statusSize, -1 - int((state.length - position) / state.frequency + 1));
-			}
-		}
-	} else {
-		statusSize = FileStatusSizeReady;
-	}
-	if (statusSize != _statusSize) {
-		setStatusSize(statusSize);
-	}
-}
-
-void HistoryGif::refreshParentId(not_null<HistoryItem*> realParent) {
-	HistoryFileMedia::refreshParentId(realParent);
-
-	const auto fullId = realParent->fullId();
-	if (_openInMediaviewLink) {
-		_openInMediaviewLink->setMessageId(fullId);
-	}
-}
-
-QString HistoryGif::additionalInfoString() const {
-	if (_data->isVideoMessage()) {
-		updateStatusText();
-		return _statusText;
-	}
-	return QString();
-}
-
-void HistoryGif::parentTextUpdated() {
-	_caption = (_parent->media() == this)
-		? createCaption(_parent->data())
-		: Text();
-	Auth().data().requestViewResize(_parent);
-}
-
-int HistoryGif::additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply *reply, const HistoryMessageForwarded *forwarded) const {
-	int result = 0;
-	if (forwarded) {
-		accumulate_max(result, st::msgReplyPadding.left() + st::msgReplyPadding.left() + forwarded->text.maxWidth() + st::msgReplyPadding.right());
-	} else if (via) {
-		accumulate_max(result, st::msgReplyPadding.left() + st::msgReplyPadding.left() + via->maxWidth + st::msgReplyPadding.left());
-	}
-	if (reply) {
-		accumulate_max(result, st::msgReplyPadding.left() + reply->replyToWidth());
-	}
-	return result;
-}
-
-Media::Player::RoundController *HistoryGif::activeRoundVideo() const {
-	return App::wnd()->controller()->roundVideo(_parent->data());
-}
-
-Media::Clip::Reader *HistoryGif::activeRoundPlayer() const {
-	if (const auto video = activeRoundVideo()) {
-		if (const auto result = video->reader()) {
-			if (result->ready()) {
-				return result;
-			}
-		}
-	}
-	return nullptr;
-}
-
-Media::Clip::Reader *HistoryGif::currentReader() const {
-	if (const auto result = activeRoundPlayer()) {
-		return result;
-	}
-	return (_gif && _gif->ready()) ? _gif.get() : nullptr;
-}
-
-Media::Clip::Playback *HistoryGif::videoPlayback() const {
-	if (const auto video = activeRoundVideo()) {
-		return video->playback();
-	}
-	return nullptr;
-}
-
-void HistoryGif::clipCallback(Media::Clip::Notification notification) {
-	using namespace Media::Clip;
-
-	const auto reader = _gif.get();
-	if (!reader) {
-		return;
-	}
-	switch (notification) {
-	case NotificationReinit: {
-		auto stopped = false;
-		if (reader->autoPausedGif()) {
-			auto amVisible = false;
-			Auth().data().queryItemVisibility().notify(
-				{ _parent->data(), &amVisible },
-				true);
-			if (!amVisible) { // Stop animation if it is not visible.
-				stopAnimation();
-				stopped = true;
-			}
-		}
-		if (!stopped) {
-			Auth().data().requestViewResize(_parent);
-		}
-	} break;
-
-	case NotificationRepaint: {
-		if (!reader->currentDisplayed()) {
-			Auth().data().requestViewRepaint(_parent);
-		}
-	} break;
-	}
-}
-
-void HistoryGif::playAnimation(bool autoplay) {
-	if (_data->isVideoMessage() && !autoplay) {
-		return;
-	} else if (_gif && autoplay) {
-		return;
-	}
-	using Mode = Media::Clip::Reader::Mode;
-	if (_gif) {
-		stopAnimation();
-	} else if (_data->loaded(DocumentData::FilePathResolveChecked)) {
-		if (!cAutoPlayGif()) {
-			Auth().data().stopAutoplayAnimations();
-		}
-		setClipReader(Media::Clip::MakeReader(
-			_data,
-			_parent->data()->fullId(),
-			[=](auto notification) { clipCallback(notification); },
-			Mode::Gif));
-		if (_gif && autoplay) {
-			_gif->setAutoplay();
-		}
-	}
-}
-
-void HistoryGif::stopAnimation() {
-	if (_gif) {
-		clearClipReader();
-		Auth().data().requestViewResize(_parent);
-		_data->unload();
-	}
-}
-
-void HistoryGif::setClipReader(Media::Clip::ReaderPointer gif) {
-	if (_gif) {
-		Auth().data().unregisterAutoplayAnimation(_gif.get());
-	}
-	_gif = std::move(gif);
-	if (_gif) {
-		Auth().data().registerAutoplayAnimation(_gif.get(), _parent);
-	}
-}
-
-HistoryGif::~HistoryGif() {
-	clearClipReader();
-}
-
-float64 HistoryGif::dataProgress() const {
-	return (_data->uploading() || _parent->data()->id > 0)
-		? _data->progress()
-		: 0;
-}
-
-bool HistoryGif::dataFinished() const {
-	return (_parent->data()->id > 0)
-		? (!_data->loading() && !_data->uploading())
-		: false;
-}
-
-bool HistoryGif::dataLoaded() const {
-	return (_parent->data()->id > 0) ? _data->loaded() : false;
-}
-
-bool HistoryGif::needInfoDisplay() const {
-	return (_parent->data()->id < 0 || _parent->isUnderCursor());
-}
-
-HistorySticker::HistorySticker(
-	not_null<Element*> parent,
-	not_null<DocumentData*> document)
-: HistoryMedia(parent)
-, _data(document)
-, _emoji(_data->sticker()->alt) {
-	_data->thumb->load(parent->data()->fullId());
-	if (auto emoji = Ui::Emoji::Find(_emoji)) {
-		_emoji = emoji->text();
-	}
-}
-
-QSize HistorySticker::countOptimalSize() {
-	auto sticker = _data->sticker();
-
-	if (!_packLink && sticker && sticker->set.type() != mtpc_inputStickerSetEmpty) {
-		_packLink = std::make_shared<LambdaClickHandler>([document = _data] {
-			StickerSetBox::Show(document);
-		});
-	}
-	_pixw = _data->dimensions.width();
-	_pixh = _data->dimensions.height();
-	if (_pixw > st::maxStickerSize) {
-		_pixh = (st::maxStickerSize * _pixh) / _pixw;
-		_pixw = st::maxStickerSize;
-	}
-	if (_pixh > st::maxStickerSize) {
-		_pixw = (st::maxStickerSize * _pixw) / _pixh;
-		_pixh = st::maxStickerSize;
-	}
-	if (_pixw < 1) _pixw = 1;
-	if (_pixh < 1) _pixh = 1;
-	auto maxWidth = qMax(_pixw, st::minPhotoSize);
-	auto minHeight = qMax(_pixh, st::minPhotoSize);
-	accumulate_max(
-		maxWidth,
-		_parent->infoWidth() + 2 * st::msgDateImgPadding.x());
-	if (_parent->media() == this) {
-		maxWidth += additionalWidth();
-	}
-	return { maxWidth, minHeight };
-}
-
-QSize HistorySticker::countCurrentSize(int newWidth) {
-	const auto item = _parent->data();
-	accumulate_min(newWidth, maxWidth());
-	if (_parent->media() == this) {
-		auto via = item->Get<HistoryMessageVia>();
-		auto reply = item->Get<HistoryMessageReply>();
-		if (via || reply) {
-			int usew = maxWidth() - additionalWidth(via, reply);
-			int availw = newWidth - usew - st::msgReplyPadding.left() - st::msgReplyPadding.left() - st::msgReplyPadding.left();
-			if (via) {
-				via->resize(availw);
-			}
-			if (reply) {
-				reply->resize(availw);
-			}
-		}
-	}
-	return { newWidth, minHeight() };
-}
-
-void HistorySticker::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const {
-	auto sticker = _data->sticker();
-	if (!sticker) return;
-
-	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
-
-	_data->checkSticker();
-	bool loaded = _data->loaded();
-	bool selected = (selection == FullSelection);
-
-	auto outbg = _parent->hasOutLayout();
-	auto inWebPage = (_parent->media() != this);
-
-	const auto item = _parent->data();
-	int usew = maxWidth(), usex = 0;
-	auto via = inWebPage ? nullptr : item->Get<HistoryMessageVia>();
-	auto reply = inWebPage ? nullptr : item->Get<HistoryMessageReply>();
-	if (via || reply) {
-		usew -= additionalWidth(via, reply);
-		if (outbg) {
-			usex = width() - usew;
-		}
-	}
-	if (rtl()) usex = width() - usex - usew;
-
-	const auto &pixmap = [&]() -> const QPixmap & {
-		const auto o = item->fullId();
-		const auto w = _pixw;
-		const auto h = _pixh;
-		const auto &c = st::msgStickerOverlay;
-		if (const auto image = _data->getStickerImage()) {
-			return selected
-				? image->pixColored(o, c, w, h)
-				: image->pix(o, w, h);
-		}
-		return selected
-			? _data->thumb->pixBlurredColored(o, c, w, h)
-			: _data->thumb->pixBlurred(o, w, h);
-	}();
-	p.drawPixmap(
-		QPoint{ usex + (usew - _pixw) / 2, (minHeight() - _pixh) / 2 },
-		pixmap);
-
-	if (!inWebPage) {
-		auto fullRight = usex + usew;
-		auto fullBottom = height();
-		if (needInfoDisplay()) {
-			_parent->drawInfo(p, fullRight, fullBottom, usex * 2 + usew, selected, InfoDisplayType::Background);
-		}
-		if (via || reply) {
-			int rectw = width() - usew - st::msgReplyPadding.left();
-			int recth = st::msgReplyPadding.top() + st::msgReplyPadding.bottom();
-			if (via) {
-				recth += st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0);
-			}
-			if (reply) {
-				recth += st::msgReplyBarSize.height();
-			}
-			int rectx = outbg ? 0 : (usew + st::msgReplyPadding.left());
-			int recty = st::msgDateImgDelta;
-			if (rtl()) rectx = width() - rectx - rectw;
-
-			App::roundRect(p, rectx, recty, rectw, recth, selected ? st::msgServiceBgSelected : st::msgServiceBg, selected ? StickerSelectedCorners : StickerCorners);
-			p.setPen(st::msgServiceFg);
-			rectx += st::msgReplyPadding.left();
-			rectw -= st::msgReplyPadding.left() + st::msgReplyPadding.right();
-			if (via) {
-				p.setFont(st::msgDateFont);
-				p.drawTextLeft(rectx, recty + st::msgReplyPadding.top(), 2 * rectx + rectw, via->text);
-				int skip = st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0);
-				recty += skip;
-			}
-			if (reply) {
-				HistoryMessageReply::PaintFlags flags = 0;
-				if (selected) {
-					flags |= HistoryMessageReply::PaintFlag::Selected;
-				}
-				reply->paint(p, _parent, rectx, recty, rectw, flags);
-			}
-		}
-		if (_parent->displayRightAction()) {
-			auto fastShareLeft = (fullRight + st::historyFastShareLeft);
-			auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize);
-			_parent->drawRightAction(p, fastShareLeft, fastShareTop, 2 * usex + usew);
-		}
-	}
-}
-
-TextState HistorySticker::textState(QPoint point, StateRequest request) const {
-	auto result = TextState(_parent);
-	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) {
-		return result;
-	}
-
-	auto outbg = _parent->hasOutLayout();
-	auto inWebPage = (_parent->media() != this);
-
-	const auto item = _parent->data();
-	int usew = maxWidth(), usex = 0;
-	auto via = inWebPage ? nullptr : item->Get<HistoryMessageVia>();
-	auto reply = inWebPage ? nullptr : item->Get<HistoryMessageReply>();
-	if (via || reply) {
-		usew -= additionalWidth(via, reply);
-		if (outbg) {
-			usex = width() - usew;
-		}
-	}
-	if (rtl()) usex = width() - usex - usew;
-
-	if (via || reply) {
-		int rectw = width() - usew - st::msgReplyPadding.left();
-		int recth = st::msgReplyPadding.top() + st::msgReplyPadding.bottom();
-		if (via) {
-			recth += st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0);
-		}
-		if (reply) {
-			recth += st::msgReplyBarSize.height();
-		}
-		int rectx = outbg ? 0 : (usew + st::msgReplyPadding.left());
-		int recty = st::msgDateImgDelta;
-		if (rtl()) rectx = width() - rectx - rectw;
-
-		if (via) {
-			int viah = st::msgReplyPadding.top() + st::msgServiceNameFont->height + (reply ? 0 : st::msgReplyPadding.bottom());
-			if (QRect(rectx, recty, rectw, viah).contains(point)) {
-				result.link = via->link;
-				return result;
-			}
-			int skip = st::msgServiceNameFont->height + (reply ? 2 * st::msgReplyPadding.top() : 0);
-			recty += skip;
-			recth -= skip;
-		}
-		if (reply) {
-			if (QRect(rectx, recty, rectw, recth).contains(point)) {
-				result.link = reply->replyToLink();
-				return result;
-			}
-		}
-	}
-	if (_parent->media() == this) {
-		auto fullRight = usex + usew;
-		auto fullBottom = height();
-		if (_parent->pointInTime(fullRight, fullBottom, point, InfoDisplayType::Image)) {
-			result.cursor = CursorState::Date;
-		}
-		if (_parent->displayRightAction()) {
-			auto fastShareLeft = (fullRight + st::historyFastShareLeft);
-			auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize);
-			if (QRect(fastShareLeft, fastShareTop, st::historyFastShareSize, st::historyFastShareSize).contains(point)) {
-				result.link = _parent->rightActionLink();
-			}
-		}
-	}
-
-	auto pixLeft = usex + (usew - _pixw) / 2;
-	auto pixTop = (minHeight() - _pixh) / 2;
-	if (QRect(pixLeft, pixTop, _pixw, _pixh).contains(point)) {
-		result.link = _packLink;
-		return result;
-	}
-	return result;
-}
-
-bool HistorySticker::needInfoDisplay() const {
-	return (_parent->data()->id < 0 || _parent->isUnderCursor());
-}
-
-int HistorySticker::additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply *reply) const {
-	int result = 0;
-	if (via) {
-		accumulate_max(result, st::msgReplyPadding.left() + st::msgReplyPadding.left() + via->maxWidth + st::msgReplyPadding.left());
-	}
-	if (reply) {
-		accumulate_max(result, st::msgReplyPadding.left() + reply->replyToWidth());
-	}
-	return result;
-}
-
-int HistorySticker::additionalWidth() const {
-	const auto item = _parent->data();
-	return additionalWidth(
-		item->Get<HistoryMessageVia>(),
-		item->Get<HistoryMessageReply>());
-}
-
-namespace {
-
-ClickHandlerPtr sendMessageClickHandler(PeerData *peer) {
-	return std::make_shared<LambdaClickHandler>([peer] {
-		App::wnd()->controller()->showPeerHistory(
-			peer->id,
-			Window::SectionShow::Way::Forward);
-	});
-}
-
-ClickHandlerPtr addContactClickHandler(not_null<HistoryItem*> item) {
-	return std::make_shared<LambdaClickHandler>([fullId = item->fullId()] {
-		if (const auto item = App::histItemById(fullId)) {
-			if (const auto media = item->media()) {
-				if (const auto contact = media->sharedContact()) {
-					Ui::show(Box<AddContactBox>(
-						contact->firstName,
-						contact->lastName,
-						contact->phoneNumber));
-				}
-			}
-		}
-	});
-}
-
-} // namespace
-
-HistoryContact::HistoryContact(
-	not_null<Element*> parent,
-	UserId userId,
-	const QString &first,
-	const QString &last,
-	const QString &phone)
-: HistoryMedia(parent)
-, _userId(userId)
-, _fname(first)
-, _lname(last)
-, _phone(App::formatPhone(phone)) {
-	Auth().data().registerContactView(userId, parent);
-
-	_name.setText(
-		st::semiboldTextStyle,
-		lng_full_name(lt_first_name, first, lt_last_name, last).trimmed(),
-		Ui::NameTextOptions());
-	_phonew = st::normalFont->width(_phone);
-}
-
-HistoryContact::~HistoryContact() {
-	Auth().data().unregisterContactView(_userId, _parent);
-}
-
-void HistoryContact::updateSharedContactUserId(UserId userId) {
-	if (_userId != userId) {
-		Auth().data().unregisterContactView(_userId, _parent);
-		_userId = userId;
-		Auth().data().registerContactView(_userId, _parent);
-	}
-}
-
-QSize HistoryContact::countOptimalSize() {
-	const auto item = _parent->data();
-	auto maxWidth = st::msgFileMinWidth;
-
-	_contact = _userId ? App::userLoaded(_userId) : nullptr;
-	if (_contact) {
-		_contact->loadUserpic();
-	} else {
-		_photoEmpty = std::make_unique<Ui::EmptyUserpic>(
-			Data::PeerUserpicColor(_userId ? _userId : _parent->data()->id),
-			_name.originalText());
-	}
-	if (_contact
-		&& _contact->contactStatus() == UserData::ContactStatus::Contact) {
-		_linkl = sendMessageClickHandler(_contact);
-		_link = lang(lng_profile_send_message).toUpper();
-	} else if (_userId) {
-		_linkl = addContactClickHandler(_parent->data());
-		_link = lang(lng_profile_add_contact).toUpper();
-	}
-	_linkw = _link.isEmpty() ? 0 : st::semiboldFont->width(_link);
-
-	auto tleft = 0;
-	auto tright = 0;
-	if (_userId) {
-		tleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right();
-		tright = st::msgFileThumbPadding.left();
-		accumulate_max(maxWidth, tleft + _phonew + tright);
-	} else {
-		tleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right();
-		tright = st::msgFileThumbPadding.left();
-		accumulate_max(maxWidth, tleft + _phonew + _parent->skipBlockWidth() + st::msgPadding.right());
-	}
-
-	accumulate_max(maxWidth, tleft + _name.maxWidth() + tright);
-	accumulate_min(maxWidth, st::msgMaxWidth);
-	auto minHeight = 0;
-	if (_userId) {
-		minHeight = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom();
-		if (item->Has<HistoryMessageSigned>()
-			|| item->Has<HistoryMessageViews>()) {
-			minHeight += st::msgDateFont->height - st::msgDateDelta.y();
-		}
-	} else {
-		minHeight = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom();
-	}
-	if (!isBubbleTop()) {
-		minHeight -= st::msgFileTopMinus;
-	}
-	return { maxWidth, minHeight };
-}
-
-void HistoryContact::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const {
-	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
-	auto paintx = 0, painty = 0, paintw = width(), painth = height();
-
-	auto outbg = _parent->hasOutLayout();
-	bool selected = (selection == FullSelection);
-
-	accumulate_min(paintw, maxWidth());
-
-	auto nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0;
-	auto topMinus = isBubbleTop() ? 0 : st::msgFileTopMinus;
-	if (_userId) {
-		nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right();
-		nametop = st::msgFileThumbNameTop - topMinus;
-		nameright = st::msgFileThumbPadding.left();
-		statustop = st::msgFileThumbStatusTop - topMinus;
-		linktop = st::msgFileThumbLinkTop - topMinus;
-
-		QRect rthumb(rtlrect(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top() - topMinus, st::msgFileThumbSize, st::msgFileThumbSize, paintw));
-		if (_contact) {
-			_contact->paintUserpic(p, rthumb.x(), rthumb.y(), st::msgFileThumbSize);
-		} else {
-			_photoEmpty->paint(p, st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top() - topMinus, paintw, st::msgFileThumbSize);
-		}
-		if (selected) {
-			PainterHighQualityEnabler hq(p);
-			p.setBrush(p.textPalette().selectOverlay);
-			p.setPen(Qt::NoPen);
-			p.drawEllipse(rthumb);
-		}
-
-		bool over = ClickHandler::showAsActive(_linkl);
-		p.setFont(over ? st::semiboldFont->underline() : st::semiboldFont);
-		p.setPen(outbg ? (selected ? st::msgFileThumbLinkOutFgSelected : st::msgFileThumbLinkOutFg) : (selected ? st::msgFileThumbLinkInFgSelected : st::msgFileThumbLinkInFg));
-		p.drawTextLeft(nameleft, linktop, paintw, _link, _linkw);
-	} else {
-		nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right();
-		nametop = st::msgFileNameTop - topMinus;
-		nameright = st::msgFilePadding.left();
-		statustop = st::msgFileStatusTop - topMinus;
-
-		_photoEmpty->paint(p, st::msgFilePadding.left(), st::msgFilePadding.top() - topMinus, paintw, st::msgFileSize);
-	}
-	auto namewidth = paintw - nameleft - nameright;
-
-	p.setFont(st::semiboldFont);
-	p.setPen(outbg ? (selected ? st::historyFileNameOutFgSelected : st::historyFileNameOutFg) : (selected ? st::historyFileNameInFgSelected : st::historyFileNameInFg));
-	_name.drawLeftElided(p, nameleft, nametop, namewidth, paintw);
-
-	auto &status = outbg ? (selected ? st::mediaOutFgSelected : st::mediaOutFg) : (selected ? st::mediaInFgSelected : st::mediaInFg);
-	p.setFont(st::normalFont);
-	p.setPen(status);
-	p.drawTextLeft(nameleft, statustop, paintw, _phone);
-}
-
-TextState HistoryContact::textState(QPoint point, StateRequest request) const {
-	auto result = TextState(_parent);
-
-	auto nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0;
-	auto topMinus = isBubbleTop() ? 0 : st::msgFileTopMinus;
-	if (_userId) {
-		nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right();
-		linktop = st::msgFileThumbLinkTop - topMinus;
-		if (rtlrect(nameleft, linktop, _linkw, st::semiboldFont->height, width()).contains(point)) {
-			result.link = _linkl;
-			return result;
-		}
-	}
-	if (QRect(0, 0, width(), height()).contains(point) && _contact) {
-		result.link = _contact->openLink();
-		return result;
-	}
-	return result;
-}
-
-HistoryCall::HistoryCall(
-	not_null<Element*> parent,
-	not_null<Data::Call*> call)
-: HistoryMedia(parent) {
-	_duration = call->duration;
-	_reason = call->finishReason;
-
-	const auto item = parent->data();
-	_text = Data::MediaCall::Text(item, _reason);
-	_status = parent->dateTime().time().toString(cTimeFormat());
-	if (_duration) {
-		if (_reason != FinishReason::Missed
-			&& _reason != FinishReason::Busy) {
-			_status = lng_call_duration_info(
-				lt_time,
-				_status,
-				lt_duration,
-				formatDurationWords(_duration));
-		} else {
-			_duration = 0;
-		}
-	}
-}
-
-QSize HistoryCall::countOptimalSize() {
-	const auto user = _parent->data()->history()->peer->asUser();
-	_link = std::make_shared<LambdaClickHandler>([=] {
-		if (user) {
-			Calls::Current().startOutgoingCall(user);
-		}
-	});
-
-	auto maxWidth = st::historyCallWidth;
-	auto minHeight = st::historyCallHeight;
-	if (!isBubbleTop()) {
-		minHeight -= st::msgFileTopMinus;
-	}
-	return { maxWidth, minHeight };
-}
-
-void HistoryCall::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const {
-	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
-	auto paintx = 0, painty = 0, paintw = width(), painth = height();
-
-	auto outbg = _parent->hasOutLayout();
-	auto selected = (selection == FullSelection);
-
-	accumulate_min(paintw, maxWidth());
-
-	auto nameleft = 0, nametop = 0, nameright = 0, statustop = 0;
-	auto topMinus = isBubbleTop() ? 0 : st::msgFileTopMinus;
-
-	nameleft = st::historyCallLeft;
-	nametop = st::historyCallTop - topMinus;
-	nameright = st::msgFilePadding.left();
-	statustop = st::historyCallStatusTop - topMinus;
-
-	auto namewidth = paintw - nameleft - nameright;
-
-	p.setFont(st::semiboldFont);
-	p.setPen(outbg ? (selected ? st::historyFileNameOutFgSelected : st::historyFileNameOutFg) : (selected ? st::historyFileNameInFgSelected : st::historyFileNameInFg));
-	p.drawTextLeft(nameleft, nametop, paintw, _text);
-
-	auto statusleft = nameleft;
-	auto missed = (_reason == FinishReason::Missed || _reason == FinishReason::Busy);
-	auto &arrow = outbg ? (selected ? st::historyCallArrowOutSelected : st::historyCallArrowOut) : missed ? (selected ? st::historyCallArrowMissedInSelected : st::historyCallArrowMissedIn) : (selected ? st::historyCallArrowInSelected : st::historyCallArrowIn);
-	arrow.paint(p, statusleft + st::historyCallArrowPosition.x(), statustop + st::historyCallArrowPosition.y(), paintw);
-	statusleft += arrow.width() + st::historyCallStatusSkip;
-
-	auto &statusFg = outbg ? (selected ? st::mediaOutFgSelected : st::mediaOutFg) : (selected ? st::mediaInFgSelected : st::mediaInFg);
-	p.setFont(st::normalFont);
-	p.setPen(statusFg);
-	p.drawTextLeft(statusleft, statustop, paintw, _status);
-
-	auto &icon = outbg ? (selected ? st::historyCallOutIconSelected : st::historyCallOutIcon) : (selected ? st::historyCallInIconSelected : st::historyCallInIcon);
-	icon.paint(p, paintw - st::historyCallIconPosition.x() - icon.width(), st::historyCallIconPosition.y() - topMinus, paintw);
-}
-
-TextState HistoryCall::textState(QPoint point, StateRequest request) const {
-	auto result = TextState(_parent);
-	if (QRect(0, 0, width(), height()).contains(point)) {
-		result.link = _link;
-		return result;
-	}
-	return result;
-}
-
-namespace {
-
-int articleThumbWidth(PhotoData *thumb, int height) {
-	auto w = thumb->medium->width();
-	auto h = thumb->medium->height();
-	return qMax(qMin(height * w / h, height), 1);
-}
-
-int articleThumbHeight(PhotoData *thumb, int width) {
-	return qMax(thumb->medium->height() * width / thumb->medium->width(), 1);
-}
-
-int unitedLineHeight() {
-	return qMax(st::webPageTitleFont->height, st::webPageDescriptionFont->height);
-}
-
-} // namespace
-
-HistoryWebPage::HistoryWebPage(
-	not_null<Element*> parent,
-	not_null<WebPageData*> data)
-: HistoryMedia(parent)
-, _data(data)
-, _title(st::msgMinWidth - st::webPageLeft)
-, _description(st::msgMinWidth - st::webPageLeft) {
-	Auth().data().registerWebPageView(_data, _parent);
-}
-
-QSize HistoryWebPage::countOptimalSize() {
-	if (_data->pendingTill) {
-		return { 0, 0 };
-	}
-	const auto versionChanged = (_dataVersion != _data->version);
-	if (versionChanged) {
-		_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;
-	}
-	auto lineHeight = unitedLineHeight();
-
-	if (!_openl && !_data->url.isEmpty()) {
-		const auto previewOfHiddenUrl = [&] {
-			const auto simplify = [](const QString &url) {
-				auto result = url.toLower();
-				if (result.endsWith('/')) {
-					result.chop(1);
-				}
-				const auto prefixes = { qstr("http://"), qstr("https://") };
-				for (const auto &prefix : prefixes) {
-					if (result.startsWith(prefix)) {
-						result = result.mid(prefix.size());
-						break;
-					}
-				}
-				return result;
-			};
-			const auto simplified = simplify(_data->url);
-			const auto full = _parent->data()->originalText();
-			for (const auto &entity : full.entities) {
-				if (entity.type() != EntityInTextUrl) {
-					continue;
-				}
-				const auto link = full.text.mid(
-					entity.offset(),
-					entity.length());
-				if (simplify(link) == simplified) {
-					return false;
-				}
-			}
-			return true;
-		}();
-		_openl = previewOfHiddenUrl
-			? std::make_shared<HiddenUrlClickHandler>(_data->url)
-			: std::make_shared<UrlClickHandler>(_data->url, true);
-	}
-
-	// init layout
-	auto title = TextUtilities::SingleLine(_data->title.isEmpty()
-		? _data->author
-		: _data->title);
-	if (!_collage.empty()) {
-		_asArticle = false;
-	} else if (!_data->document
-		&& _data->photo
-		&& _data->type != WebPageType::Photo
-		&& _data->type != WebPageType::Video) {
-		if (_data->type == WebPageType::Profile) {
-			_asArticle = true;
-		} else if (_data->siteName == qstr("Twitter")
-			|| _data->siteName == qstr("Facebook")
-			|| _data->type == WebPageType::ArticleWithIV) {
-			_asArticle = false;
-		} else {
-			_asArticle = true;
-		}
-		if (_asArticle
-			&& _data->description.text.isEmpty()
-			&& title.isEmpty()
-			&& _data->siteName.isEmpty()) {
-			_asArticle = false;
-		}
-	} else {
-		_asArticle = false;
-	}
-
-	// init attach
-	if (!_attach && !_asArticle) {
-		_attach = CreateAttach(
-			_parent,
-			_data->document,
-			_data->photo,
-			_collage);
-	}
-
-	auto textFloatsAroundInfo = !_asArticle && !_attach && isBubbleBottom();
-
-	// init strings
-	if (_description.isEmpty() && !_data->description.text.isEmpty()) {
-		auto text = _data->description;
-
-		if (textFloatsAroundInfo) {
-			text.text += _parent->skipBlock();
-		}
-		if (isLogEntryOriginal()) {
-			// Fix layout for small bubbles (narrow media caption edit log entries).
-			_description = Text(st::minPhotoSize
-				- st::msgPadding.left()
-				- st::msgPadding.right()
-				- st::webPageLeft);
-		}
-		_description.setMarkedText(
-			st::webPageDescriptionStyle,
-			text,
-			Ui::WebpageTextDescriptionOptions(_data->siteName));
-	}
-	if (_title.isEmpty() && !title.isEmpty()) {
-		if (textFloatsAroundInfo && _description.isEmpty()) {
-			title += _parent->skipBlock();
-		}
-		_title.setText(
-			st::webPageTitleStyle,
-			title,
-			Ui::WebpageTextTitleOptions());
-	}
-	if (!_siteNameWidth && !_data->siteName.isEmpty()) {
-		_siteNameWidth = st::webPageTitleFont->width(_data->siteName);
-	}
-
-	// init dimensions
-	auto l = st::msgPadding.left() + st::webPageLeft, r = st::msgPadding.right();
-	auto skipBlockWidth = _parent->skipBlockWidth();
-	auto maxWidth = skipBlockWidth;
-	auto minHeight = 0;
-
-	auto siteNameHeight = _data->siteName.isEmpty() ? 0 : lineHeight;
-	auto titleMinHeight = _title.isEmpty() ? 0 : lineHeight;
-	auto descMaxLines = isLogEntryOriginal() ? kMaxOriginalEntryLines : (3 + (siteNameHeight ? 0 : 1) + (titleMinHeight ? 0 : 1));
-	auto descriptionMinHeight = _description.isEmpty() ? 0 : qMin(_description.minHeight(), descMaxLines * lineHeight);
-	auto articleMinHeight = siteNameHeight + titleMinHeight + descriptionMinHeight;
-	auto articlePhotoMaxWidth = 0;
-	if (_asArticle) {
-		articlePhotoMaxWidth = st::webPagePhotoDelta + qMax(articleThumbWidth(_data->photo, articleMinHeight), lineHeight);
-	}
-
-	if (_siteNameWidth) {
-		if (_title.isEmpty() && _description.isEmpty()) {
-			accumulate_max(maxWidth, _siteNameWidth + _parent->skipBlockWidth());
-		} else {
-			accumulate_max(maxWidth, _siteNameWidth + articlePhotoMaxWidth);
-		}
-		minHeight += lineHeight;
-	}
-	if (!_title.isEmpty()) {
-		accumulate_max(maxWidth, _title.maxWidth() + articlePhotoMaxWidth);
-		minHeight += titleMinHeight;
-	}
-	if (!_description.isEmpty()) {
-		accumulate_max(maxWidth, _description.maxWidth() + articlePhotoMaxWidth);
-		minHeight += descriptionMinHeight;
-	}
-	if (_attach) {
-		auto attachAtTop = !_siteNameWidth && _title.isEmpty() && _description.isEmpty();
-		if (!attachAtTop) minHeight += st::mediaInBubbleSkip;
-
-		_attach->initDimensions();
-		auto bubble = _attach->bubbleMargins();
-		auto maxMediaWidth = _attach->maxWidth() - bubble.left() - bubble.right();
-		if (isBubbleBottom() && _attach->customInfoLayout()) {
-			maxMediaWidth += skipBlockWidth;
-		}
-		accumulate_max(maxWidth, maxMediaWidth);
-		minHeight += _attach->minHeight() - bubble.top() - bubble.bottom();
-		if (!_attach->additionalInfoString().isEmpty()) {
-			minHeight += bottomInfoPadding();
-		}
-	}
-	if (_data->type == WebPageType::Video && _data->duration) {
-		_duration = formatDurationText(_data->duration);
-		_durationWidth = st::msgDateFont->width(_duration);
-	}
-	maxWidth += st::msgPadding.left() + st::webPageLeft + st::msgPadding.right();
-	auto padding = inBubblePadding();
-	minHeight += padding.top() + padding.bottom();
-
-	if (_asArticle) {
-		minHeight = resizeGetHeight(maxWidth);
-	}
-	return { maxWidth, minHeight };
-}
-
-QSize HistoryWebPage::countCurrentSize(int newWidth) {
-	if (_data->pendingTill) {
-		return { newWidth, minHeight() };
-	}
-
-	auto innerWidth = newWidth - st::msgPadding.left() - st::webPageLeft - st::msgPadding.right();
-	auto newHeight = 0;
-
-	auto lineHeight = unitedLineHeight();
-	auto linesMax = isLogEntryOriginal() ? kMaxOriginalEntryLines : 5;
-	auto siteNameLines = _siteNameWidth ? 1 : 0;
-	auto siteNameHeight = _siteNameWidth ? lineHeight : 0;
-	if (_asArticle) {
-		_pixh = linesMax * lineHeight;
-		do {
-			_pixw = articleThumbWidth(_data->photo, _pixh);
-			auto wleft = innerWidth - st::webPagePhotoDelta - qMax(_pixw, lineHeight);
-
-			newHeight = siteNameHeight;
-
-			if (_title.isEmpty()) {
-				_titleLines = 0;
-			} else {
-				if (_title.countHeight(wleft) < 2 * st::webPageTitleFont->height) {
-					_titleLines = 1;
-				} else {
-					_titleLines = 2;
-				}
-				newHeight += _titleLines * lineHeight;
-			}
-
-			auto descriptionHeight = _description.countHeight(wleft);
-			if (descriptionHeight < (linesMax - siteNameLines - _titleLines) * st::webPageDescriptionFont->height) {
-				// We have height for all the lines.
-				_descriptionLines = -1;
-				newHeight += descriptionHeight;
-			} else {
-				_descriptionLines = (linesMax - siteNameLines - _titleLines);
-				newHeight += _descriptionLines * lineHeight;
-			}
-
-			if (newHeight >= _pixh) {
-				break;
-			}
-
-			_pixh -= lineHeight;
-		} while (_pixh > lineHeight);
-		newHeight += bottomInfoPadding();
-	} else {
-		newHeight = siteNameHeight;
-
-		if (_title.isEmpty()) {
-			_titleLines = 0;
-		} else {
-			if (_title.countHeight(innerWidth) < 2 * st::webPageTitleFont->height) {
-				_titleLines = 1;
-			} else {
-				_titleLines = 2;
-			}
-			newHeight += _titleLines * lineHeight;
-		}
-
-		if (_description.isEmpty()) {
-			_descriptionLines = 0;
-		} else {
-			auto descriptionHeight = _description.countHeight(innerWidth);
-			if (descriptionHeight < (linesMax - siteNameLines - _titleLines) * st::webPageDescriptionFont->height) {
-				// We have height for all the lines.
-				_descriptionLines = -1;
-				newHeight += descriptionHeight;
-			} else {
-				_descriptionLines = (linesMax - siteNameLines - _titleLines);
-				newHeight += _descriptionLines * lineHeight;
-			}
-		}
-
-		if (_attach) {
-			auto attachAtTop = !_siteNameWidth && !_titleLines && !_descriptionLines;
-			if (!attachAtTop) newHeight += st::mediaInBubbleSkip;
-
-			auto bubble = _attach->bubbleMargins();
-
-			_attach->resizeGetHeight(innerWidth + bubble.left() + bubble.right());
-			newHeight += _attach->height() - bubble.top() - bubble.bottom();
-			if (!_attach->additionalInfoString().isEmpty()) {
-				newHeight += bottomInfoPadding();
-			} else if (isBubbleBottom() && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > innerWidth + bubble.left() + bubble.right()) {
-				newHeight += bottomInfoPadding();
-			}
-		}
-	}
-	auto padding = inBubblePadding();
-	newHeight += padding.top() + padding.bottom();
-
-	return { newWidth, newHeight };
-}
-
-TextSelection HistoryWebPage::toDescriptionSelection(
-		TextSelection selection) const {
-	return HistoryView::UnshiftItemSelection(selection, _title);
-}
-
-TextSelection HistoryWebPage::fromDescriptionSelection(
-		TextSelection selection) const {
-	return HistoryView::ShiftItemSelection(selection, _title);
-}
-
-void HistoryWebPage::refreshParentId(not_null<HistoryItem*> realParent) {
-	if (_attach) {
-		_attach->refreshParentId(realParent);
-	}
-}
-
-void HistoryWebPage::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const {
-	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
-	auto paintx = 0, painty = 0, paintw = width(), painth = height();
-
-	auto outbg = _parent->hasOutLayout();
-	bool selected = (selection == FullSelection);
-
-	auto &barfg = selected ? (outbg ? st::msgOutReplyBarSelColor : st::msgInReplyBarSelColor) : (outbg ? st::msgOutReplyBarColor : st::msgInReplyBarColor);
-	auto &semibold = selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg);
-	auto &regular = selected ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) : (outbg ? st::msgOutDateFg : st::msgInDateFg);
-
-	QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins());
-	auto padding = inBubblePadding();
-	auto tshift = padding.top();
-	auto bshift = padding.bottom();
-	paintw -= padding.left() + padding.right();
-	auto attachAdditionalInfoText = _attach ? _attach->additionalInfoString() : QString();
-	if (_asArticle) {
-		bshift += bottomInfoPadding();
-	} else if (!attachAdditionalInfoText.isEmpty()) {
-		bshift += bottomInfoPadding();
-	} else if (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > paintw + bubble.left() + bubble.right()) {
-		bshift += bottomInfoPadding();
-	}
-
-	QRect bar(rtlrect(st::msgPadding.left(), tshift, st::webPageBar, height() - tshift - bshift, width()));
-	p.fillRect(bar, barfg);
-
-	auto lineHeight = unitedLineHeight();
-	if (_asArticle) {
-		const auto contextId = _parent->data()->fullId();
-		_data->photo->medium->load(contextId, false, false);
-		bool full = _data->photo->medium->loaded();
-		QPixmap pix;
-		auto pw = qMax(_pixw, lineHeight);
-		auto ph = _pixh;
-		auto pixw = _pixw, pixh = articleThumbHeight(_data->photo, _pixw);
-		auto maxw = ConvertScale(_data->photo->medium->width()), maxh = ConvertScale(_data->photo->medium->height());
-		if (pixw * ph != pixh * pw) {
-			float64 coef = (pixw * ph > pixh * pw) ? qMin(ph / float64(pixh), maxh / float64(pixh)) : qMin(pw / float64(pixw), maxw / float64(pixw));
-			pixh = qRound(pixh * coef);
-			pixw = qRound(pixw * coef);
-		}
-		if (full) {
-			pix = _data->photo->medium->pixSingle(contextId, pixw, pixh, pw, ph, ImageRoundRadius::Small);
-		} else {
-			pix = _data->photo->thumb->pixBlurredSingle(contextId, pixw, pixh, pw, ph, ImageRoundRadius::Small);
-		}
-		p.drawPixmapLeft(padding.left() + paintw - pw, tshift, width(), pix);
-		if (selected) {
-			App::roundRect(p, rtlrect(padding.left() + paintw - pw, tshift, pw, _pixh, width()), p.textPalette().selectOverlay, SelectedOverlaySmallCorners);
-		}
-		paintw -= pw + st::webPagePhotoDelta;
-	}
-	if (_siteNameWidth) {
-		p.setFont(st::webPageTitleFont);
-		p.setPen(semibold);
-		p.drawTextLeft(padding.left(), tshift, width(), (paintw >= _siteNameWidth) ? _data->siteName : st::webPageTitleFont->elided(_data->siteName, paintw));
-		tshift += lineHeight;
-	}
-	if (_titleLines) {
-		p.setPen(outbg ? st::webPageTitleOutFg : st::webPageTitleInFg);
-		auto endskip = 0;
-		if (_title.hasSkipBlock()) {
-			endskip = _parent->skipBlockWidth();
-		}
-		_title.drawLeftElided(p, padding.left(), tshift, paintw, width(), _titleLines, style::al_left, 0, -1, endskip, false, selection);
-		tshift += _titleLines * lineHeight;
-	}
-	if (_descriptionLines) {
-		p.setPen(outbg ? st::webPageDescriptionOutFg : st::webPageDescriptionInFg);
-		auto endskip = 0;
-		if (_description.hasSkipBlock()) {
-			endskip = _parent->skipBlockWidth();
-		}
-		if (_descriptionLines > 0) {
-			_description.drawLeftElided(p, padding.left(), tshift, paintw, width(), _descriptionLines, style::al_left, 0, -1, endskip, false, toDescriptionSelection(selection));
-			tshift += _descriptionLines * lineHeight;
-		} else {
-			_description.drawLeft(p, padding.left(), tshift, paintw, width(), style::al_left, 0, -1, toDescriptionSelection(selection));
-			tshift += _description.countHeight(paintw);
-		}
-	}
-	if (_attach) {
-		auto attachAtTop = !_siteNameWidth && !_titleLines && !_descriptionLines;
-		if (!attachAtTop) tshift += st::mediaInBubbleSkip;
-
-		auto attachLeft = padding.left() - bubble.left();
-		auto attachTop = tshift - bubble.top();
-		if (rtl()) attachLeft = width() - attachLeft - _attach->width();
-
-		p.translate(attachLeft, attachTop);
-
-		auto attachSelection = selected ? FullSelection : TextSelection { 0, 0 };
-		_attach->draw(p, r.translated(-attachLeft, -attachTop), attachSelection, ms);
-		auto pixwidth = _attach->width();
-		auto pixheight = _attach->height();
-
-		if (_data->type == WebPageType::Video
-			&& _attach->type() == MediaTypePhoto) {
-			if (_attach->isReadyForOpen()) {
-				if (_data->siteName == qstr("YouTube")) {
-					st::youtubeIcon.paint(p, (pixwidth - st::youtubeIcon.width()) / 2, (pixheight - st::youtubeIcon.height()) / 2, width());
-				} else {
-					st::videoIcon.paint(p, (pixwidth - st::videoIcon.width()) / 2, (pixheight - st::videoIcon.height()) / 2, width());
-				}
-			}
-			if (_durationWidth) {
-				auto dateX = pixwidth - _durationWidth - st::msgDateImgDelta - 2 * st::msgDateImgPadding.x();
-				auto dateY = pixheight - st::msgDateFont->height - 2 * st::msgDateImgPadding.y() - st::msgDateImgDelta;
-				auto dateW = pixwidth - dateX - st::msgDateImgDelta;
-				auto dateH = pixheight - dateY - st::msgDateImgDelta;
-
-				App::roundRect(p, dateX, dateY, dateW, dateH, selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners);
-
-				p.setFont(st::msgDateFont);
-				p.setPen(st::msgDateImgFg);
-				p.drawTextLeft(dateX + st::msgDateImgPadding.x(), dateY + st::msgDateImgPadding.y(), pixwidth, _duration);
-			}
-		}
-
-		p.translate(-attachLeft, -attachTop);
-
-		if (!attachAdditionalInfoText.isEmpty()) {
-			p.setFont(st::msgDateFont);
-			p.setPen(selected ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) : (outbg ? st::msgOutDateFg : st::msgInDateFg));
-			p.drawTextLeft(st::msgPadding.left(), bar.y() + bar.height() + st::mediaInBubbleSkip, width(), attachAdditionalInfoText);
-		}
-	}
-}
-
-TextState HistoryWebPage::textState(QPoint point, StateRequest request) const {
-	auto result = TextState(_parent);
-
-	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) {
-		return result;
-	}
-	auto paintx = 0, painty = 0, paintw = width(), painth = height();
-
-	QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins());
-	auto padding = inBubblePadding();
-	auto tshift = padding.top();
-	auto bshift = padding.bottom();
-	if (_asArticle || (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > paintw + bubble.left() + bubble.right())) {
-		bshift += bottomInfoPadding();
-	}
-	paintw -= padding.left() + padding.right();
-
-	auto lineHeight = unitedLineHeight();
-	auto inThumb = false;
-	if (_asArticle) {
-		auto pw = qMax(_pixw, lineHeight);
-		if (rtlrect(padding.left() + paintw - pw, 0, pw, _pixh, width()).contains(point)) {
-			inThumb = true;
-		}
-		paintw -= pw + st::webPagePhotoDelta;
-	}
-	int symbolAdd = 0;
-	if (_siteNameWidth) {
-		tshift += lineHeight;
-	}
-	if (_titleLines) {
-		if (point.y() >= tshift && point.y() < tshift + _titleLines * lineHeight) {
-			Text::StateRequestElided titleRequest = request.forText();
-			titleRequest.lines = _titleLines;
-			result = TextState(_parent, _title.getStateElidedLeft(
-				point - QPoint(padding.left(), tshift),
-				paintw,
-				width(),
-				titleRequest));
-		} else if (point.y() >= tshift + _titleLines * lineHeight) {
-			symbolAdd += _title.length();
-		}
-		tshift += _titleLines * lineHeight;
-	}
-	if (_descriptionLines) {
-		auto descriptionHeight = (_descriptionLines > 0) ? _descriptionLines * lineHeight : _description.countHeight(paintw);
-		if (point.y() >= tshift && point.y() < tshift + descriptionHeight) {
-			if (_descriptionLines > 0) {
-				Text::StateRequestElided descriptionRequest = request.forText();
-				descriptionRequest.lines = _descriptionLines;
-				result = TextState(_parent, _description.getStateElidedLeft(
-					point - QPoint(padding.left(), tshift),
-					paintw,
-					width(),
-					descriptionRequest));
-			} else {
-				result = TextState(_parent, _description.getStateLeft(
-					point - QPoint(padding.left(), tshift),
-					paintw,
-					width(),
-					request.forText()));
-			}
-		} else if (point.y() >= tshift + descriptionHeight) {
-			symbolAdd += _description.length();
-		}
-		tshift += descriptionHeight;
-	}
-	if (inThumb) {
-		result.link = _openl;
-	} else if (_attach) {
-		auto attachAtTop = !_siteNameWidth && !_titleLines && !_descriptionLines;
-		if (!attachAtTop) tshift += st::mediaInBubbleSkip;
-
-		if (QRect(padding.left(), tshift, paintw, height() - tshift - bshift).contains(point)) {
-			auto attachLeft = padding.left() - bubble.left();
-			auto attachTop = tshift - bubble.top();
-			if (rtl()) attachLeft = width() - attachLeft - _attach->width();
-			result = _attach->textState(point - QPoint(attachLeft, attachTop), request);
-
-			if (result.link && !_data->document && _data->photo && _collage.empty() && _attach->isReadyForOpen()) {
-				if (_data->type == WebPageType::Profile
-					|| _data->type == WebPageType::Video) {
-					result.link = _openl;
-				} else if (_data->type == WebPageType::Photo
-					|| _data->siteName == qstr("Twitter")
-					|| _data->siteName == qstr("Facebook")) {
-					// leave photo link
-				} else {
-					result.link = _openl;
-				}
-			}
-		}
-	}
-
-	result.symbol += symbolAdd;
-	return result;
-}
-
-TextSelection HistoryWebPage::adjustSelection(TextSelection selection, TextSelectType type) const {
-	if (!_descriptionLines || selection.to <= _title.length()) {
-		return _title.adjustSelection(selection, type);
-	}
-	auto descriptionSelection = _description.adjustSelection(toDescriptionSelection(selection), type);
-	if (selection.from >= _title.length()) {
-		return fromDescriptionSelection(descriptionSelection);
-	}
-	auto titleSelection = _title.adjustSelection(selection, type);
-	return { titleSelection.from, fromDescriptionSelection(descriptionSelection).to };
-}
-
-void HistoryWebPage::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
-	if (_attach) {
-		_attach->clickHandlerActiveChanged(p, active);
-	}
-}
-
-void HistoryWebPage::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) {
-	if (_attach) {
-		_attach->clickHandlerPressedChanged(p, pressed);
-	}
-}
-
-void HistoryWebPage::playAnimation(bool autoplay) {
-	if (_attach) {
-		if (autoplay) {
-			_attach->autoplayAnimation();
-		} else {
-			_attach->playAnimation();
-		}
-	}
-}
-
-bool HistoryWebPage::isDisplayed() const {
-	const auto item = _parent->data();
-	return !_data->pendingTill
-		&& !item->Has<HistoryMessageLogEntryOriginal>();
-}
-
-TextWithEntities HistoryWebPage::selectedText(TextSelection selection) const {
-	auto titleResult = _title.originalTextWithEntities(
-		selection,
-		ExpandLinksAll);
-	auto descriptionResult = _description.originalTextWithEntities(
-		toDescriptionSelection(selection),
-		ExpandLinksAll);
-	if (titleResult.text.isEmpty()) {
-		return descriptionResult;
-	} else if (descriptionResult.text.isEmpty()) {
-		return titleResult;
-	}
-
-	titleResult.text += '\n';
-	TextUtilities::Append(titleResult, std::move(descriptionResult));
-	return titleResult;
-}
-
-QMargins HistoryWebPage::inBubblePadding() const {
-	auto lshift = st::msgPadding.left() + st::webPageLeft;
-	auto rshift = st::msgPadding.right();
-	auto bshift = isBubbleBottom() ? st::msgPadding.left() : st::mediaInBubbleSkip;
-	auto tshift = isBubbleTop() ? st::msgPadding.left() : st::mediaInBubbleSkip;
-	return QMargins(lshift, tshift, rshift, bshift);
-}
-
-bool HistoryWebPage::isLogEntryOriginal() const {
-	return _parent->data()->isLogEntry() && _parent->media() != this;
-}
-
-int HistoryWebPage::bottomInfoPadding() const {
-	if (!isBubbleBottom()) return 0;
-
-	auto result = st::msgDateFont->height;
-
-	// We use padding greater than st::msgPadding.bottom() in the
-	// bottom of the bubble so that the left line looks pretty.
-	// but if we have bottom skip because of the info display
-	// we don't need that additional padding so we replace it
-	// back with st::msgPadding.bottom() instead of left().
-	result += st::msgPadding.bottom() - st::msgPadding.left();
-	return result;
-}
-
-HistoryWebPage::~HistoryWebPage() {
-	Auth().data().unregisterWebPageView(_data, _parent);
-}
-
-HistoryGame::HistoryGame(
-	not_null<Element*> parent,
-	not_null<GameData*> data,
-	const TextWithEntities &consumed)
-: HistoryMedia(parent)
-, _data(data)
-, _title(st::msgMinWidth - st::webPageLeft)
-, _description(st::msgMinWidth - st::webPageLeft) {
-	if (!consumed.text.isEmpty()) {
-		_description.setMarkedText(
-			st::webPageDescriptionStyle,
-			consumed,
-			Ui::ItemTextOptions(parent->data()));
-	}
-	Auth().data().registerGameView(_data, _parent);
-}
-
-QSize HistoryGame::countOptimalSize() {
-	auto lineHeight = unitedLineHeight();
-
-	const auto item = _parent->data();
-	if (!_openl && IsServerMsgId(item->id)) {
-		const auto row = 0;
-		const auto column = 0;
-		_openl = std::make_shared<ReplyMarkupClickHandler>(
-			row,
-			column,
-			item->fullId());
-	}
-
-	auto title = TextUtilities::SingleLine(_data->title);
-
-	// init attach
-	if (!_attach) {
-		_attach = CreateAttach(_parent, _data->document, _data->photo);
-	}
-
-	// init strings
-	if (_description.isEmpty() && !_data->description.isEmpty()) {
-		auto text = _data->description;
-		if (!text.isEmpty()) {
-			if (!_attach) {
-				text += _parent->skipBlock();
-			}
-			auto marked = TextWithEntities { text };
-			auto parseFlags = TextParseLinks | TextParseMultiline | TextParseRichText;
-			TextUtilities::ParseEntities(marked, parseFlags);
-			_description.setMarkedText(
-				st::webPageDescriptionStyle,
-				marked,
-				Ui::WebpageTextDescriptionOptions());
-		}
-	}
-	if (_title.isEmpty() && !title.isEmpty()) {
-		_title.setText(
-			st::webPageTitleStyle,
-			title,
-			Ui::WebpageTextTitleOptions());
-	}
-
-	// init dimensions
-	auto l = st::msgPadding.left() + st::webPageLeft, r = st::msgPadding.right();
-	auto skipBlockWidth = _parent->skipBlockWidth();
-	auto maxWidth = skipBlockWidth;
-	auto minHeight = 0;
-
-	auto titleMinHeight = _title.isEmpty() ? 0 : lineHeight;
-	// enable any count of lines in game description / message
-	auto descMaxLines = 4096;
-	auto descriptionMinHeight = _description.isEmpty() ? 0 : qMin(_description.minHeight(), descMaxLines * lineHeight);
-
-	if (!_title.isEmpty()) {
-		accumulate_max(maxWidth, _title.maxWidth());
-		minHeight += titleMinHeight;
-	}
-	if (!_description.isEmpty()) {
-		accumulate_max(maxWidth, _description.maxWidth());
-		minHeight += descriptionMinHeight;
-	}
-	if (_attach) {
-		auto attachAtTop = !_titleLines && !_descriptionLines;
-		if (!attachAtTop) minHeight += st::mediaInBubbleSkip;
-
-		_attach->initDimensions();
-		QMargins bubble(_attach->bubbleMargins());
-		auto maxMediaWidth = _attach->maxWidth() - bubble.left() - bubble.right();
-		if (isBubbleBottom() && _attach->customInfoLayout()) {
-			maxMediaWidth += skipBlockWidth;
-		}
-		accumulate_max(maxWidth, maxMediaWidth);
-		minHeight += _attach->minHeight() - bubble.top() - bubble.bottom();
-	}
-	maxWidth += st::msgPadding.left() + st::webPageLeft + st::msgPadding.right();
-	auto padding = inBubblePadding();
-	minHeight += padding.top() + padding.bottom();
-
-	if (!_gameTagWidth) {
-		_gameTagWidth = st::msgDateFont->width(lang(lng_game_tag).toUpper());
-	}
-	return { maxWidth, minHeight };
-}
-
-void HistoryGame::refreshParentId(not_null<HistoryItem*> realParent) {
-	if (_openl) {
-		_openl->setMessageId(realParent->fullId());
-	}
-	if (_attach) {
-		_attach->refreshParentId(realParent);
-	}
-}
-
-QSize HistoryGame::countCurrentSize(int newWidth) {
-	accumulate_min(newWidth, maxWidth());
-	auto innerWidth = newWidth - st::msgPadding.left() - st::webPageLeft - st::msgPadding.right();
-
-	// enable any count of lines in game description / message
-	auto linesMax = 4096;
-	auto lineHeight = unitedLineHeight();
-	auto newHeight = 0;
-	if (_title.isEmpty()) {
-		_titleLines = 0;
-	} else {
-		if (_title.countHeight(innerWidth) < 2 * st::webPageTitleFont->height) {
-			_titleLines = 1;
-		} else {
-			_titleLines = 2;
-		}
-		newHeight += _titleLines * lineHeight;
-	}
-
-	if (_description.isEmpty()) {
-		_descriptionLines = 0;
-	} else {
-		auto descriptionHeight = _description.countHeight(innerWidth);
-		if (descriptionHeight < (linesMax - _titleLines) * st::webPageDescriptionFont->height) {
-			_descriptionLines = (descriptionHeight / st::webPageDescriptionFont->height);
-		} else {
-			_descriptionLines = (linesMax - _titleLines);
-		}
-		newHeight += _descriptionLines * lineHeight;
-	}
-
-	if (_attach) {
-		auto attachAtTop = !_titleLines && !_descriptionLines;
-		if (!attachAtTop) newHeight += st::mediaInBubbleSkip;
-
-		QMargins bubble(_attach->bubbleMargins());
-
-		_attach->resizeGetHeight(innerWidth + bubble.left() + bubble.right());
-		newHeight += _attach->height() - bubble.top() - bubble.bottom();
-		if (isBubbleBottom() && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > innerWidth + bubble.left() + bubble.right()) {
-			newHeight += bottomInfoPadding();
-		}
-	}
-	auto padding = inBubblePadding();
-	newHeight += padding.top() + padding.bottom();
-
-	return { newWidth, newHeight };
-}
-
-TextSelection HistoryGame::toDescriptionSelection(
-		TextSelection selection) const {
-	return HistoryView::UnshiftItemSelection(selection, _title);
-}
-
-TextSelection HistoryGame::fromDescriptionSelection(
-		TextSelection selection) const {
-	return HistoryView::ShiftItemSelection(selection, _title);
-}
-
-void HistoryGame::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const {
-	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
-	auto paintw = width(), painth = height();
-
-	auto outbg = _parent->hasOutLayout();
-	bool selected = (selection == FullSelection);
-
-	auto &barfg = selected ? (outbg ? st::msgOutReplyBarSelColor : st::msgInReplyBarSelColor) : (outbg ? st::msgOutReplyBarColor : st::msgInReplyBarColor);
-	auto &semibold = selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg);
-	auto &regular = selected ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) : (outbg ? st::msgOutDateFg : st::msgInDateFg);
-
-	QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins());
-	auto padding = inBubblePadding();
-	auto tshift = padding.top();
-	auto bshift = padding.bottom();
-	paintw -= padding.left() + padding.right();
-	if (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > paintw + bubble.left() + bubble.right()) {
-		bshift += bottomInfoPadding();
-	}
-
-	QRect bar(rtlrect(st::msgPadding.left(), tshift, st::webPageBar, height() - tshift - bshift, width()));
-	p.fillRect(bar, barfg);
-
-	auto lineHeight = unitedLineHeight();
-	if (_titleLines) {
-		p.setPen(semibold);
-		auto endskip = 0;
-		if (_title.hasSkipBlock()) {
-			endskip = _parent->skipBlockWidth();
-		}
-		_title.drawLeftElided(p, padding.left(), tshift, paintw, width(), _titleLines, style::al_left, 0, -1, endskip, false, selection);
-		tshift += _titleLines * lineHeight;
-	}
-	if (_descriptionLines) {
-		p.setPen(outbg ? st::webPageDescriptionOutFg : st::webPageDescriptionInFg);
-		auto endskip = 0;
-		if (_description.hasSkipBlock()) {
-			endskip = _parent->skipBlockWidth();
-		}
-		_description.drawLeftElided(p, padding.left(), tshift, paintw, width(), _descriptionLines, style::al_left, 0, -1, endskip, false, toDescriptionSelection(selection));
-		tshift += _descriptionLines * lineHeight;
-	}
-	if (_attach) {
-		auto attachAtTop = !_titleLines && !_descriptionLines;
-		if (!attachAtTop) tshift += st::mediaInBubbleSkip;
-
-		auto attachLeft = padding.left() - bubble.left();
-		auto attachTop = tshift - bubble.top();
-		if (rtl()) attachLeft = width() - attachLeft - _attach->width();
-
-		auto attachSelection = selected ? FullSelection : TextSelection { 0, 0 };
-
-		p.translate(attachLeft, attachTop);
-		_attach->draw(p, r.translated(-attachLeft, -attachTop), attachSelection, ms);
-		auto pixwidth = _attach->width();
-		auto pixheight = _attach->height();
-
-		auto gameW = _gameTagWidth + 2 * st::msgDateImgPadding.x();
-		auto gameH = st::msgDateFont->height + 2 * st::msgDateImgPadding.y();
-		auto gameX = pixwidth - st::msgDateImgDelta - gameW;
-		auto gameY = pixheight - st::msgDateImgDelta - gameH;
-
-		App::roundRect(p, rtlrect(gameX, gameY, gameW, gameH, pixwidth), selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners);
-
-		p.setFont(st::msgDateFont);
-		p.setPen(st::msgDateImgFg);
-		p.drawTextLeft(gameX + st::msgDateImgPadding.x(), gameY + st::msgDateImgPadding.y(), pixwidth, lang(lng_game_tag).toUpper());
-
-		p.translate(-attachLeft, -attachTop);
-	}
-}
-
-TextState HistoryGame::textState(QPoint point, StateRequest request) const {
-	auto result = TextState(_parent);
-
-	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) {
-		return result;
-	}
-	auto paintw = width(), painth = height();
-
-	QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins());
-	auto padding = inBubblePadding();
-	auto tshift = padding.top();
-	auto bshift = padding.bottom();
-	if (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > paintw + bubble.left() + bubble.right()) {
-		bshift += bottomInfoPadding();
-	}
-	paintw -= padding.left() + padding.right();
-
-	auto inThumb = false;
-	auto symbolAdd = 0;
-	auto lineHeight = unitedLineHeight();
-	if (_titleLines) {
-		if (point.y() >= tshift && point.y() < tshift + _titleLines * lineHeight) {
-			Text::StateRequestElided titleRequest = request.forText();
-			titleRequest.lines = _titleLines;
-			result = TextState(_parent, _title.getStateElidedLeft(
-				point - QPoint(padding.left(), tshift),
-				paintw,
-				width(),
-				titleRequest));
-		} else if (point.y() >= tshift + _titleLines * lineHeight) {
-			symbolAdd += _title.length();
-		}
-		tshift += _titleLines * lineHeight;
-	}
-	if (_descriptionLines) {
-		if (point.y() >= tshift && point.y() < tshift + _descriptionLines * lineHeight) {
-			Text::StateRequestElided descriptionRequest = request.forText();
-			descriptionRequest.lines = _descriptionLines;
-			result = TextState(_parent, _description.getStateElidedLeft(
-				point - QPoint(padding.left(), tshift),
-				paintw,
-				width(),
-				descriptionRequest));
-		} else if (point.y() >= tshift + _descriptionLines * lineHeight) {
-			symbolAdd += _description.length();
-		}
-		tshift += _descriptionLines * lineHeight;
-	}
-	if (inThumb) {
-		if (!_parent->data()->isLogEntry()) {
-			result.link = _openl;
-		}
-	} else if (_attach) {
-		auto attachAtTop = !_titleLines && !_descriptionLines;
-		if (!attachAtTop) tshift += st::mediaInBubbleSkip;
-
-		auto attachLeft = padding.left() - bubble.left();
-		auto attachTop = tshift - bubble.top();
-		if (rtl()) attachLeft = width() - attachLeft - _attach->width();
-
-		if (QRect(attachLeft, tshift, _attach->width(), height() - tshift - bshift).contains(point)) {
-			if (_attach->isReadyForOpen()) {
-				if (!_parent->data()->isLogEntry()) {
-					result.link = _openl;
-				}
-			} else {
-				result = _attach->textState(point - QPoint(attachLeft, attachTop), request);
-			}
-		}
-	}
-
-	result.symbol += symbolAdd;
-	return result;
-}
-
-TextSelection HistoryGame::adjustSelection(TextSelection selection, TextSelectType type) const {
-	if (!_descriptionLines || selection.to <= _title.length()) {
-		return _title.adjustSelection(selection, type);
-	}
-	auto descriptionSelection = _description.adjustSelection(toDescriptionSelection(selection), type);
-	if (selection.from >= _title.length()) {
-		return fromDescriptionSelection(descriptionSelection);
-	}
-	auto titleSelection = _title.adjustSelection(selection, type);
-	return { titleSelection.from, fromDescriptionSelection(descriptionSelection).to };
-}
-
-void HistoryGame::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
-	if (_attach) {
-		_attach->clickHandlerActiveChanged(p, active);
-	}
-}
-
-void HistoryGame::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) {
-	if (_attach) {
-		_attach->clickHandlerPressedChanged(p, pressed);
-	}
-}
-
-TextWithEntities HistoryGame::selectedText(TextSelection selection) const {
-	auto titleResult = _title.originalTextWithEntities(
-		selection,
-		ExpandLinksAll);
-	auto descriptionResult = _description.originalTextWithEntities(
-		toDescriptionSelection(selection),
-		ExpandLinksAll);
-	if (titleResult.text.isEmpty()) {
-		return descriptionResult;
-	} else if (descriptionResult.text.isEmpty()) {
-		return titleResult;
-	}
-
-	titleResult.text += '\n';
-	TextUtilities::Append(titleResult, std::move(descriptionResult));
-	return titleResult;
-}
-
-void HistoryGame::playAnimation(bool autoplay) {
-	if (_attach) {
-		if (autoplay) {
-			_attach->autoplayAnimation();
-		} else {
-			_attach->playAnimation();
-		}
-	}
-}
-
-QMargins HistoryGame::inBubblePadding() const {
-	auto lshift = st::msgPadding.left() + st::webPageLeft;
-	auto rshift = st::msgPadding.right();
-	auto bshift = isBubbleBottom() ? st::msgPadding.left() : st::mediaInBubbleSkip;
-	auto tshift = isBubbleTop() ? st::msgPadding.left() : st::mediaInBubbleSkip;
-	return QMargins(lshift, tshift, rshift, bshift);
-}
-
-int HistoryGame::bottomInfoPadding() const {
-	if (!isBubbleBottom()) return 0;
-
-	auto result = st::msgDateFont->height;
-
-	// we use padding greater than st::msgPadding.bottom() in the
-	// bottom of the bubble so that the left line looks pretty.
-	// but if we have bottom skip because of the info display
-	// we don't need that additional padding so we replace it
-	// back with st::msgPadding.bottom() instead of left().
-	result += st::msgPadding.bottom() - st::msgPadding.left();
-	return result;
-}
-
-void HistoryGame::parentTextUpdated() {
-	if (const auto media = _parent->data()->media()) {
-		const auto consumed = media->consumedMessageText();
-		if (!consumed.text.isEmpty()) {
-			_description.setMarkedText(
-				st::webPageDescriptionStyle,
-				consumed,
-				Ui::ItemTextOptions(_parent->data()));
-		} else {
-			_description = Text(st::msgMinWidth - st::webPageLeft);
-		}
-		Auth().data().requestViewResize(_parent);
-	}
-}
-
-HistoryGame::~HistoryGame() {
-	Auth().data().unregisterGameView(_data, _parent);
-}
-
-HistoryInvoice::HistoryInvoice(
-	not_null<Element*> parent,
-	not_null<Data::Invoice*> invoice)
-: HistoryMedia(parent)
-, _title(st::msgMinWidth)
-, _description(st::msgMinWidth)
-, _status(st::msgMinWidth) {
-	fillFromData(invoice);
-}
-
-void HistoryInvoice::fillFromData(not_null<Data::Invoice*> invoice) {
-	if (invoice->photo) {
-		_attach = std::make_unique<HistoryPhoto>(
-			_parent,
-			_parent->data(),
-			invoice->photo);
-	} else {
-		_attach = nullptr;
-	}
-	auto labelText = [&] {
-		if (invoice->receiptMsgId) {
-			if (invoice->isTest) {
-				return lang(lng_payments_receipt_label_test);
-			}
-			return lang(lng_payments_receipt_label);
-		} else if (invoice->isTest) {
-			return lang(lng_payments_invoice_label_test);
-		}
-		return lang(lng_payments_invoice_label);
-	};
-	auto statusText = TextWithEntities {
-		FillAmountAndCurrency(invoice->amount, invoice->currency),
-		EntitiesInText()
-	};
-	statusText.entities.push_back(EntityInText(EntityInTextBold, 0, statusText.text.size()));
-	statusText.text += ' ' + labelText().toUpper();
-	_status.setMarkedText(
-		st::defaultTextStyle,
-		statusText,
-		Ui::ItemTextOptions(_parent->data()));
-
-	_receiptMsgId = invoice->receiptMsgId;
-
-	// init strings
-	if (!invoice->description.isEmpty()) {
-		auto marked = TextWithEntities { invoice->description };
-		auto parseFlags = TextParseLinks | TextParseMultiline | TextParseRichText;
-		TextUtilities::ParseEntities(marked, parseFlags);
-		_description.setMarkedText(
-			st::webPageDescriptionStyle,
-			marked,
-			Ui::WebpageTextDescriptionOptions());
-	}
-	if (!invoice->title.isEmpty()) {
-		_title.setText(
-			st::webPageTitleStyle,
-			invoice->title,
-			Ui::WebpageTextTitleOptions());
-	}
-}
-
-QSize HistoryInvoice::countOptimalSize() {
-	auto lineHeight = unitedLineHeight();
-
-	if (_attach) {
-		if (_status.hasSkipBlock()) {
-			_status.removeSkipBlock();
-		}
-	} else {
-		_status.updateSkipBlock(
-			_parent->skipBlockWidth(),
-			_parent->skipBlockHeight());
-	}
-
-	// init dimensions
-	auto l = st::msgPadding.left(), r = st::msgPadding.right();
-	auto skipBlockWidth = _parent->skipBlockWidth();
-	auto maxWidth = skipBlockWidth;
-	auto minHeight = 0;
-
-	auto titleMinHeight = _title.isEmpty() ? 0 : lineHeight;
-	// enable any count of lines in game description / message
-	auto descMaxLines = 4096;
-	auto descriptionMinHeight = _description.isEmpty() ? 0 : qMin(_description.minHeight(), descMaxLines * lineHeight);
-
-	if (!_title.isEmpty()) {
-		accumulate_max(maxWidth, _title.maxWidth());
-		minHeight += titleMinHeight;
-	}
-	if (!_description.isEmpty()) {
-		accumulate_max(maxWidth, _description.maxWidth());
-		minHeight += descriptionMinHeight;
-	}
-	if (_attach) {
-		auto attachAtTop = _title.isEmpty() && _description.isEmpty();
-		if (!attachAtTop) minHeight += st::mediaInBubbleSkip;
-
-		_attach->initDimensions();
-		auto bubble = _attach->bubbleMargins();
-		auto maxMediaWidth = _attach->maxWidth() - bubble.left() - bubble.right();
-		if (isBubbleBottom() && _attach->customInfoLayout()) {
-			maxMediaWidth += skipBlockWidth;
-		}
-		accumulate_max(maxWidth, maxMediaWidth);
-		minHeight += _attach->minHeight() - bubble.top() - bubble.bottom();
-	} else {
-		accumulate_max(maxWidth, _status.maxWidth());
-		minHeight += st::mediaInBubbleSkip + _status.minHeight();
-	}
-	auto padding = inBubblePadding();
-	maxWidth += padding.left() + padding.right();
-	minHeight += padding.top() + padding.bottom();
-	return { maxWidth, minHeight };
-}
-
-QSize HistoryInvoice::countCurrentSize(int newWidth) {
-	accumulate_min(newWidth, maxWidth());
-	auto innerWidth = newWidth - st::msgPadding.left() - st::msgPadding.right();
-
-	auto lineHeight = unitedLineHeight();
-
-	auto newHeight = 0;
-	if (_title.isEmpty()) {
-		_titleHeight = 0;
-	} else {
-		if (_title.countHeight(innerWidth) < 2 * st::webPageTitleFont->height) {
-			_titleHeight = lineHeight;
-		} else {
-			_titleHeight = 2 * lineHeight;
-		}
-		newHeight += _titleHeight;
-	}
-
-	if (_description.isEmpty()) {
-		_descriptionHeight = 0;
-	} else {
-		_descriptionHeight = _description.countHeight(innerWidth);
-		newHeight += _descriptionHeight;
-	}
-
-	if (_attach) {
-		auto attachAtTop = !_title.isEmpty() && _description.isEmpty();
-		if (!attachAtTop) newHeight += st::mediaInBubbleSkip;
-
-		QMargins bubble(_attach->bubbleMargins());
-
-		_attach->resizeGetHeight(innerWidth + bubble.left() + bubble.right());
-		newHeight += _attach->height() - bubble.top() - bubble.bottom();
-		if (isBubbleBottom() && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > innerWidth + bubble.left() + bubble.right()) {
-			newHeight += bottomInfoPadding();
-		}
-	} else {
-		newHeight += st::mediaInBubbleSkip + _status.countHeight(innerWidth);
-	}
-	auto padding = inBubblePadding();
-	newHeight += padding.top() + padding.bottom();
-
-	return { newWidth, newHeight };
-}
-
-TextSelection HistoryInvoice::toDescriptionSelection(
-		TextSelection selection) const {
-	return HistoryView::UnshiftItemSelection(selection, _title);
-}
-
-TextSelection HistoryInvoice::fromDescriptionSelection(
-		TextSelection selection) const {
-	return HistoryView::ShiftItemSelection(selection, _title);
-}
-
-void HistoryInvoice::refreshParentId(not_null<HistoryItem*> realParent) {
-	if (_attach) {
-		_attach->refreshParentId(realParent);
-	}
-}
-
-void HistoryInvoice::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const {
-	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
-	auto paintw = width(), painth = height();
-
-	auto outbg = _parent->hasOutLayout();
-	bool selected = (selection == FullSelection);
-
-	auto &barfg = selected ? (outbg ? st::msgOutReplyBarSelColor : st::msgInReplyBarSelColor) : (outbg ? st::msgOutReplyBarColor : st::msgInReplyBarColor);
-	auto &semibold = selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg);
-	auto &regular = selected ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) : (outbg ? st::msgOutDateFg : st::msgInDateFg);
-
-	QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins());
-	auto padding = inBubblePadding();
-	auto tshift = padding.top();
-	auto bshift = padding.bottom();
-	paintw -= padding.left() + padding.right();
-	if (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > paintw + bubble.left() + bubble.right()) {
-		bshift += bottomInfoPadding();
-	}
-
-	auto lineHeight = unitedLineHeight();
-	if (_titleHeight) {
-		p.setPen(semibold);
-		p.setTextPalette(selected ? (outbg ? st::outTextPaletteSelected : st::inTextPaletteSelected) : (outbg ? st::outSemiboldPalette : st::inSemiboldPalette));
-
-		auto endskip = 0;
-		if (_title.hasSkipBlock()) {
-			endskip = _parent->skipBlockWidth();
-		}
-		_title.drawLeftElided(p, padding.left(), tshift, paintw, width(), _titleHeight / lineHeight, style::al_left, 0, -1, endskip, false, selection);
-		tshift += _titleHeight;
-
-		p.setTextPalette(selected ? (outbg ? st::outTextPaletteSelected : st::inTextPaletteSelected) : (outbg ? st::outTextPalette : st::inTextPalette));
-	}
-	if (_descriptionHeight) {
-		p.setPen(outbg ? st::webPageDescriptionOutFg : st::webPageDescriptionInFg);
-		_description.drawLeft(p, padding.left(), tshift, paintw, width(), style::al_left, 0, -1, toDescriptionSelection(selection));
-		tshift += _descriptionHeight;
-	}
-	if (_attach) {
-		auto attachAtTop = !_titleHeight && !_descriptionHeight;
-		if (!attachAtTop) tshift += st::mediaInBubbleSkip;
-
-		auto attachLeft = padding.left() - bubble.left();
-		auto attachTop = tshift - bubble.top();
-		if (rtl()) attachLeft = width() - attachLeft - _attach->width();
-
-		auto attachSelection = selected ? FullSelection : TextSelection { 0, 0 };
-
-		p.translate(attachLeft, attachTop);
-		_attach->draw(p, r.translated(-attachLeft, -attachTop), attachSelection, ms);
-		auto pixwidth = _attach->width();
-		auto pixheight = _attach->height();
-
-		auto available = _status.maxWidth();
-		auto statusW = available + 2 * st::msgDateImgPadding.x();
-		auto statusH = st::msgDateFont->height + 2 * st::msgDateImgPadding.y();
-		auto statusX = st::msgDateImgDelta;
-		auto statusY = st::msgDateImgDelta;
-
-		App::roundRect(p, rtlrect(statusX, statusY, statusW, statusH, pixwidth), selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners);
-
-		p.setFont(st::msgDateFont);
-		p.setPen(st::msgDateImgFg);
-		_status.drawLeftElided(p, statusX + st::msgDateImgPadding.x(), statusY + st::msgDateImgPadding.y(), available, pixwidth);
-
-		p.translate(-attachLeft, -attachTop);
-	} else {
-		p.setPen(outbg ? st::webPageDescriptionOutFg : st::webPageDescriptionInFg);
-		_status.drawLeft(p, padding.left(), tshift + st::mediaInBubbleSkip, paintw, width());
-	}
-}
-
-TextState HistoryInvoice::textState(QPoint point, StateRequest request) const {
-	auto result = TextState(_parent);
-
-	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) {
-		return result;
-	}
-	auto paintw = width(), painth = height();
-
-	QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins());
-	auto padding = inBubblePadding();
-	auto tshift = padding.top();
-	auto bshift = padding.bottom();
-	if (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > paintw + bubble.left() + bubble.right()) {
-		bshift += bottomInfoPadding();
-	}
-	paintw -= padding.left() + padding.right();
-
-	auto lineHeight = unitedLineHeight();
-	auto symbolAdd = 0;
-	if (_titleHeight) {
-		if (point.y() >= tshift && point.y() < tshift + _titleHeight) {
-			Text::StateRequestElided titleRequest = request.forText();
-			titleRequest.lines = _titleHeight / lineHeight;
-			result = TextState(_parent, _title.getStateElidedLeft(
-				point - QPoint(padding.left(), tshift),
-				paintw,
-				width(),
-				titleRequest));
-		} else if (point.y() >= tshift + _titleHeight) {
-			symbolAdd += _title.length();
-		}
-		tshift += _titleHeight;
-	}
-	if (_descriptionHeight) {
-		if (point.y() >= tshift && point.y() < tshift + _descriptionHeight) {
-			result = TextState(_parent, _description.getStateLeft(
-				point - QPoint(padding.left(), tshift),
-				paintw,
-				width(),
-				request.forText()));
-		} else if (point.y() >= tshift + _descriptionHeight) {
-			symbolAdd += _description.length();
-		}
-		tshift += _descriptionHeight;
-	}
-	if (_attach) {
-		auto attachAtTop = !_titleHeight && !_descriptionHeight;
-		if (!attachAtTop) tshift += st::mediaInBubbleSkip;
-
-		auto attachLeft = padding.left() - bubble.left();
-		auto attachTop = tshift - bubble.top();
-		if (rtl()) attachLeft = width() - attachLeft - _attach->width();
-
-		if (QRect(attachLeft, tshift, _attach->width(), height() - tshift - bshift).contains(point)) {
-			result = _attach->textState(point - QPoint(attachLeft, attachTop), request);
-		}
-	}
-
-	result.symbol += symbolAdd;
-	return result;
-}
-
-TextSelection HistoryInvoice::adjustSelection(TextSelection selection, TextSelectType type) const {
-	if (!_descriptionHeight || selection.to <= _title.length()) {
-		return _title.adjustSelection(selection, type);
-	}
-	auto descriptionSelection = _description.adjustSelection(toDescriptionSelection(selection), type);
-	if (selection.from >= _title.length()) {
-		return fromDescriptionSelection(descriptionSelection);
-	}
-	auto titleSelection = _title.adjustSelection(selection, type);
-	return { titleSelection.from, fromDescriptionSelection(descriptionSelection).to };
-}
-
-void HistoryInvoice::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
-	if (_attach) {
-		_attach->clickHandlerActiveChanged(p, active);
-	}
-}
-
-void HistoryInvoice::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) {
-	if (_attach) {
-		_attach->clickHandlerPressedChanged(p, pressed);
-	}
-}
-
-TextWithEntities HistoryInvoice::selectedText(TextSelection selection) const {
-	auto titleResult = _title.originalTextWithEntities(
-		selection,
-		ExpandLinksAll);
-	auto descriptionResult = _description.originalTextWithEntities(
-		toDescriptionSelection(selection),
-		ExpandLinksAll);
-	if (titleResult.text.isEmpty()) {
-		return descriptionResult;
-	} else if (descriptionResult.text.isEmpty()) {
-		return titleResult;
-	}
-
-	titleResult.text += '\n';
-	TextUtilities::Append(titleResult, std::move(descriptionResult));
-	return titleResult;
-}
-
-QMargins HistoryInvoice::inBubblePadding() const {
-	auto lshift = st::msgPadding.left();
-	auto rshift = st::msgPadding.right();
-	auto bshift = isBubbleBottom() ? st::msgPadding.top() : st::mediaInBubbleSkip;
-	auto tshift = isBubbleTop() ? st::msgPadding.bottom() : st::mediaInBubbleSkip;
-	return QMargins(lshift, tshift, rshift, bshift);
-}
-
-int HistoryInvoice::bottomInfoPadding() const {
-	if (!isBubbleBottom()) return 0;
-
-	auto result = st::msgDateFont->height;
-	return result;
-}
-
-HistoryLocation::HistoryLocation(
-	not_null<Element*> parent,
-	not_null<LocationData*> location,
-	const QString &title,
-	const QString &description)
-: HistoryMedia(parent)
-, _data(location)
-, _title(st::msgMinWidth)
-, _description(st::msgMinWidth)
-, _link(std::make_shared<LocationClickHandler>(_data->coords)) {
-	if (!title.isEmpty()) {
-		_title.setText(
-			st::webPageTitleStyle,
-			TextUtilities::Clean(title),
-			Ui::WebpageTextTitleOptions());
-	}
-	if (!description.isEmpty()) {
-		_description.setMarkedText(
-			st::webPageDescriptionStyle,
-			TextUtilities::ParseEntities(
-				TextUtilities::Clean(description),
-				TextParseLinks | TextParseMultiline | TextParseRichText),
-			Ui::WebpageTextDescriptionOptions());
-	}
-}
-
-QSize HistoryLocation::countOptimalSize() {
-	auto tw = fullWidth();
-	auto th = fullHeight();
-	if (tw > st::maxMediaSize) {
-		th = (st::maxMediaSize * th) / tw;
-		tw = st::maxMediaSize;
-	}
-	auto minWidth = qMax(st::minPhotoSize, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x()));
-	auto maxWidth = qMax(tw, minWidth);
-	auto minHeight = qMax(th, st::minPhotoSize);
-
-	if (_parent->hasBubble()) {
-		if (!_title.isEmpty()) {
-			minHeight += qMin(_title.countHeight(maxWidth - st::msgPadding.left() - st::msgPadding.right()), 2 * st::webPageTitleFont->height);
-		}
-		if (!_description.isEmpty()) {
-			minHeight += qMin(_description.countHeight(maxWidth - st::msgPadding.left() - st::msgPadding.right()), 3 * st::webPageDescriptionFont->height);
-		}
-		if (!_title.isEmpty() || !_description.isEmpty()) {
-			minHeight += st::mediaInBubbleSkip;
-			if (isBubbleTop()) {
-				minHeight += st::msgPadding.top();
-			}
-		}
-	}
-	return { maxWidth, minHeight };
-}
-
-QSize HistoryLocation::countCurrentSize(int newWidth) {
-	accumulate_min(newWidth, maxWidth());
-
-	auto tw = fullWidth();
-	auto th = fullHeight();
-	if (tw > st::maxMediaSize) {
-		th = (st::maxMediaSize * th) / tw;
-		tw = st::maxMediaSize;
-	}
-	auto newHeight = th;
-	if (tw > newWidth) {
-		newHeight = (newWidth * newHeight / tw);
-	} else {
-		newWidth = tw;
-	}
-	auto minWidth = qMax(st::minPhotoSize, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x()));
-	accumulate_max(newWidth, minWidth);
-	accumulate_max(newHeight, st::minPhotoSize);
-	if (_parent->hasBubble()) {
-		if (!_title.isEmpty()) {
-			newHeight += qMin(_title.countHeight(newWidth - st::msgPadding.left() - st::msgPadding.right()), st::webPageTitleFont->height * 2);
-		}
-		if (!_description.isEmpty()) {
-			newHeight += qMin(_description.countHeight(newWidth - st::msgPadding.left() - st::msgPadding.right()), st::webPageDescriptionFont->height * 3);
-		}
-		if (!_title.isEmpty() || !_description.isEmpty()) {
-			newHeight += st::mediaInBubbleSkip;
-			if (isBubbleTop()) {
-				newHeight += st::msgPadding.top();
-			}
-		}
-	}
-	return { newWidth, newHeight };
-}
-
-TextSelection HistoryLocation::toDescriptionSelection(
-		TextSelection selection) const {
-	return HistoryView::UnshiftItemSelection(selection, _title);
-}
-
-TextSelection HistoryLocation::fromDescriptionSelection(
-		TextSelection selection) const {
-	return HistoryView::ShiftItemSelection(selection, _title);
-}
-
-void HistoryLocation::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const {
-	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
-	auto paintx = 0, painty = 0, paintw = width(), painth = height();
-	bool bubble = _parent->hasBubble();
-	auto outbg = _parent->hasOutLayout();
-	bool selected = (selection == FullSelection);
-
-	if (bubble) {
-		if (!_title.isEmpty() || !_description.isEmpty()) {
-			if (isBubbleTop()) {
-				painty += st::msgPadding.top();
-			}
-		}
-
-		auto textw = width() - st::msgPadding.left() - st::msgPadding.right();
-
-		if (!_title.isEmpty()) {
-			p.setPen(outbg ? st::webPageTitleOutFg : st::webPageTitleInFg);
-			_title.drawLeftElided(p, paintx + st::msgPadding.left(), painty, textw, width(), 2, style::al_left, 0, -1, 0, false, selection);
-			painty += qMin(_title.countHeight(textw), 2 * st::webPageTitleFont->height);
-		}
-		if (!_description.isEmpty()) {
-			p.setPen(outbg ? st::webPageDescriptionOutFg : st::webPageDescriptionInFg);
-			_description.drawLeftElided(p, paintx + st::msgPadding.left(), painty, textw, width(), 3, style::al_left, 0, -1, 0, false, toDescriptionSelection(selection));
-			painty += qMin(_description.countHeight(textw), 3 * st::webPageDescriptionFont->height);
-		}
-		if (!_title.isEmpty() || !_description.isEmpty()) {
-			painty += st::mediaInBubbleSkip;
-		}
-		painth -= painty;
-	} else {
-		App::roundShadow(p, 0, 0, paintw, painth, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners);
-	}
-
-	const auto contextId = _parent->data()->fullId();
-	_data->load(contextId);
-	auto roundRadius = ImageRoundRadius::Large;
-	auto roundCorners = ((isBubbleTop() && _title.isEmpty() && _description.isEmpty()) ? (RectPart::TopLeft | RectPart::TopRight) : RectPart::None)
-		| (isBubbleBottom() ? (RectPart::BottomLeft | RectPart::BottomRight) : RectPart::None);
-	auto rthumb = QRect(paintx, painty, paintw, painth);
-	if (_data && !_data->thumb->isNull()) {
-		const auto &pix = _data->thumb->pixSingle(contextId, paintw, painth, paintw, painth, roundRadius, roundCorners);
-		p.drawPixmap(rthumb.topLeft(), pix);
-	} else {
-		App::complexLocationRect(p, rthumb, roundRadius, roundCorners);
-	}
-	const auto paintMarker = [&](const style::icon &icon) {
-		icon.paint(
-			p,
-			rthumb.x() + ((rthumb.width() - icon.width()) / 2),
-			rthumb.y() + (rthumb.height() / 2) - icon.height(),
-			width());
-	};
-	paintMarker(st::historyMapPoint);
-	paintMarker(st::historyMapPointInner);
-	if (selected) {
-		App::complexOverlayRect(p, rthumb, roundRadius, roundCorners);
-	}
-
-	if (_parent->media() == this) {
-		auto fullRight = paintx + paintw;
-		auto fullBottom = height();
-		_parent->drawInfo(p, fullRight, fullBottom, paintx * 2 + paintw, selected, InfoDisplayType::Image);
-		if (!bubble && _parent->displayRightAction()) {
-			auto fastShareLeft = (fullRight + st::historyFastShareLeft);
-			auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize);
-			_parent->drawRightAction(p, fastShareLeft, fastShareTop, 2 * paintx + paintw);
-		}
-	}
-}
-
-TextState HistoryLocation::textState(QPoint point, StateRequest request) const {
-	auto result = TextState(_parent);
-	auto symbolAdd = 0;
-
-	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) {
-		return result;
-	}
-	auto paintx = 0, painty = 0, paintw = width(), painth = height();
-	bool bubble = _parent->hasBubble();
-
-	if (bubble) {
-		if (!_title.isEmpty() || !_description.isEmpty()) {
-			if (isBubbleTop()) {
-				painty += st::msgPadding.top();
-			}
-		}
-
-		auto textw = width() - st::msgPadding.left() - st::msgPadding.right();
-
-		if (!_title.isEmpty()) {
-			auto titleh = qMin(_title.countHeight(textw), 2 * st::webPageTitleFont->height);
-			if (point.y() >= painty && point.y() < painty + titleh) {
-				result = TextState(_parent, _title.getStateLeft(
-					point - QPoint(paintx + st::msgPadding.left(), painty),
-					textw,
-					width(),
-					request.forText()));
-				return result;
-			} else if (point.y() >= painty + titleh) {
-				symbolAdd += _title.length();
-			}
-			painty += titleh;
-		}
-		if (!_description.isEmpty()) {
-			auto descriptionh = qMin(_description.countHeight(textw), 3 * st::webPageDescriptionFont->height);
-			if (point.y() >= painty && point.y() < painty + descriptionh) {
-				result = TextState(_parent, _description.getStateLeft(
-					point - QPoint(paintx + st::msgPadding.left(), painty),
-					textw,
-					width(),
-					request.forText()));
-			} else if (point.y() >= painty + descriptionh) {
-				symbolAdd += _description.length();
-			}
-			painty += descriptionh;
-		}
-		if (!_title.isEmpty() || !_description.isEmpty()) {
-			painty += st::mediaInBubbleSkip;
-		}
-		painth -= painty;
-	}
-	if (QRect(paintx, painty, paintw, painth).contains(point) && _data) {
-		result.link = _link;
-	}
-	if (_parent->media() == this) {
-		auto fullRight = paintx + paintw;
-		auto fullBottom = height();
-		if (_parent->pointInTime(fullRight, fullBottom, point, InfoDisplayType::Image)) {
-			result.cursor = CursorState::Date;
-		}
-		if (!bubble && _parent->displayRightAction()) {
-			auto fastShareLeft = (fullRight + st::historyFastShareLeft);
-			auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize);
-			if (QRect(fastShareLeft, fastShareTop, st::historyFastShareSize, st::historyFastShareSize).contains(point)) {
-				result.link = _parent->rightActionLink();
-			}
-		}
-	}
-	result.symbol += symbolAdd;
-	return result;
-}
-
-TextSelection HistoryLocation::adjustSelection(TextSelection selection, TextSelectType type) const {
-	if (_description.isEmpty() || selection.to <= _title.length()) {
-		return _title.adjustSelection(selection, type);
-	}
-	auto descriptionSelection = _description.adjustSelection(toDescriptionSelection(selection), type);
-	if (selection.from >= _title.length()) {
-		return fromDescriptionSelection(descriptionSelection);
-	}
-	auto titleSelection = _title.adjustSelection(selection, type);
-	return { titleSelection.from, fromDescriptionSelection(descriptionSelection).to };
-}
-
-TextWithEntities HistoryLocation::selectedText(TextSelection selection) const {
-	auto titleResult = _title.originalTextWithEntities(selection);
-	auto descriptionResult = _description.originalTextWithEntities(toDescriptionSelection(selection));
-	if (titleResult.text.isEmpty()) {
-		return descriptionResult;
-	} else if (descriptionResult.text.isEmpty()) {
-		return titleResult;
-	}
-	titleResult.text += '\n';
-	TextUtilities::Append(titleResult, std::move(descriptionResult));
-	return titleResult;
-}
-
-bool HistoryLocation::needsBubble() const {
-	if (!_title.isEmpty() || !_description.isEmpty()) {
-		return true;
-	}
-	const auto item = _parent->data();
-	return item->viaBot()
-		|| item->Has<HistoryMessageReply>()
-		|| _parent->displayForwardedFrom()
-		|| _parent->displayFromName();
-	return false;
-}
-
-int HistoryLocation::fullWidth() const {
-	return st::locationSize.width();
-}
-
-int HistoryLocation::fullHeight() const {
-	return st::locationSize.height();
-}
diff --git a/Telegram/SourceFiles/history/history_media_types.h b/Telegram/SourceFiles/history/history_media_types.h
deleted file mode 100644
index 01d20e2e2..000000000
--- a/Telegram/SourceFiles/history/history_media_types.h
+++ /dev/null
@@ -1,990 +0,0 @@
-/*
-This file is part of Telegram Desktop,
-the official desktop application for the Telegram messaging service.
-
-For license and copyright information please follow this link:
-https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
-*/
-#pragma once
-
-#include "base/runtime_composer.h"
-#include "history/history_media.h"
-#include "ui/effects/radial_animation.h"
-#include "data/data_document.h"
-#include "data/data_photo.h"
-#include "data/data_web_page.h"
-#include "data/data_game.h"
-
-class ReplyMarkupClickHandler;
-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 {
-namespace Clip {
-class Playback;
-} // namespace Clip
-
-namespace Player {
-class RoundController;
-} // namespace Player
-} // namespace Media
-
-namespace Ui {
-class EmptyUserpic;
-} // namespace Ui
-
-QString FillAmountAndCurrency(uint64 amount, const QString &currency);
-
-class HistoryFileMedia : public HistoryMedia {
-public:
-	HistoryFileMedia(
-		not_null<Element*> parent,
-		not_null<HistoryItem*> realParent)
-	: HistoryMedia(parent)
-	, _realParent(realParent) {
-	}
-
-	bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override {
-		return p == _openl || p == _savel || p == _cancell;
-	}
-	bool dragItemByHandler(const ClickHandlerPtr &p) const override {
-		return p == _openl || p == _savel || p == _cancell;
-	}
-
-	void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
-	void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
-
-	void refreshParentId(not_null<HistoryItem*> realParent) override;
-
-	bool allowsFastShare() const override {
-		return true;
-	}
-
-	~HistoryFileMedia();
-
-protected:
-	using FileClickHandlerPtr = std::shared_ptr<FileClickHandler>;
-
-	not_null<HistoryItem*> _realParent;
-	FileClickHandlerPtr _openl, _savel, _cancell;
-
-	void setLinks(
-		FileClickHandlerPtr &&openl,
-		FileClickHandlerPtr &&savel,
-		FileClickHandlerPtr &&cancell);
-	void setDocumentLinks(
-		not_null<DocumentData*> document,
-		not_null<HistoryItem*> realParent,
-		bool inlinegif = false);
-
-	// >= 0 will contain download / upload string, _statusSize = loaded bytes
-	// < 0 will contain played string, _statusSize = -(seconds + 1) played
-	// 0x7FFFFFF0 will contain status for not yet downloaded file
-	// 0x7FFFFFF1 will contain status for already downloaded file
-	// 0x7FFFFFF2 will contain status for failed to download / upload file
-	mutable int _statusSize;
-	mutable QString _statusText;
-
-	// duration = -1 - no duration, duration = -2 - "GIF" duration
-	void setStatusSize(int newSize, int fullSize, int duration, qint64 realDuration) const;
-
-	void step_radial(TimeMs ms, bool timer);
-	void thumbAnimationCallback();
-
-	void ensureAnimation() const;
-	void checkAnimationFinished() const;
-
-	bool isRadialAnimation(TimeMs ms) const {
-		if (!_animation || !_animation->radial.animating()) return false;
-
-		_animation->radial.step(ms);
-		return _animation && _animation->radial.animating();
-	}
-	bool isThumbAnimation(TimeMs ms) const {
-		if (_animation) {
-			if (_animation->a_thumbOver.animating(ms)) {
-				return true;
-			}
-			checkAnimationFinished();
-		}
-		return false;
-	}
-
-	virtual float64 dataProgress() const = 0;
-	virtual bool dataFinished() const = 0;
-	virtual bool dataLoaded() const = 0;
-
-	struct AnimationData {
-		AnimationData(AnimationCallbacks &&radialCallbacks)
-			: radial(std::move(radialCallbacks)) {
-		}
-		Animation a_thumbOver;
-		Ui::RadialAnimation radial;
-	};
-	mutable std::unique_ptr<AnimationData> _animation;
-
-};
-
-class HistoryPhoto : public HistoryFileMedia {
-public:
-	HistoryPhoto(
-		not_null<Element*> parent,
-		not_null<HistoryItem*> realParent,
-		not_null<PhotoData*> photo);
-	HistoryPhoto(
-		not_null<Element*> parent,
-		not_null<PeerData*> chat,
-		not_null<PhotoData*> photo,
-		int width);
-
-	HistoryMediaType type() const override {
-		return MediaTypePhoto;
-	}
-
-	void draw(Painter &p, const QRect &clip, TextSelection selection, TimeMs ms) const override;
-	TextState textState(QPoint point, StateRequest request) const override;
-
-	[[nodiscard]] TextSelection adjustSelection(
-			TextSelection selection,
-			TextSelectType type) const override {
-		return _caption.adjustSelection(selection, type);
-	}
-	uint16 fullSelectionLength() const override {
-		return _caption.length();
-	}
-	bool hasTextForCopy() const override {
-		return !_caption.isEmpty();
-	}
-
-	TextWithEntities selectedText(TextSelection selection) const override;
-
-	PhotoData *getPhoto() const override {
-		return _data;
-	}
-
-	QSize sizeForGrouping() const override;
-	void drawGrouped(
-		Painter &p,
-		const QRect &clip,
-		TextSelection selection,
-		TimeMs 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;
-
-	TextWithEntities getCaption() const override {
-		return _caption.originalTextWithEntities();
-	}
-	bool needsBubble() const override;
-	bool customInfoLayout() const override {
-		return _caption.isEmpty();
-	}
-	bool skipBubbleTail() const override {
-		return isBubbleBottom() && _caption.isEmpty();
-	}
-	bool isReadyForOpen() const override {
-		return _data->loaded();
-	}
-
-	void parentTextUpdated() override;
-
-protected:
-	float64 dataProgress() const override;
-	bool dataFinished() const override;
-	bool dataLoaded() const override;
-
-private:
-	void create(FullMsgId contextId, PeerData *chat = nullptr);
-
-	QSize countOptimalSize() override;
-	QSize countCurrentSize(int newWidth) override;
-
-	bool needInfoDisplay() const;
-	void validateGroupedCache(
-		const QRect &geometry,
-		RectParts corners,
-		not_null<uint64*> cacheKey,
-		not_null<QPixmap*> cache) const;
-
-	not_null<PhotoData*> _data;
-	int _serviceWidth = 0;
-	int _pixw = 1;
-	int _pixh = 1;
-	Text _caption;
-
-};
-
-class HistoryVideo : public HistoryFileMedia {
-public:
-	HistoryVideo(
-		not_null<Element*> parent,
-		not_null<HistoryItem*> realParent,
-		not_null<DocumentData*> document);
-
-	HistoryMediaType type() const override {
-		return MediaTypeVideo;
-	}
-
-	void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override;
-	TextState textState(QPoint point, StateRequest request) const override;
-
-	[[nodiscard]] TextSelection adjustSelection(
-			TextSelection selection,
-			TextSelectType type) const override {
-		return _caption.adjustSelection(selection, type);
-	}
-	uint16 fullSelectionLength() const override {
-		return _caption.length();
-	}
-	bool hasTextForCopy() const override {
-		return !_caption.isEmpty();
-	}
-
-	TextWithEntities selectedText(TextSelection selection) const override;
-
-	DocumentData *getDocument() const override {
-		return _data;
-	}
-
-	QSize sizeForGrouping() const override;
-	void drawGrouped(
-		Painter &p,
-		const QRect &clip,
-		TextSelection selection,
-		TimeMs 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;
-
-	bool uploading() const override {
-		return _data->uploading();
-	}
-
-	TextWithEntities getCaption() const override {
-		return _caption.originalTextWithEntities();
-	}
-	bool needsBubble() const override;
-	bool customInfoLayout() const override {
-		return _caption.isEmpty();
-	}
-	bool skipBubbleTail() const override {
-		return isBubbleBottom() && _caption.isEmpty();
-	}
-
-	void parentTextUpdated() override;
-
-protected:
-	float64 dataProgress() const override;
-	bool dataFinished() const override;
-	bool dataLoaded() const override;
-
-private:
-	QSize countOptimalSize() override;
-	QSize countCurrentSize(int newWidth) override;
-
-	void validateGroupedCache(
-		const QRect &geometry,
-		RectParts corners,
-		not_null<uint64*> cacheKey,
-		not_null<QPixmap*> cache) const;
-	void setStatusSize(int newSize) const;
-	void updateStatusText() const;
-
-	not_null<DocumentData*> _data;
-	int _thumbw = 1;
-	int _thumbh = 1;
-	Text _caption;
-
-};
-
-class HistoryDocument
-	: public HistoryFileMedia
-	, public RuntimeComposer<HistoryDocument> {
-public:
-	HistoryDocument(
-		not_null<Element*> parent,
-		not_null<DocumentData*> document);
-
-	HistoryMediaType type() const override {
-		return _data->isVoiceMessage()
-			? MediaTypeVoiceFile
-			: (_data->isSong()
-				? MediaTypeMusicFile
-				: MediaTypeFile);
-	}
-
-	void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override;
-	TextState textState(QPoint point, StateRequest request) const override;
-	void updatePressed(QPoint point) override;
-
-	[[nodiscard]] TextSelection adjustSelection(
-		TextSelection selection,
-		TextSelectType type) const override;
-	uint16 fullSelectionLength() const override;
-	bool hasTextForCopy() const override;
-
-	TextWithEntities selectedText(TextSelection selection) const override;
-
-	bool uploading() const override {
-		return _data->uploading();
-	}
-
-	DocumentData *getDocument() const override {
-		return _data;
-	}
-
-	TextWithEntities getCaption() const override;
-	bool needsBubble() const override {
-		return true;
-	}
-	bool customInfoLayout() const override {
-		return false;
-	}
-	QMargins bubbleMargins() const override;
-	bool hideForwardedFrom() const override {
-		return _data->isSong();
-	}
-
-	void step_voiceProgress(float64 ms, bool timer);
-
-	void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
-
-	void refreshParentId(not_null<HistoryItem*> realParent) override;
-	void parentTextUpdated() override;
-
-protected:
-	float64 dataProgress() const override;
-	bool dataFinished() const override;
-	bool dataLoaded() const override;
-
-private:
-	QSize countOptimalSize() override;
-	QSize countCurrentSize(int newWidth) override;
-
-	void createComponents(bool caption);
-	void fillNamedFromData(HistoryDocumentNamed *named);
-
-	void setStatusSize(int newSize, qint64 realDuration = 0) const;
-	bool updateStatusText() const; // returns showPause
-
-	not_null<DocumentData*> _data;
-
-};
-
-class HistoryGif : public HistoryFileMedia {
-public:
-	HistoryGif(
-		not_null<Element*> parent,
-		not_null<DocumentData*> document);
-
-	HistoryMediaType type() const override {
-		return MediaTypeGif;
-	}
-
-	void refreshParentId(not_null<HistoryItem*> realParent) override;
-
-	void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override;
-	TextState textState(QPoint point, StateRequest request) const override;
-
-	[[nodiscard]] TextSelection adjustSelection(
-			TextSelection selection,
-			TextSelectType type) const override {
-		return _caption.adjustSelection(selection, type);
-	}
-	uint16 fullSelectionLength() const override {
-		return _caption.length();
-	}
-	bool hasTextForCopy() const override {
-		return !_caption.isEmpty();
-	}
-
-	TextWithEntities selectedText(TextSelection selection) const override;
-
-	bool uploading() const override {
-		return _data->uploading();
-	}
-
-	DocumentData *getDocument() const override {
-		return _data;
-	}
-
-	void stopAnimation() override;
-
-	TextWithEntities getCaption() const override {
-		return _caption.originalTextWithEntities();
-	}
-	bool needsBubble() const override;
-	bool customInfoLayout() const override {
-		return _caption.isEmpty();
-	}
-	QString additionalInfoString() const override;
-
-	bool skipBubbleTail() const override {
-		return isBubbleBottom() && _caption.isEmpty();
-	}
-	bool isReadyForOpen() const override {
-		return _data->loaded();
-	}
-
-	void parentTextUpdated() override;
-
-	~HistoryGif();
-
-protected:
-	float64 dataProgress() const override;
-	bool dataFinished() const override;
-	bool dataLoaded() const override;
-
-	void setClipReader(Media::Clip::ReaderPointer gif);
-	void clearClipReader() {
-		setClipReader(Media::Clip::ReaderPointer());
-	}
-
-private:
-	void playAnimation(bool autoplay) override;
-	QSize countOptimalSize() override;
-	QSize countCurrentSize(int newWidth) override;
-	Media::Player::RoundController *activeRoundVideo() const;
-	Media::Clip::Reader *activeRoundPlayer() const;
-	Media::Clip::Reader *currentReader() const;
-	Media::Clip::Playback *videoPlayback() const;
-	void clipCallback(Media::Clip::Notification notification);
-
-	bool needInfoDisplay() const;
-	int additionalWidth(
-		const HistoryMessageVia *via,
-		const HistoryMessageReply *reply,
-		const HistoryMessageForwarded *forwarded) const;
-	int additionalWidth() const;
-	QString mediaTypeString() const;
-	bool isSeparateRoundVideo() const;
-
-	not_null<DocumentData*> _data;
-	FileClickHandlerPtr _openInMediaviewLink;
-	int _thumbw = 1;
-	int _thumbh = 1;
-	Text _caption;
-	Media::Clip::ReaderPointer _gif;
-
-	void setStatusSize(int newSize) const;
-	void updateStatusText() const;
-
-};
-
-class HistorySticker : public HistoryMedia {
-public:
-	HistorySticker(
-		not_null<Element*> parent,
-		not_null<DocumentData*> document);
-
-	HistoryMediaType type() const override {
-		return MediaTypeSticker;
-	}
-
-	void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override;
-	TextState textState(QPoint point, StateRequest request) const override;
-
-	bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override {
-		return true;
-	}
-	bool dragItem() const override {
-		return true;
-	}
-	bool dragItemByHandler(const ClickHandlerPtr &p) const override {
-		return true;
-	}
-
-	DocumentData *getDocument() const override {
-		return _data;
-	}
-
-	bool needsBubble() const override {
-		return false;
-	}
-	bool customInfoLayout() const override {
-		return true;
-	}
-	QString emoji() const {
-		return _emoji;
-	}
-
-private:
-	QSize countOptimalSize() override;
-	QSize countCurrentSize(int newWidth) override;
-
-	bool needInfoDisplay() const;
-	int additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply *reply) const;
-	int additionalWidth() const;
-
-	int _pixw = 1;
-	int _pixh = 1;
-	ClickHandlerPtr _packLink;
-	not_null<DocumentData*> _data;
-	QString _emoji;
-
-};
-
-class HistoryContact : public HistoryMedia {
-public:
-	HistoryContact(
-		not_null<Element*> parent,
-		UserId userId,
-		const QString &first,
-		const QString &last,
-		const QString &phone);
-	~HistoryContact();
-
-	HistoryMediaType type() const override {
-		return MediaTypeContact;
-	}
-
-	void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override;
-	TextState textState(QPoint point, StateRequest request) const override;
-
-	bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override {
-		return true;
-	}
-	bool dragItemByHandler(const ClickHandlerPtr &p) const override {
-		return true;
-	}
-
-	bool needsBubble() const override {
-		return true;
-	}
-	bool customInfoLayout() const override {
-		return false;
-	}
-
-	const QString &fname() const {
-		return _fname;
-	}
-	const QString &lname() const {
-		return _lname;
-	}
-	const QString &phone() const {
-		return _phone;
-	}
-
-	// Should be called only by Data::Session.
-	void updateSharedContactUserId(UserId userId) override;
-
-private:
-	QSize countOptimalSize() override;
-
-	UserId _userId = 0;
-	UserData *_contact = nullptr;
-
-	int _phonew = 0;
-	QString _fname, _lname, _phone;
-	Text _name;
-	std::unique_ptr<Ui::EmptyUserpic> _photoEmpty;
-
-	ClickHandlerPtr _linkl;
-	int _linkw = 0;
-	QString _link;
-
-};
-
-class HistoryCall : public HistoryMedia {
-public:
-	HistoryCall(
-		not_null<Element*> parent,
-		not_null<Data::Call*> call);
-
-	HistoryMediaType type() const override {
-		return MediaTypeCall;
-	}
-
-	void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override;
-	TextState textState(QPoint point, StateRequest request) const override;
-
-	bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override {
-		return true;
-	}
-	bool dragItemByHandler(const ClickHandlerPtr &p) const override {
-		return false;
-	}
-
-	bool needsBubble() const override {
-		return true;
-	}
-	bool customInfoLayout() const override {
-		return true;
-	}
-
-	Data::CallFinishReason reason() const;
-
-private:
-	using FinishReason = Data::CallFinishReason;
-
-	QSize countOptimalSize() override;
-
-	FinishReason _reason;
-	int _duration = 0;
-
-	QString _text;
-	QString _status;
-
-	ClickHandlerPtr _link;
-
-};
-
-class HistoryWebPage : public HistoryMedia {
-public:
-	HistoryWebPage(
-		not_null<Element*> parent,
-		not_null<WebPageData*> data);
-
-	HistoryMediaType type() const override {
-		return MediaTypeWebPage;
-	}
-
-	void refreshParentId(not_null<HistoryItem*> realParent) override;
-
-	void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override;
-	TextState textState(QPoint point, StateRequest request) const override;
-
-	bool hideMessageText() const override {
-		return false;
-	}
-
-	[[nodiscard]] TextSelection adjustSelection(
-		TextSelection selection,
-		TextSelectType type) const override;
-	uint16 fullSelectionLength() const override {
-		return _title.length() + _description.length();
-	}
-	bool hasTextForCopy() const override {
-		return false; // we do not add _title and _description in FullSelection text copy.
-	}
-
-	bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override {
-		return _attach && _attach->toggleSelectionByHandlerClick(p);
-	}
-	bool dragItemByHandler(const ClickHandlerPtr &p) const override {
-		return _attach && _attach->dragItemByHandler(p);
-	}
-
-	TextWithEntities selectedText(TextSelection selection) const override;
-
-	void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
-	void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
-
-	bool isDisplayed() const override;
-	PhotoData *getPhoto() const override {
-		return _attach ? _attach->getPhoto() : nullptr;
-	}
-	DocumentData *getDocument() const override {
-		return _attach ? _attach->getDocument() : nullptr;
-	}
-	void stopAnimation() override {
-		if (_attach) _attach->stopAnimation();
-	}
-
-	not_null<WebPageData*> webpage() {
-		return _data;
-	}
-
-	bool needsBubble() const override {
-		return true;
-	}
-	bool customInfoLayout() const override {
-		return false;
-	}
-	bool allowsFastShare() const override {
-		return true;
-	}
-
-	HistoryMedia *attach() const {
-		return _attach.get();
-	}
-
-	~HistoryWebPage();
-
-private:
-	void playAnimation(bool autoplay) override;
-	QSize countOptimalSize() override;
-	QSize countCurrentSize(int newWidth) override;
-
-	TextSelection toDescriptionSelection(TextSelection selection) const;
-	TextSelection fromDescriptionSelection(TextSelection selection) const;
-	QMargins inBubblePadding() const;
-	int bottomInfoPadding() const;
-	bool isLogEntryOriginal() const;
-
-	not_null<WebPageData*> _data;
-	std::vector<std::unique_ptr<Data::Media>> _collage;
-	ClickHandlerPtr _openl;
-	std::unique_ptr<HistoryMedia> _attach;
-
-	bool _asArticle = false;
-	int _dataVersion = -1;
-	int _titleLines = 0;
-	int _descriptionLines = 0;
-
-	Text _title, _description;
-	int _siteNameWidth = 0;
-
-	QString _duration;
-	int _durationWidth = 0;
-
-	int _pixw = 0;
-	int _pixh = 0;
-
-};
-
-class HistoryGame : public HistoryMedia {
-public:
-	HistoryGame(
-		not_null<Element*> parent,
-		not_null<GameData*> data,
-		const TextWithEntities &consumed);
-
-	HistoryMediaType type() const override {
-		return MediaTypeGame;
-	}
-
-	void refreshParentId(not_null<HistoryItem*> realParent) override;
-
-	void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override;
-	TextState textState(QPoint point, StateRequest request) const override;
-
-	[[nodiscard]] TextSelection adjustSelection(
-		TextSelection selection,
-		TextSelectType type) const override;
-	uint16 fullSelectionLength() const override {
-		return _title.length() + _description.length();
-	}
-	bool hasTextForCopy() const override {
-		return false; // we do not add _title and _description in FullSelection text copy.
-	}
-
-	bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override {
-		return _attach && _attach->toggleSelectionByHandlerClick(p);
-	}
-	bool dragItemByHandler(const ClickHandlerPtr &p) const override {
-		return _attach && _attach->dragItemByHandler(p);
-	}
-
-	TextWithEntities selectedText(TextSelection selection) const override;
-
-	void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
-	void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
-
-	PhotoData *getPhoto() const override {
-		return _attach ? _attach->getPhoto() : nullptr;
-	}
-	DocumentData *getDocument() const override {
-		return _attach ? _attach->getDocument() : nullptr;
-	}
-	void stopAnimation() override {
-		if (_attach) _attach->stopAnimation();
-	}
-
-	not_null<GameData*> game() {
-		return _data;
-	}
-
-	bool needsBubble() const override {
-		return true;
-	}
-	bool customInfoLayout() const override {
-		return false;
-	}
-	bool allowsFastShare() const override {
-		return true;
-	}
-
-	HistoryMedia *attach() const {
-		return _attach.get();
-	}
-
-	void parentTextUpdated() override;
-
-	~HistoryGame();
-
-private:
-	void playAnimation(bool autoplay) override;
-	QSize countOptimalSize() override;
-	QSize countCurrentSize(int newWidth) override;
-
-	TextSelection toDescriptionSelection(TextSelection selection) const;
-	TextSelection fromDescriptionSelection(TextSelection selection) const;
-	QMargins inBubblePadding() const;
-	int bottomInfoPadding() const;
-
-	not_null<GameData*> _data;
-	std::shared_ptr<ReplyMarkupClickHandler> _openl;
-	std::unique_ptr<HistoryMedia> _attach;
-
-	int _titleLines, _descriptionLines;
-
-	Text _title, _description;
-
-	int _gameTagWidth = 0;
-
-};
-
-class HistoryInvoice : public HistoryMedia {
-public:
-	HistoryInvoice(
-		not_null<Element*> parent,
-		not_null<Data::Invoice*> invoice);
-
-	HistoryMediaType type() const override {
-		return MediaTypeInvoice;
-	}
-
-	void refreshParentId(not_null<HistoryItem*> realParent) override;
-
-	MsgId getReceiptMsgId() const {
-		return _receiptMsgId;
-	}
-	QString getTitle() const {
-		return _title.originalText();
-	}
-
-	bool hideMessageText() const override {
-		return false;
-	}
-
-	void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override;
-	TextState textState(QPoint point, StateRequest request) const override;
-
-	[[nodiscard]] TextSelection adjustSelection(
-		TextSelection selection,
-		TextSelectType type) const override;
-	uint16 fullSelectionLength() const override {
-		return _title.length() + _description.length();
-	}
-	bool hasTextForCopy() const override {
-		return false; // we do not add _title and _description in FullSelection text copy.
-	}
-
-	bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override {
-		return _attach && _attach->toggleSelectionByHandlerClick(p);
-	}
-	bool dragItemByHandler(const ClickHandlerPtr &p) const override {
-		return _attach && _attach->dragItemByHandler(p);
-	}
-
-	TextWithEntities selectedText(TextSelection selection) const override;
-
-	void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
-	void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
-
-	bool needsBubble() const override {
-		return true;
-	}
-	bool customInfoLayout() const override {
-		return false;
-	}
-
-	HistoryMedia *attach() const {
-		return _attach.get();
-	}
-
-private:
-	QSize countOptimalSize() override;
-	QSize countCurrentSize(int newWidth) override;
-
-	void fillFromData(not_null<Data::Invoice*> invoice);
-
-	TextSelection toDescriptionSelection(TextSelection selection) const;
-	TextSelection fromDescriptionSelection(TextSelection selection) const;
-	QMargins inBubblePadding() const;
-	int bottomInfoPadding() const;
-
-	std::unique_ptr<HistoryMedia> _attach;
-
-	int _titleHeight = 0;
-	int _descriptionHeight = 0;
-	Text _title;
-	Text _description;
-	Text _status;
-
-	MsgId _receiptMsgId = 0;
-
-};
-
-class LocationCoords;
-struct LocationData;
-
-class HistoryLocation : public HistoryMedia {
-public:
-	HistoryLocation(
-		not_null<Element*> parent,
-		not_null<LocationData*> location,
-		const QString &title = QString(),
-		const QString &description = QString());
-
-	HistoryMediaType type() const override {
-		return MediaTypeLocation;
-	}
-
-	void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override;
-	TextState textState(QPoint point, StateRequest request) const override;
-
-	[[nodiscard]] TextSelection adjustSelection(
-		TextSelection selection,
-		TextSelectType type) const override;
-	uint16 fullSelectionLength() const override {
-		return _title.length() + _description.length();
-	}
-	bool hasTextForCopy() const override {
-		return !_title.isEmpty() || !_description.isEmpty();
-	}
-
-	bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override {
-		return p == _link;
-	}
-	bool dragItemByHandler(const ClickHandlerPtr &p) const override {
-		return p == _link;
-	}
-
-	TextWithEntities selectedText(TextSelection selection) const override;
-
-	bool needsBubble() const override;
-	bool customInfoLayout() const override {
-		return true;
-	}
-
-	bool skipBubbleTail() const override {
-		return isBubbleBottom();
-	}
-
-private:
-	QSize countOptimalSize() override;
-	QSize countCurrentSize(int newWidth) override;
-
-	TextSelection toDescriptionSelection(TextSelection selection) const;
-	TextSelection fromDescriptionSelection(TextSelection selection) const;
-
-	LocationData *_data;
-	Text _title, _description;
-	ClickHandlerPtr _link;
-
-	int fullWidth() const;
-	int fullHeight() const;
-
-};
diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp
index 484eedfce..f2e6b57be 100644
--- a/Telegram/SourceFiles/history/history_message.cpp
+++ b/Telegram/SourceFiles/history/history_message.cpp
@@ -14,7 +14,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "history/history.h"
 #include "history/history_item_components.h"
 #include "history/history_location_manager.h"
-#include "history/history_media_types.h"
 #include "history/history_service.h"
 #include "history/view/history_view_service_message.h"
 #include "auth_session.h"
@@ -29,6 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "observer_peer.h"
 #include "storage/storage_shared_media.h"
 #include "data/data_session.h"
+#include "data/data_game.h"
 #include "data/data_media_types.h"
 #include "styles/style_dialogs.h"
 #include "styles/style_widgets.h"
diff --git a/Telegram/SourceFiles/history/history_service.cpp b/Telegram/SourceFiles/history/history_service.cpp
index 9770d52f8..083cfaec2 100644
--- a/Telegram/SourceFiles/history/history_service.cpp
+++ b/Telegram/SourceFiles/history/history_service.cpp
@@ -13,13 +13,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "apiwrap.h"
 #include "layout.h"
 #include "history/history.h"
-#include "history/history_media_types.h"
+#include "history/media/history_media_invoice.h"
 #include "history/history_message.h"
 #include "history/history_item_components.h"
 #include "history/view/history_view_service_message.h"
 #include "data/data_feed.h"
 #include "data/data_session.h"
 #include "data/data_media_types.h"
+#include "data/data_game.h"
 #include "window/notifications_manager.h"
 #include "window/window_controller.h"
 #include "storage/storage_shared_media.h"
diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp
index 6e295c62b..9f60aceff 100644
--- a/Telegram/SourceFiles/history/history_widget.cpp
+++ b/Telegram/SourceFiles/history/history_widget.cpp
@@ -26,11 +26,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "inline_bots/inline_bot_result.h"
 #include "data/data_drafts.h"
 #include "data/data_session.h"
+#include "data/data_web_page.h"
+#include "data/data_document.h"
+#include "data/data_photo.h"
 #include "data/data_media_types.h"
 #include "history/history.h"
 #include "history/history_item.h"
 #include "history/history_message.h"
-#include "history/history_media_types.h"
+#include "history/media/history_media.h"
 #include "history/history_drag_area.h"
 #include "history/history_inner_widget.h"
 #include "history/history_item_components.h"
diff --git a/Telegram/SourceFiles/history/history_media.cpp b/Telegram/SourceFiles/history/media/history_media.cpp
similarity index 97%
rename from Telegram/SourceFiles/history/history_media.cpp
rename to Telegram/SourceFiles/history/media/history_media.cpp
index b540556d8..31a31d066 100644
--- a/Telegram/SourceFiles/history/history_media.cpp
+++ b/Telegram/SourceFiles/history/media/history_media.cpp
@@ -5,7 +5,7 @@ the official desktop application for the Telegram messaging service.
 For license and copyright information please follow this link:
 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
-#include "history/history_media.h"
+#include "history/media/history_media.h"
 
 #include "history/history_item.h"
 #include "history/view/history_view_element.h"
diff --git a/Telegram/SourceFiles/history/history_media.h b/Telegram/SourceFiles/history/media/history_media.h
similarity index 100%
rename from Telegram/SourceFiles/history/history_media.h
rename to Telegram/SourceFiles/history/media/history_media.h
diff --git a/Telegram/SourceFiles/history/media/history_media_call.cpp b/Telegram/SourceFiles/history/media/history_media_call.cpp
new file mode 100644
index 000000000..c4d63305c
--- /dev/null
+++ b/Telegram/SourceFiles/history/media/history_media_call.cpp
@@ -0,0 +1,111 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "history/media/history_media_call.h"
+
+#include "lang/lang_keys.h"
+#include "layout.h"
+#include "history/history.h"
+#include "history/history_item.h"
+#include "history/view/history_view_element.h"
+#include "history/view/history_view_cursor_state.h"
+#include "calls/calls_instance.h"
+#include "data/data_media_types.h"
+#include "styles/style_history.h"
+
+namespace {
+
+using TextState = HistoryView::TextState;
+
+} // namespace
+
+HistoryCall::HistoryCall(
+	not_null<Element*> parent,
+	not_null<Data::Call*> call)
+: HistoryMedia(parent) {
+	_duration = call->duration;
+	_reason = call->finishReason;
+
+	const auto item = parent->data();
+	_text = Data::MediaCall::Text(item, _reason);
+	_status = parent->dateTime().time().toString(cTimeFormat());
+	if (_duration) {
+		if (_reason != FinishReason::Missed
+			&& _reason != FinishReason::Busy) {
+			_status = lng_call_duration_info(
+				lt_time,
+				_status,
+				lt_duration,
+				formatDurationWords(_duration));
+		} else {
+			_duration = 0;
+		}
+	}
+}
+
+QSize HistoryCall::countOptimalSize() {
+	const auto user = _parent->data()->history()->peer->asUser();
+	_link = std::make_shared<LambdaClickHandler>([=] {
+		if (user) {
+			Calls::Current().startOutgoingCall(user);
+		}
+	});
+
+	auto maxWidth = st::historyCallWidth;
+	auto minHeight = st::historyCallHeight;
+	if (!isBubbleTop()) {
+		minHeight -= st::msgFileTopMinus;
+	}
+	return { maxWidth, minHeight };
+}
+
+void HistoryCall::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const {
+	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
+	auto paintx = 0, painty = 0, paintw = width(), painth = height();
+
+	auto outbg = _parent->hasOutLayout();
+	auto selected = (selection == FullSelection);
+
+	accumulate_min(paintw, maxWidth());
+
+	auto nameleft = 0, nametop = 0, nameright = 0, statustop = 0;
+	auto topMinus = isBubbleTop() ? 0 : st::msgFileTopMinus;
+
+	nameleft = st::historyCallLeft;
+	nametop = st::historyCallTop - topMinus;
+	nameright = st::msgFilePadding.left();
+	statustop = st::historyCallStatusTop - topMinus;
+
+	auto namewidth = paintw - nameleft - nameright;
+
+	p.setFont(st::semiboldFont);
+	p.setPen(outbg ? (selected ? st::historyFileNameOutFgSelected : st::historyFileNameOutFg) : (selected ? st::historyFileNameInFgSelected : st::historyFileNameInFg));
+	p.drawTextLeft(nameleft, nametop, paintw, _text);
+
+	auto statusleft = nameleft;
+	auto missed = (_reason == FinishReason::Missed || _reason == FinishReason::Busy);
+	auto &arrow = outbg ? (selected ? st::historyCallArrowOutSelected : st::historyCallArrowOut) : missed ? (selected ? st::historyCallArrowMissedInSelected : st::historyCallArrowMissedIn) : (selected ? st::historyCallArrowInSelected : st::historyCallArrowIn);
+	arrow.paint(p, statusleft + st::historyCallArrowPosition.x(), statustop + st::historyCallArrowPosition.y(), paintw);
+	statusleft += arrow.width() + st::historyCallStatusSkip;
+
+	auto &statusFg = outbg ? (selected ? st::mediaOutFgSelected : st::mediaOutFg) : (selected ? st::mediaInFgSelected : st::mediaInFg);
+	p.setFont(st::normalFont);
+	p.setPen(statusFg);
+	p.drawTextLeft(statusleft, statustop, paintw, _status);
+
+	auto &icon = outbg ? (selected ? st::historyCallOutIconSelected : st::historyCallOutIcon) : (selected ? st::historyCallInIconSelected : st::historyCallInIcon);
+	icon.paint(p, paintw - st::historyCallIconPosition.x() - icon.width(), st::historyCallIconPosition.y() - topMinus, paintw);
+}
+
+TextState HistoryCall::textState(QPoint point, StateRequest request) const {
+	auto result = TextState(_parent);
+	if (QRect(0, 0, width(), height()).contains(point)) {
+		result.link = _link;
+		return result;
+	}
+	return result;
+}
diff --git a/Telegram/SourceFiles/history/media/history_media_call.h b/Telegram/SourceFiles/history/media/history_media_call.h
new file mode 100644
index 000000000..bb6bcc358
--- /dev/null
+++ b/Telegram/SourceFiles/history/media/history_media_call.h
@@ -0,0 +1,59 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+#include "history/media/history_media.h"
+
+namespace Data {
+enum class CallFinishReason : char;
+struct Call;
+} // namespace Data
+
+class HistoryCall : public HistoryMedia {
+public:
+	HistoryCall(
+		not_null<Element*> parent,
+		not_null<Data::Call*> call);
+
+	HistoryMediaType type() const override {
+		return MediaTypeCall;
+	}
+
+	void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override;
+	TextState textState(QPoint point, StateRequest request) const override;
+
+	bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override {
+		return true;
+	}
+	bool dragItemByHandler(const ClickHandlerPtr &p) const override {
+		return false;
+	}
+
+	bool needsBubble() const override {
+		return true;
+	}
+	bool customInfoLayout() const override {
+		return true;
+	}
+
+	Data::CallFinishReason reason() const;
+
+private:
+	using FinishReason = Data::CallFinishReason;
+
+	QSize countOptimalSize() override;
+
+	FinishReason _reason;
+	int _duration = 0;
+
+	QString _text;
+	QString _status;
+
+	ClickHandlerPtr _link;
+
+};
diff --git a/Telegram/SourceFiles/history/media/history_media_common.cpp b/Telegram/SourceFiles/history/media/history_media_common.cpp
new file mode 100644
index 000000000..a1a2d3ef1
--- /dev/null
+++ b/Telegram/SourceFiles/history/media/history_media_common.cpp
@@ -0,0 +1,92 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "history/media/history_media_common.h"
+
+#include "layout.h"
+#include "data/data_document.h"
+#include "history/view/history_view_element.h"
+#include "history/media/history_media_grouped.h"
+#include "history/media/history_media_photo.h"
+#include "history/media/history_media_gif.h"
+#include "history/media/history_media_document.h"
+#include "history/media/history_media_sticker.h"
+#include "history/media/history_media_video.h"
+
+int documentMaxStatusWidth(DocumentData *document) {
+	auto result = st::normalFont->width(formatDownloadText(document->size, document->size));
+	if (const auto song = document->song()) {
+		accumulate_max(result, st::normalFont->width(formatPlayedText(song->duration, song->duration)));
+		accumulate_max(result, st::normalFont->width(formatDurationAndSizeText(song->duration, document->size)));
+	} else if (const auto voice = document->voice()) {
+		accumulate_max(result, st::normalFont->width(formatPlayedText(voice->duration, voice->duration)));
+		accumulate_max(result, st::normalFont->width(formatDurationAndSizeText(voice->duration, document->size)));
+	} else if (document->isVideoFile()) {
+		accumulate_max(result, st::normalFont->width(formatDurationAndSizeText(document->duration(), document->size)));
+	} else {
+		accumulate_max(result, st::normalFont->width(formatSizeText(document->size)));
+	}
+	return result;
+}
+
+void PaintInterpolatedIcon(
+		Painter &p,
+		const style::icon &a,
+		const style::icon &b,
+		float64 b_ratio,
+		QRect rect) {
+	PainterHighQualityEnabler hq(p);
+	p.save();
+	p.translate(rect.center());
+	p.setOpacity(b_ratio);
+	p.scale(b_ratio, b_ratio);
+	b.paintInCenter(p, rect.translated(-rect.center()));
+	p.restore();
+
+	p.save();
+	p.translate(rect.center());
+	p.setOpacity(1. - b_ratio);
+	p.scale(1. - b_ratio, 1. - b_ratio);
+	a.paintInCenter(p, rect.translated(-rect.center()));
+	p.restore();
+}
+
+std::unique_ptr<HistoryMedia> CreateAttach(
+		not_null<HistoryView::Element*> parent,
+		DocumentData *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()) {
+			return std::make_unique<HistoryGif>(
+				parent,
+				document);
+		} else if (document->isVideoFile()) {
+			return std::make_unique<HistoryVideo>(
+				parent,
+				parent->data(),
+				document);
+		}
+		return std::make_unique<HistoryDocument>(
+			parent,
+			document);
+	} else if (photo) {
+		return std::make_unique<HistoryPhoto>(
+			parent,
+			parent->data(),
+			photo);
+	}
+	return nullptr;
+}
+
+int unitedLineHeight() {
+	return qMax(st::webPageTitleFont->height, st::webPageDescriptionFont->height);
+}
diff --git a/Telegram/SourceFiles/history/media/history_media_common.h b/Telegram/SourceFiles/history/media/history_media_common.h
new file mode 100644
index 000000000..57d952733
--- /dev/null
+++ b/Telegram/SourceFiles/history/media/history_media_common.h
@@ -0,0 +1,36 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+namespace HistoryView {
+class Element;
+} // namespace HistoryView
+
+namespace Data {
+class Media;
+} // namespace Data
+
+class DocumentData;
+class PhotoData;
+class HistoryMedia;
+
+int documentMaxStatusWidth(DocumentData *document);
+
+void PaintInterpolatedIcon(
+	Painter &p,
+	const style::icon &a,
+	const style::icon &b,
+	float64 b_ratio,
+	QRect rect);
+
+std::unique_ptr<HistoryMedia> CreateAttach(
+	not_null<HistoryView::Element*> parent,
+	DocumentData *document,
+	PhotoData *photo,
+	const std::vector<std::unique_ptr<Data::Media>> &collage = {});
+int unitedLineHeight();
diff --git a/Telegram/SourceFiles/history/media/history_media_contact.cpp b/Telegram/SourceFiles/history/media/history_media_contact.cpp
new file mode 100644
index 000000000..fba2194a7
--- /dev/null
+++ b/Telegram/SourceFiles/history/media/history_media_contact.cpp
@@ -0,0 +1,216 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "history/media/history_media_contact.h"
+
+#include "lang/lang_keys.h"
+#include "layout.h"
+#include "mainwindow.h"
+#include "auth_session.h"
+#include "boxes/add_contact_box.h"
+#include "history/history_item_components.h"
+#include "history/history_item.h"
+#include "history/view/history_view_element.h"
+#include "history/view/history_view_cursor_state.h"
+#include "window/window_controller.h"
+#include "ui/empty_userpic.h"
+#include "ui/text_options.h"
+#include "data/data_session.h"
+#include "data/data_media_types.h"
+#include "styles/style_history.h"
+
+namespace {
+
+using TextState = HistoryView::TextState;
+
+} // namespace
+
+namespace {
+
+ClickHandlerPtr sendMessageClickHandler(PeerData *peer) {
+	return std::make_shared<LambdaClickHandler>([peer] {
+		App::wnd()->controller()->showPeerHistory(
+			peer->id,
+			Window::SectionShow::Way::Forward);
+	});
+}
+
+ClickHandlerPtr addContactClickHandler(not_null<HistoryItem*> item) {
+	return std::make_shared<LambdaClickHandler>([fullId = item->fullId()] {
+		if (const auto item = App::histItemById(fullId)) {
+			if (const auto media = item->media()) {
+				if (const auto contact = media->sharedContact()) {
+					Ui::show(Box<AddContactBox>(
+						contact->firstName,
+						contact->lastName,
+						contact->phoneNumber));
+				}
+			}
+		}
+	});
+}
+
+} // namespace
+
+HistoryContact::HistoryContact(
+	not_null<Element*> parent,
+	UserId userId,
+	const QString &first,
+	const QString &last,
+	const QString &phone)
+: HistoryMedia(parent)
+, _userId(userId)
+, _fname(first)
+, _lname(last)
+, _phone(App::formatPhone(phone)) {
+	Auth().data().registerContactView(userId, parent);
+
+	_name.setText(
+		st::semiboldTextStyle,
+		lng_full_name(lt_first_name, first, lt_last_name, last).trimmed(),
+		Ui::NameTextOptions());
+	_phonew = st::normalFont->width(_phone);
+}
+
+HistoryContact::~HistoryContact() {
+	Auth().data().unregisterContactView(_userId, _parent);
+}
+
+void HistoryContact::updateSharedContactUserId(UserId userId) {
+	if (_userId != userId) {
+		Auth().data().unregisterContactView(_userId, _parent);
+		_userId = userId;
+		Auth().data().registerContactView(_userId, _parent);
+	}
+}
+
+QSize HistoryContact::countOptimalSize() {
+	const auto item = _parent->data();
+	auto maxWidth = st::msgFileMinWidth;
+
+	_contact = _userId ? App::userLoaded(_userId) : nullptr;
+	if (_contact) {
+		_contact->loadUserpic();
+	} else {
+		_photoEmpty = std::make_unique<Ui::EmptyUserpic>(
+			Data::PeerUserpicColor(_userId ? _userId : _parent->data()->id),
+			_name.originalText());
+	}
+	if (_contact
+		&& _contact->contactStatus() == UserData::ContactStatus::Contact) {
+		_linkl = sendMessageClickHandler(_contact);
+		_link = lang(lng_profile_send_message).toUpper();
+	} else if (_userId) {
+		_linkl = addContactClickHandler(_parent->data());
+		_link = lang(lng_profile_add_contact).toUpper();
+	}
+	_linkw = _link.isEmpty() ? 0 : st::semiboldFont->width(_link);
+
+	auto tleft = 0;
+	auto tright = 0;
+	if (_userId) {
+		tleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right();
+		tright = st::msgFileThumbPadding.left();
+		accumulate_max(maxWidth, tleft + _phonew + tright);
+	} else {
+		tleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right();
+		tright = st::msgFileThumbPadding.left();
+		accumulate_max(maxWidth, tleft + _phonew + _parent->skipBlockWidth() + st::msgPadding.right());
+	}
+
+	accumulate_max(maxWidth, tleft + _name.maxWidth() + tright);
+	accumulate_min(maxWidth, st::msgMaxWidth);
+	auto minHeight = 0;
+	if (_userId) {
+		minHeight = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom();
+		if (item->Has<HistoryMessageSigned>()
+			|| item->Has<HistoryMessageViews>()) {
+			minHeight += st::msgDateFont->height - st::msgDateDelta.y();
+		}
+	} else {
+		minHeight = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom();
+	}
+	if (!isBubbleTop()) {
+		minHeight -= st::msgFileTopMinus;
+	}
+	return { maxWidth, minHeight };
+}
+
+void HistoryContact::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const {
+	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
+	auto paintx = 0, painty = 0, paintw = width(), painth = height();
+
+	auto outbg = _parent->hasOutLayout();
+	bool selected = (selection == FullSelection);
+
+	accumulate_min(paintw, maxWidth());
+
+	auto nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0;
+	auto topMinus = isBubbleTop() ? 0 : st::msgFileTopMinus;
+	if (_userId) {
+		nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right();
+		nametop = st::msgFileThumbNameTop - topMinus;
+		nameright = st::msgFileThumbPadding.left();
+		statustop = st::msgFileThumbStatusTop - topMinus;
+		linktop = st::msgFileThumbLinkTop - topMinus;
+
+		QRect rthumb(rtlrect(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top() - topMinus, st::msgFileThumbSize, st::msgFileThumbSize, paintw));
+		if (_contact) {
+			_contact->paintUserpic(p, rthumb.x(), rthumb.y(), st::msgFileThumbSize);
+		} else {
+			_photoEmpty->paint(p, st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top() - topMinus, paintw, st::msgFileThumbSize);
+		}
+		if (selected) {
+			PainterHighQualityEnabler hq(p);
+			p.setBrush(p.textPalette().selectOverlay);
+			p.setPen(Qt::NoPen);
+			p.drawEllipse(rthumb);
+		}
+
+		bool over = ClickHandler::showAsActive(_linkl);
+		p.setFont(over ? st::semiboldFont->underline() : st::semiboldFont);
+		p.setPen(outbg ? (selected ? st::msgFileThumbLinkOutFgSelected : st::msgFileThumbLinkOutFg) : (selected ? st::msgFileThumbLinkInFgSelected : st::msgFileThumbLinkInFg));
+		p.drawTextLeft(nameleft, linktop, paintw, _link, _linkw);
+	} else {
+		nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right();
+		nametop = st::msgFileNameTop - topMinus;
+		nameright = st::msgFilePadding.left();
+		statustop = st::msgFileStatusTop - topMinus;
+
+		_photoEmpty->paint(p, st::msgFilePadding.left(), st::msgFilePadding.top() - topMinus, paintw, st::msgFileSize);
+	}
+	auto namewidth = paintw - nameleft - nameright;
+
+	p.setFont(st::semiboldFont);
+	p.setPen(outbg ? (selected ? st::historyFileNameOutFgSelected : st::historyFileNameOutFg) : (selected ? st::historyFileNameInFgSelected : st::historyFileNameInFg));
+	_name.drawLeftElided(p, nameleft, nametop, namewidth, paintw);
+
+	auto &status = outbg ? (selected ? st::mediaOutFgSelected : st::mediaOutFg) : (selected ? st::mediaInFgSelected : st::mediaInFg);
+	p.setFont(st::normalFont);
+	p.setPen(status);
+	p.drawTextLeft(nameleft, statustop, paintw, _phone);
+}
+
+TextState HistoryContact::textState(QPoint point, StateRequest request) const {
+	auto result = TextState(_parent);
+
+	auto nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0;
+	auto topMinus = isBubbleTop() ? 0 : st::msgFileTopMinus;
+	if (_userId) {
+		nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right();
+		linktop = st::msgFileThumbLinkTop - topMinus;
+		if (rtlrect(nameleft, linktop, _linkw, st::semiboldFont->height, width()).contains(point)) {
+			result.link = _linkl;
+			return result;
+		}
+	}
+	if (QRect(0, 0, width(), height()).contains(point) && _contact) {
+		result.link = _contact->openLink();
+		return result;
+	}
+	return result;
+}
diff --git a/Telegram/SourceFiles/history/media/history_media_contact.h b/Telegram/SourceFiles/history/media/history_media_contact.h
new file mode 100644
index 000000000..906241849
--- /dev/null
+++ b/Telegram/SourceFiles/history/media/history_media_contact.h
@@ -0,0 +1,71 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+#include "history/media/history_media.h"
+
+class HistoryContact : public HistoryMedia {
+public:
+	HistoryContact(
+		not_null<Element*> parent,
+		UserId userId,
+		const QString &first,
+		const QString &last,
+		const QString &phone);
+	~HistoryContact();
+
+	HistoryMediaType type() const override {
+		return MediaTypeContact;
+	}
+
+	void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override;
+	TextState textState(QPoint point, StateRequest request) const override;
+
+	bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override {
+		return true;
+	}
+	bool dragItemByHandler(const ClickHandlerPtr &p) const override {
+		return true;
+	}
+
+	bool needsBubble() const override {
+		return true;
+	}
+	bool customInfoLayout() const override {
+		return false;
+	}
+
+	const QString &fname() const {
+		return _fname;
+	}
+	const QString &lname() const {
+		return _lname;
+	}
+	const QString &phone() const {
+		return _phone;
+	}
+
+	// Should be called only by Data::Session.
+	void updateSharedContactUserId(UserId userId) override;
+
+private:
+	QSize countOptimalSize() override;
+
+	UserId _userId = 0;
+	UserData *_contact = nullptr;
+
+	int _phonew = 0;
+	QString _fname, _lname, _phone;
+	Text _name;
+	std::unique_ptr<Ui::EmptyUserpic> _photoEmpty;
+
+	ClickHandlerPtr _linkl;
+	int _linkw = 0;
+	QString _link;
+
+};
diff --git a/Telegram/SourceFiles/history/media/history_media_document.cpp b/Telegram/SourceFiles/history/media/history_media_document.cpp
new file mode 100644
index 000000000..581c0eb42
--- /dev/null
+++ b/Telegram/SourceFiles/history/media/history_media_document.cpp
@@ -0,0 +1,770 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "history/media/history_media_document.h"
+
+#include "lang/lang_keys.h"
+#include "layout.h"
+#include "auth_session.h"
+#include "storage/localstorage.h"
+#include "media/media_audio.h"
+#include "media/player/media_player_instance.h"
+#include "history/history_item_components.h"
+#include "history/view/history_view_element.h"
+#include "history/view/history_view_cursor_state.h"
+#include "history/media/history_media_common.h"
+#include "ui/image/image.h"
+#include "data/data_session.h"
+#include "data/data_document.h"
+#include "data/data_media_types.h"
+#include "styles/style_history.h"
+
+namespace {
+
+using TextState = HistoryView::TextState;
+
+} // namespace
+
+HistoryDocument::HistoryDocument(
+	not_null<Element*> parent,
+	not_null<DocumentData*> document)
+: HistoryFileMedia(parent, parent->data())
+, _data(document) {
+	const auto item = parent->data();
+	auto caption = createCaption(item);
+
+	createComponents(!caption.isEmpty());
+	if (auto named = Get<HistoryDocumentNamed>()) {
+		fillNamedFromData(named);
+	}
+
+	setDocumentLinks(_data, item);
+
+	setStatusSize(FileStatusSizeReady);
+
+	if (auto captioned = Get<HistoryDocumentCaptioned>()) {
+		captioned->_caption = std::move(caption);
+	}
+}
+
+HistoryMediaType HistoryDocument::type() const {
+	return _data->isVoiceMessage()
+		? MediaTypeVoiceFile
+		: (_data->isSong()
+			? MediaTypeMusicFile
+			: MediaTypeFile);
+}
+
+float64 HistoryDocument::dataProgress() const {
+	return _data->progress();
+}
+
+bool HistoryDocument::dataFinished() const {
+	return !_data->loading() && !_data->uploading();
+}
+
+bool HistoryDocument::dataLoaded() const {
+	return _data->loaded();
+}
+
+void HistoryDocument::createComponents(bool caption) {
+	uint64 mask = 0;
+	if (_data->isVoiceMessage()) {
+		mask |= HistoryDocumentVoice::Bit();
+	} else {
+		mask |= HistoryDocumentNamed::Bit();
+		if (!_data->isSong()
+			&& !_data->thumb->isNull()
+			&& _data->thumb->width()
+			&& _data->thumb->height()
+			&& !Data::IsExecutableName(_data->filename())) {
+			mask |= HistoryDocumentThumbed::Bit();
+		}
+	}
+	if (caption) {
+		mask |= HistoryDocumentCaptioned::Bit();
+	}
+	UpdateComponents(mask);
+	if (auto thumbed = Get<HistoryDocumentThumbed>()) {
+		thumbed->_linksavel = std::make_shared<DocumentSaveClickHandler>(
+			_data,
+			_parent->data()->fullId());
+		thumbed->_linkcancell = std::make_shared<DocumentCancelClickHandler>(
+			_data,
+			_parent->data()->fullId());
+	}
+	if (auto voice = Get<HistoryDocumentVoice>()) {
+		voice->_seekl = std::make_shared<VoiceSeekClickHandler>(
+			_data,
+			_parent->data()->fullId());
+	}
+}
+
+void HistoryDocument::fillNamedFromData(HistoryDocumentNamed *named) {
+	auto nameString = named->_name = _data->composeNameString();
+	named->_namew = st::semiboldFont->width(nameString);
+}
+
+QSize HistoryDocument::countOptimalSize() {
+	const auto item = _parent->data();
+
+	auto captioned = Get<HistoryDocumentCaptioned>();
+	if (_parent->media() != this) {
+		if (captioned) {
+			RemoveComponents(HistoryDocumentCaptioned::Bit());
+			captioned = nullptr;
+		}
+	} else if (captioned && captioned->_caption.hasSkipBlock()) {
+		captioned->_caption.updateSkipBlock(
+			_parent->skipBlockWidth(),
+			_parent->skipBlockHeight());
+	}
+	auto thumbed = Get<HistoryDocumentThumbed>();
+	if (thumbed) {
+		_data->thumb->load(_realParent->fullId());
+		auto tw = ConvertScale(_data->thumb->width());
+		auto th = ConvertScale(_data->thumb->height());
+		if (tw > th) {
+			thumbed->_thumbw = (tw * st::msgFileThumbSize) / th;
+		} else {
+			thumbed->_thumbw = st::msgFileThumbSize;
+		}
+	}
+
+	auto maxWidth = st::msgFileMinWidth;
+
+	auto tleft = 0;
+	auto tright = 0;
+	if (thumbed) {
+		tleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right();
+		tright = st::msgFileThumbPadding.left();
+		accumulate_max(maxWidth, tleft + documentMaxStatusWidth(_data) + tright);
+	} else {
+		tleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right();
+		tright = st::msgFileThumbPadding.left();
+		auto unread = _data->isVoiceMessage() ? (st::mediaUnreadSkip + st::mediaUnreadSize) : 0;
+		accumulate_max(maxWidth, tleft + documentMaxStatusWidth(_data) + unread + _parent->skipBlockWidth() + st::msgPadding.right());
+	}
+
+	if (auto named = Get<HistoryDocumentNamed>()) {
+		accumulate_max(maxWidth, tleft + named->_namew + tright);
+		accumulate_min(maxWidth, st::msgMaxWidth);
+	}
+
+	auto minHeight = 0;
+	if (thumbed) {
+		minHeight = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom();
+	} else {
+		minHeight = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom();
+	}
+	if (!captioned && (item->Has<HistoryMessageSigned>()
+		|| item->Has<HistoryMessageViews>()
+		|| _parent->displayEditedBadge())) {
+		minHeight += st::msgDateFont->height - st::msgDateDelta.y();
+	}
+	if (!isBubbleTop()) {
+		minHeight -= st::msgFileTopMinus;
+	}
+
+	if (captioned) {
+		auto captionw = maxWidth
+			- st::msgPadding.left()
+			- st::msgPadding.right();
+		minHeight += captioned->_caption.countHeight(captionw);
+		if (isBubbleBottom()) {
+			minHeight += st::msgPadding.bottom();
+		}
+	}
+	return { maxWidth, minHeight };
+}
+
+QSize HistoryDocument::countCurrentSize(int newWidth) {
+	auto captioned = Get<HistoryDocumentCaptioned>();
+	if (!captioned) {
+		return HistoryFileMedia::countCurrentSize(newWidth);
+	}
+
+	accumulate_min(newWidth, maxWidth());
+	auto newHeight = 0;
+	if (Get<HistoryDocumentThumbed>()) {
+		newHeight = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom();
+	} else {
+		newHeight = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom();
+	}
+	if (!isBubbleTop()) {
+		newHeight -= st::msgFileTopMinus;
+	}
+	auto captionw = newWidth - st::msgPadding.left() - st::msgPadding.right();
+	newHeight += captioned->_caption.countHeight(captionw);
+	if (isBubbleBottom()) {
+		newHeight += st::msgPadding.bottom();
+	}
+
+	return { newWidth, newHeight };
+}
+
+void HistoryDocument::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const {
+	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
+
+	_data->automaticLoad(_realParent->fullId(), _parent->data());
+	bool loaded = _data->loaded(), displayLoading = _data->displayLoading();
+	bool selected = (selection == FullSelection);
+
+	int captionw = width() - st::msgPadding.left() - st::msgPadding.right();
+	auto outbg = _parent->hasOutLayout();
+
+	if (displayLoading) {
+		ensureAnimation();
+		if (!_animation->radial.animating()) {
+			_animation->radial.start(_data->progress());
+		}
+	}
+	bool showPause = updateStatusText();
+	bool radial = isRadialAnimation(ms);
+
+	auto topMinus = isBubbleTop() ? 0 : st::msgFileTopMinus;
+	int nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0, bottom = 0;
+	if (auto thumbed = Get<HistoryDocumentThumbed>()) {
+		nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right();
+		nametop = st::msgFileThumbNameTop - topMinus;
+		nameright = st::msgFileThumbPadding.left();
+		statustop = st::msgFileThumbStatusTop - topMinus;
+		linktop = st::msgFileThumbLinkTop - topMinus;
+		bottom = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom() - topMinus;
+
+		auto inWebPage = (_parent->media() != this);
+		auto roundRadius = inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large;
+		QRect rthumb(rtlrect(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top() - topMinus, st::msgFileThumbSize, st::msgFileThumbSize, width()));
+		QPixmap thumb;
+		if (loaded) {
+			thumb = _data->thumb->pixSingle(_realParent->fullId(), thumbed->_thumbw, 0, st::msgFileThumbSize, st::msgFileThumbSize, roundRadius);
+		} else {
+			thumb = _data->thumb->pixBlurredSingle(_realParent->fullId(), thumbed->_thumbw, 0, st::msgFileThumbSize, st::msgFileThumbSize, roundRadius);
+		}
+		p.drawPixmap(rthumb.topLeft(), thumb);
+		if (selected) {
+			auto overlayCorners = inWebPage ? SelectedOverlaySmallCorners : SelectedOverlayLargeCorners;
+			App::roundRect(p, rthumb, p.textPalette().selectOverlay, overlayCorners);
+		}
+
+		if (radial || (!loaded && !_data->loading())) {
+			float64 radialOpacity = (radial && loaded && !_data->uploading()) ? _animation->radial.opacity() : 1;
+			QRect inner(rthumb.x() + (rthumb.width() - st::msgFileSize) / 2, rthumb.y() + (rthumb.height() - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize);
+			p.setPen(Qt::NoPen);
+			if (selected) {
+				p.setBrush(st::msgDateImgBgSelected);
+			} else if (isThumbAnimation(ms)) {
+				auto over = _animation->a_thumbOver.current();
+				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(radialOpacity * p.opacity());
+
+			{
+				PainterHighQualityEnabler hq(p);
+				p.drawEllipse(inner);
+			}
+
+			p.setOpacity(radialOpacity);
+			auto icon = ([radial, this, selected] {
+				if (radial || _data->loading()) {
+					return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel);
+				}
+				return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload);
+			})();
+			p.setOpacity((radial && loaded) ? _animation->radial.opacity() : 1);
+			icon->paintInCenter(p, inner);
+			if (radial) {
+				p.setOpacity(1);
+
+				QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine)));
+				_animation->radial.draw(p, rinner, st::msgFileRadialLine, selected ? st::historyFileThumbRadialFgSelected : st::historyFileThumbRadialFg);
+			}
+		}
+
+		if (_data->status != FileUploadFailed) {
+			const auto &lnk = (_data->loading() || _data->uploading())
+				? thumbed->_linkcancell
+				: thumbed->_linksavel;
+			bool over = ClickHandler::showAsActive(lnk);
+			p.setFont(over ? st::semiboldFont->underline() : st::semiboldFont);
+			p.setPen(outbg ? (selected ? st::msgFileThumbLinkOutFgSelected : st::msgFileThumbLinkOutFg) : (selected ? st::msgFileThumbLinkInFgSelected : st::msgFileThumbLinkInFg));
+			p.drawTextLeft(nameleft, linktop, width(), thumbed->_link, thumbed->_linkw);
+		}
+	} else {
+		nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right();
+		nametop = st::msgFileNameTop - topMinus;
+		nameright = st::msgFilePadding.left();
+		statustop = st::msgFileStatusTop - topMinus;
+		bottom = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom() - topMinus;
+
+		QRect inner(rtlrect(st::msgFilePadding.left(), st::msgFilePadding.top() - topMinus, st::msgFileSize, st::msgFileSize, width()));
+		p.setPen(Qt::NoPen);
+		if (selected) {
+			p.setBrush(outbg ? st::msgFileOutBgSelected : st::msgFileInBgSelected);
+		} else if (isThumbAnimation(ms)) {
+			auto over = _animation->a_thumbOver.current();
+			p.setBrush(anim::brush(outbg ? st::msgFileOutBg : st::msgFileInBg, outbg ? st::msgFileOutBgOver : st::msgFileInBgOver, over));
+		} else {
+			auto over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel);
+			p.setBrush(outbg ? (over ? st::msgFileOutBgOver : st::msgFileOutBg) : (over ? st::msgFileInBgOver : st::msgFileInBg));
+		}
+
+		{
+			PainterHighQualityEnabler hq(p);
+			p.drawEllipse(inner);
+		}
+
+		if (radial) {
+			QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine)));
+			auto fg = outbg ? (selected ? st::historyFileOutRadialFgSelected : st::historyFileOutRadialFg) : (selected ? st::historyFileInRadialFgSelected : st::historyFileInRadialFg);
+			_animation->radial.draw(p, rinner, st::msgFileRadialLine, fg);
+		}
+
+		auto icon = ([showPause, radial, this, loaded, outbg, selected] {
+			if (showPause) {
+				return &(outbg ? (selected ? st::historyFileOutPauseSelected : st::historyFileOutPause) : (selected ? st::historyFileInPauseSelected : st::historyFileInPause));
+			} else if (radial || _data->loading()) {
+				return &(outbg ? (selected ? st::historyFileOutCancelSelected : st::historyFileOutCancel) : (selected ? st::historyFileInCancelSelected : st::historyFileInCancel));
+			} else if (loaded) {
+				if (_data->isAudioFile() || _data->isVoiceMessage()) {
+					return &(outbg ? (selected ? st::historyFileOutPlaySelected : st::historyFileOutPlay) : (selected ? st::historyFileInPlaySelected : st::historyFileInPlay));
+				} else if (_data->isImage()) {
+					return &(outbg ? (selected ? st::historyFileOutImageSelected : st::historyFileOutImage) : (selected ? st::historyFileInImageSelected : st::historyFileInImage));
+				}
+				return &(outbg ? (selected ? st::historyFileOutDocumentSelected : st::historyFileOutDocument) : (selected ? st::historyFileInDocumentSelected : st::historyFileInDocument));
+			}
+			return &(outbg ? (selected ? st::historyFileOutDownloadSelected : st::historyFileOutDownload) : (selected ? st::historyFileInDownloadSelected : st::historyFileInDownload));
+		})();
+		icon->paintInCenter(p, inner);
+	}
+	auto namewidth = width() - nameleft - nameright;
+	auto statuswidth = namewidth;
+
+	auto voiceStatusOverride = QString();
+	if (auto voice = Get<HistoryDocumentVoice>()) {
+		const VoiceWaveform *wf = nullptr;
+		uchar norm_value = 0;
+		if (const auto voiceData = _data->voice()) {
+			wf = &voiceData->waveform;
+			if (wf->isEmpty()) {
+				wf = nullptr;
+				if (loaded) {
+					Local::countVoiceWaveform(_data);
+				}
+			} else if (wf->at(0) < 0) {
+				wf = nullptr;
+			} else {
+				norm_value = voiceData->wavemax;
+			}
+		}
+		auto progress = ([voice] {
+			if (voice->seeking()) {
+				return voice->seekingCurrent();
+			} else if (voice->_playback) {
+				return voice->_playback->a_progress.current();
+			}
+			return 0.;
+		})();
+		if (voice->seeking()) {
+			voiceStatusOverride = formatPlayedText(qRound(progress * voice->_lastDurationMs) / 1000, voice->_lastDurationMs / 1000);
+		}
+
+		// rescale waveform by going in waveform.size * bar_count 1D grid
+		auto active = outbg ? (selected ? st::msgWaveformOutActiveSelected : st::msgWaveformOutActive) : (selected ? st::msgWaveformInActiveSelected : st::msgWaveformInActive);
+		auto inactive = outbg ? (selected ? st::msgWaveformOutInactiveSelected : st::msgWaveformOutInactive) : (selected ? st::msgWaveformInInactiveSelected : st::msgWaveformInInactive);
+		auto wf_size = wf ? wf->size() : Media::Player::kWaveformSamplesCount;
+		auto availw = namewidth + st::msgWaveformSkip;
+		auto activew = qRound(availw * progress);
+		if (!outbg && !voice->_playback && _parent->data()->isMediaUnread()) {
+			activew = availw;
+		}
+		auto bar_count = qMin(availw / (st::msgWaveformBar + st::msgWaveformSkip), wf_size);
+		auto max_value = 0;
+		auto max_delta = st::msgWaveformMax - st::msgWaveformMin;
+		auto bottom = st::msgFilePadding.top() - topMinus + st::msgWaveformMax;
+		p.setPen(Qt::NoPen);
+		for (auto i = 0, bar_x = 0, sum_i = 0; i < wf_size; ++i) {
+			auto value = wf ? wf->at(i) : 0;
+			if (sum_i + bar_count >= wf_size) { // draw bar
+				sum_i = sum_i + bar_count - wf_size;
+				if (sum_i < (bar_count + 1) / 2) {
+					if (max_value < value) max_value = value;
+				}
+				auto bar_value = ((max_value * max_delta) + ((norm_value + 1) / 2)) / (norm_value + 1);
+
+				if (bar_x >= activew) {
+					p.fillRect(nameleft + bar_x, bottom - bar_value, st::msgWaveformBar, st::msgWaveformMin + bar_value, inactive);
+				} else if (bar_x + st::msgWaveformBar <= activew) {
+					p.fillRect(nameleft + bar_x, bottom - bar_value, st::msgWaveformBar, st::msgWaveformMin + bar_value, active);
+				} else {
+					p.fillRect(nameleft + bar_x, bottom - bar_value, activew - bar_x, st::msgWaveformMin + bar_value, active);
+					p.fillRect(nameleft + activew, bottom - bar_value, st::msgWaveformBar - (activew - bar_x), st::msgWaveformMin + bar_value, inactive);
+				}
+				bar_x += st::msgWaveformBar + st::msgWaveformSkip;
+
+				if (sum_i < (bar_count + 1) / 2) {
+					max_value = 0;
+				} else {
+					max_value = value;
+				}
+			} else {
+				if (max_value < value) max_value = value;
+
+				sum_i += bar_count;
+			}
+		}
+	} else if (auto named = Get<HistoryDocumentNamed>()) {
+		p.setFont(st::semiboldFont);
+		p.setPen(outbg ? (selected ? st::historyFileNameOutFgSelected : st::historyFileNameOutFg) : (selected ? st::historyFileNameInFgSelected : st::historyFileNameInFg));
+		if (namewidth < named->_namew) {
+			p.drawTextLeft(nameleft, nametop, width(), st::semiboldFont->elided(named->_name, namewidth, Qt::ElideMiddle));
+		} else {
+			p.drawTextLeft(nameleft, nametop, width(), named->_name, named->_namew);
+		}
+	}
+
+	auto statusText = voiceStatusOverride.isEmpty() ? _statusText : voiceStatusOverride;
+	auto status = outbg ? (selected ? st::mediaOutFgSelected : st::mediaOutFg) : (selected ? st::mediaInFgSelected : st::mediaInFg);
+	p.setFont(st::normalFont);
+	p.setPen(status);
+	p.drawTextLeft(nameleft, statustop, width(), statusText);
+
+	if (_parent->data()->isMediaUnread()) {
+		auto w = st::normalFont->width(statusText);
+		if (w + st::mediaUnreadSkip + st::mediaUnreadSize <= statuswidth) {
+			p.setPen(Qt::NoPen);
+			p.setBrush(outbg ? (selected ? st::msgFileOutBgSelected : st::msgFileOutBg) : (selected ? st::msgFileInBgSelected : st::msgFileInBg));
+
+			{
+				PainterHighQualityEnabler hq(p);
+				p.drawEllipse(rtlrect(nameleft + w + st::mediaUnreadSkip, statustop + st::mediaUnreadTop, st::mediaUnreadSize, st::mediaUnreadSize, width()));
+			}
+		}
+	}
+
+	if (auto captioned = Get<HistoryDocumentCaptioned>()) {
+		p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg));
+		captioned->_caption.draw(p, st::msgPadding.left(), bottom, captionw, style::al_left, 0, -1, selection);
+	}
+}
+
+TextState HistoryDocument::textState(QPoint point, StateRequest request) const {
+	auto result = TextState(_parent);
+
+	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) {
+		return result;
+	}
+
+	bool loaded = _data->loaded();
+
+	bool showPause = updateStatusText();
+
+	auto nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0, bottom = 0;
+	auto topMinus = isBubbleTop() ? 0 : st::msgFileTopMinus;
+	if (auto thumbed = Get<HistoryDocumentThumbed>()) {
+		nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right();
+		nameright = st::msgFileThumbPadding.left();
+		nametop = st::msgFileThumbNameTop - topMinus;
+		linktop = st::msgFileThumbLinkTop - topMinus;
+		bottom = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom() - topMinus;
+
+		QRect rthumb(rtlrect(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top() - topMinus, st::msgFileThumbSize, st::msgFileThumbSize, width()));
+
+		if ((_data->loading() || _data->uploading() || !loaded) && rthumb.contains(point)) {
+			result.link = (_data->loading() || _data->uploading()) ? _cancell : _savel;
+			return result;
+		}
+
+		if (_data->status != FileUploadFailed) {
+			if (rtlrect(nameleft, linktop, thumbed->_linkw, st::semiboldFont->height, width()).contains(point)) {
+				result.link = (_data->loading() || _data->uploading())
+					? thumbed->_linkcancell
+					: thumbed->_linksavel;
+				return result;
+			}
+		}
+	} else {
+		nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right();
+		nameright = st::msgFilePadding.left();
+		nametop = st::msgFileNameTop - topMinus;
+		bottom = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom() - topMinus;
+
+		QRect inner(rtlrect(st::msgFilePadding.left(), st::msgFilePadding.top() - topMinus, st::msgFileSize, st::msgFileSize, width()));
+		if ((_data->loading() || _data->uploading() || !loaded) && inner.contains(point)) {
+			result.link = (_data->loading() || _data->uploading()) ? _cancell : _savel;
+			return result;
+		}
+	}
+
+	if (auto voice = Get<HistoryDocumentVoice>()) {
+		auto namewidth = width() - nameleft - nameright;
+		auto waveformbottom = st::msgFilePadding.top() - topMinus + st::msgWaveformMax + st::msgWaveformMin;
+		if (QRect(nameleft, nametop, namewidth, waveformbottom - nametop).contains(point)) {
+			auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice);
+			if (state.id == AudioMsgId(_data, _parent->data()->fullId())
+				&& !Media::Player::IsStoppedOrStopping(state.state)) {
+				if (!voice->seeking()) {
+					voice->setSeekingStart((point.x() - nameleft) / float64(namewidth));
+				}
+				result.link = voice->_seekl;
+				return result;
+			}
+		}
+	}
+
+	auto painth = height();
+	if (auto captioned = Get<HistoryDocumentCaptioned>()) {
+		if (point.y() >= bottom) {
+			result = TextState(_parent, captioned->_caption.getState(
+				point - QPoint(st::msgPadding.left(), bottom),
+				width() - st::msgPadding.left() - st::msgPadding.right(),
+				request.forText()));
+			return result;
+		}
+		auto captionw = width() - st::msgPadding.left() - st::msgPadding.right();
+		painth -= captioned->_caption.countHeight(captionw);
+		if (isBubbleBottom()) {
+			painth -= st::msgPadding.bottom();
+		}
+	}
+	if (QRect(0, 0, width(), painth).contains(point) && !_data->loading() && !_data->uploading() && _data->isValid()) {
+		result.link = _openl;
+		return result;
+	}
+	return result;
+}
+
+void HistoryDocument::updatePressed(QPoint point) {
+	if (auto voice = Get<HistoryDocumentVoice>()) {
+		if (voice->seeking()) {
+			auto nameleft = 0, nameright = 0;
+			if (auto thumbed = Get<HistoryDocumentThumbed>()) {
+				nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right();
+				nameright = st::msgFileThumbPadding.left();
+			} else {
+				nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right();
+				nameright = st::msgFilePadding.left();
+			}
+			voice->setSeekingCurrent(snap((point.x() - nameleft) / float64(width() - nameleft - nameright), 0., 1.));
+			Auth().data().requestViewRepaint(_parent);
+		}
+	}
+}
+
+TextSelection HistoryDocument::adjustSelection(
+		TextSelection selection,
+		TextSelectType type) const {
+	if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
+		return captioned->_caption.adjustSelection(selection, type);
+	}
+	return selection;
+}
+
+uint16 HistoryDocument::fullSelectionLength() const {
+	if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
+		return captioned->_caption.length();
+	}
+	return 0;
+}
+
+bool HistoryDocument::hasTextForCopy() const {
+	return Has<HistoryDocumentCaptioned>();
+}
+
+TextWithEntities HistoryDocument::selectedText(TextSelection selection) const {
+	if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
+		const auto &caption = captioned->_caption;
+		return caption.originalTextWithEntities(selection, ExpandLinksAll);
+	}
+	return TextWithEntities();
+}
+
+bool HistoryDocument::uploading() const {
+	return _data->uploading();
+}
+
+void HistoryDocument::setStatusSize(int newSize, qint64 realDuration) const {
+	auto duration = _data->isSong()
+		? _data->song()->duration
+		: (_data->isVoiceMessage()
+			? _data->voice()->duration
+			: -1);
+	HistoryFileMedia::setStatusSize(newSize, _data->size, duration, realDuration);
+	if (auto thumbed = Get<HistoryDocumentThumbed>()) {
+		if (_statusSize == FileStatusSizeReady) {
+			thumbed->_link = lang(lng_media_download).toUpper();
+		} else if (_statusSize == FileStatusSizeLoaded) {
+			thumbed->_link = lang(lng_media_open_with).toUpper();
+		} else if (_statusSize == FileStatusSizeFailed) {
+			thumbed->_link = lang(lng_media_download).toUpper();
+		} else if (_statusSize >= 0) {
+			thumbed->_link = lang(lng_media_cancel).toUpper();
+		} else {
+			thumbed->_link = lang(lng_media_open_with).toUpper();
+		}
+		thumbed->_linkw = st::semiboldFont->width(thumbed->_link);
+	}
+}
+
+bool HistoryDocument::updateStatusText() const {
+	auto showPause = false;
+	auto statusSize = 0;
+	auto realDuration = 0;
+	if (_data->status == FileDownloadFailed || _data->status == FileUploadFailed) {
+		statusSize = FileStatusSizeFailed;
+	} else if (_data->uploading()) {
+		statusSize = _data->uploadingData->offset;
+	} else if (_data->loading()) {
+		statusSize = _data->loadOffset();
+	} else if (_data->loaded()) {
+		using State = Media::Player::State;
+		statusSize = FileStatusSizeLoaded;
+		if (_data->isVoiceMessage()) {
+			auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice);
+			if (state.id == AudioMsgId(_data, _parent->data()->fullId())
+				&& !Media::Player::IsStoppedOrStopping(state.state)) {
+				if (auto voice = Get<HistoryDocumentVoice>()) {
+					bool was = (voice->_playback != nullptr);
+					voice->ensurePlayback(this);
+					if (!was || state.position != voice->_playback->_position) {
+						auto prg = state.length ? snap(float64(state.position) / state.length, 0., 1.) : 0.;
+						if (voice->_playback->_position < state.position) {
+							voice->_playback->a_progress.start(prg);
+						} else {
+							voice->_playback->a_progress = anim::value(0., prg);
+						}
+						voice->_playback->_position = state.position;
+						voice->_playback->_a_progress.start();
+					}
+					voice->_lastDurationMs = static_cast<int>((state.length * 1000LL) / state.frequency); // Bad :(
+				}
+
+				statusSize = -1 - (state.position / state.frequency);
+				realDuration = (state.length / state.frequency);
+				showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
+			} else {
+				if (auto voice = Get<HistoryDocumentVoice>()) {
+					voice->checkPlaybackFinished();
+				}
+			}
+			if (!showPause && (state.id == AudioMsgId(_data, _parent->data()->fullId()))) {
+				showPause = Media::Player::instance()->isSeeking(AudioMsgId::Type::Voice);
+			}
+		} else if (_data->isAudioFile()) {
+			auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song);
+			if (state.id == AudioMsgId(_data, _parent->data()->fullId())
+				&& !Media::Player::IsStoppedOrStopping(state.state)) {
+				statusSize = -1 - (state.position / state.frequency);
+				realDuration = (state.length / state.frequency);
+				showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
+			} else {
+			}
+			if (!showPause && (state.id == AudioMsgId(_data, _parent->data()->fullId()))) {
+				showPause = Media::Player::instance()->isSeeking(AudioMsgId::Type::Song);
+			}
+		}
+	} else {
+		statusSize = FileStatusSizeReady;
+	}
+	if (statusSize != _statusSize) {
+		setStatusSize(statusSize, realDuration);
+	}
+	return showPause;
+}
+
+QMargins HistoryDocument::bubbleMargins() const {
+	return Get<HistoryDocumentThumbed>() ? QMargins(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top(), st::msgFileThumbPadding.left(), st::msgFileThumbPadding.bottom()) : st::msgPadding;
+}
+
+bool HistoryDocument::hideForwardedFrom() const {
+	return _data->isSong();
+}
+
+void HistoryDocument::step_voiceProgress(float64 ms, bool timer) {
+	if (anim::Disabled()) {
+		ms += (2 * AudioVoiceMsgUpdateView);
+	}
+	if (auto voice = Get<HistoryDocumentVoice>()) {
+		if (voice->_playback) {
+			float64 dt = ms / (2 * AudioVoiceMsgUpdateView);
+			if (dt >= 1) {
+				voice->_playback->_a_progress.stop();
+				voice->_playback->a_progress.finish();
+			} else {
+				voice->_playback->a_progress.update(qMin(dt, 1.), anim::linear);
+			}
+			if (timer) {
+				Auth().data().requestViewRepaint(_parent);
+			}
+		}
+	}
+}
+
+void HistoryDocument::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) {
+	if (auto voice = Get<HistoryDocumentVoice>()) {
+		if (pressed && p == voice->_seekl && !voice->seeking()) {
+			voice->startSeeking();
+		} else if (!pressed && voice->seeking()) {
+			auto type = AudioMsgId::Type::Voice;
+			auto state = Media::Player::mixer()->currentState(type);
+			if (state.id == AudioMsgId(_data, _parent->data()->fullId()) && state.length) {
+				auto currentProgress = voice->seekingCurrent();
+				auto currentPosition = state.frequency
+					? qRound(currentProgress * state.length * 1000. / state.frequency)
+					: 0;
+				Media::Player::mixer()->seek(type, currentPosition);
+
+				voice->ensurePlayback(this);
+				voice->_playback->_position = 0;
+				voice->_playback->a_progress = anim::value(currentProgress, currentProgress);
+			}
+			voice->stopSeeking();
+		}
+	}
+	HistoryFileMedia::clickHandlerPressedChanged(p, pressed);
+}
+
+void HistoryDocument::refreshParentId(not_null<HistoryItem*> realParent) {
+	HistoryFileMedia::refreshParentId(realParent);
+
+	const auto fullId = realParent->fullId();
+	if (auto thumbed = Get<HistoryDocumentThumbed>()) {
+		if (thumbed->_linksavel) {
+			thumbed->_linksavel->setMessageId(fullId);
+			thumbed->_linkcancell->setMessageId(fullId);
+		}
+	}
+	if (auto voice = Get<HistoryDocumentVoice>()) {
+		if (voice->_seekl) {
+			voice->_seekl->setMessageId(fullId);
+		}
+	}
+}
+
+void HistoryDocument::parentTextUpdated() {
+	auto caption = (_parent->media() == this)
+		? createCaption(_parent->data())
+		: Text();
+	if (!caption.isEmpty()) {
+		AddComponents(HistoryDocumentCaptioned::Bit());
+		auto captioned = Get<HistoryDocumentCaptioned>();
+		captioned->_caption = std::move(caption);
+	} else {
+		RemoveComponents(HistoryDocumentCaptioned::Bit());
+	}
+	Auth().data().requestViewResize(_parent);
+}
+
+TextWithEntities HistoryDocument::getCaption() const {
+	if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
+		return captioned->_caption.originalTextWithEntities();
+	}
+	return TextWithEntities();
+}
diff --git a/Telegram/SourceFiles/history/media/history_media_document.h b/Telegram/SourceFiles/history/media/history_media_document.h
new file mode 100644
index 000000000..07ce6b89d
--- /dev/null
+++ b/Telegram/SourceFiles/history/media/history_media_document.h
@@ -0,0 +1,77 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+#include "history/media/history_media_file.h"
+#include "base/runtime_composer.h"
+
+struct HistoryDocumentNamed;
+
+class HistoryDocument
+	: public HistoryFileMedia
+	, public RuntimeComposer<HistoryDocument> {
+public:
+	HistoryDocument(
+		not_null<Element*> parent,
+		not_null<DocumentData*> document);
+
+	HistoryMediaType type() const override;
+
+	void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override;
+	TextState textState(QPoint point, StateRequest request) const override;
+	void updatePressed(QPoint point) override;
+
+	[[nodiscard]] TextSelection adjustSelection(
+		TextSelection selection,
+		TextSelectType type) const override;
+	uint16 fullSelectionLength() const override;
+	bool hasTextForCopy() const override;
+
+	TextWithEntities selectedText(TextSelection selection) const override;
+
+	bool uploading() const override;
+
+	DocumentData *getDocument() const override {
+		return _data;
+	}
+
+	TextWithEntities getCaption() const override;
+	bool needsBubble() const override {
+		return true;
+	}
+	bool customInfoLayout() const override {
+		return false;
+	}
+	QMargins bubbleMargins() const override;
+	bool hideForwardedFrom() const override;
+
+	void step_voiceProgress(float64 ms, bool timer);
+
+	void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
+
+	void refreshParentId(not_null<HistoryItem*> realParent) override;
+	void parentTextUpdated() override;
+
+protected:
+	float64 dataProgress() const override;
+	bool dataFinished() const override;
+	bool dataLoaded() const override;
+
+private:
+	QSize countOptimalSize() override;
+	QSize countCurrentSize(int newWidth) override;
+
+	void createComponents(bool caption);
+	void fillNamedFromData(HistoryDocumentNamed *named);
+
+	void setStatusSize(int newSize, qint64 realDuration = 0) const;
+	bool updateStatusText() const; // returns showPause
+
+	not_null<DocumentData*> _data;
+
+};
diff --git a/Telegram/SourceFiles/history/media/history_media_file.cpp b/Telegram/SourceFiles/history/media/history_media_file.cpp
new file mode 100644
index 000000000..8899cca56
--- /dev/null
+++ b/Telegram/SourceFiles/history/media/history_media_file.cpp
@@ -0,0 +1,126 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "history/media/history_media_file.h"
+
+#include "lang/lang_keys.h"
+#include "layout.h"
+#include "auth_session.h"
+#include "history/history_item.h"
+#include "data/data_document.h"
+#include "data/data_session.h"
+#include "styles/style_history.h"
+
+void HistoryFileMedia::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
+	if (p == _savel || p == _cancell) {
+		if (active && !dataLoaded()) {
+			ensureAnimation();
+			_animation->a_thumbOver.start([this] { thumbAnimationCallback(); }, 0., 1., st::msgFileOverDuration);
+		} else if (!active && _animation && !dataLoaded()) {
+			_animation->a_thumbOver.start([this] { thumbAnimationCallback(); }, 1., 0., st::msgFileOverDuration);
+		}
+	}
+}
+
+void HistoryFileMedia::thumbAnimationCallback() {
+	Auth().data().requestViewRepaint(_parent);
+}
+
+void HistoryFileMedia::clickHandlerPressedChanged(
+		const ClickHandlerPtr &handler,
+		bool pressed) {
+	Auth().data().requestViewRepaint(_parent);
+}
+
+void HistoryFileMedia::setLinks(
+		FileClickHandlerPtr &&openl,
+		FileClickHandlerPtr &&savel,
+		FileClickHandlerPtr &&cancell) {
+	_openl = std::move(openl);
+	_savel = std::move(savel);
+	_cancell = std::move(cancell);
+}
+
+void HistoryFileMedia::refreshParentId(not_null<HistoryItem*> realParent) {
+	const auto contextId = realParent->fullId();
+	_openl->setMessageId(contextId);
+	_savel->setMessageId(contextId);
+	_cancell->setMessageId(contextId);
+}
+
+void HistoryFileMedia::setStatusSize(int newSize, int fullSize, int duration, qint64 realDuration) const {
+	_statusSize = newSize;
+	if (_statusSize == FileStatusSizeReady) {
+		_statusText = (duration >= 0) ? formatDurationAndSizeText(duration, fullSize) : (duration < -1 ? formatGifAndSizeText(fullSize) : formatSizeText(fullSize));
+	} else if (_statusSize == FileStatusSizeLoaded) {
+		_statusText = (duration >= 0) ? formatDurationText(duration) : (duration < -1 ? qsl("GIF") : formatSizeText(fullSize));
+	} else if (_statusSize == FileStatusSizeFailed) {
+		_statusText = lang(lng_attach_failed);
+	} else if (_statusSize >= 0) {
+		_statusText = formatDownloadText(_statusSize, fullSize);
+	} else {
+		_statusText = formatPlayedText(-_statusSize - 1, realDuration);
+	}
+}
+
+void HistoryFileMedia::step_radial(TimeMs ms, bool timer) {
+	const auto updateRadial = [&] {
+		return _animation->radial.update(
+			dataProgress(),
+			dataFinished(),
+			ms);
+	};
+	if (timer) {
+		if (!anim::Disabled() || updateRadial()) {
+			Auth().data().requestViewRepaint(_parent);
+		}
+	} else {
+		updateRadial();
+		if (!_animation->radial.animating()) {
+			checkAnimationFinished();
+		}
+	}
+}
+
+void HistoryFileMedia::ensureAnimation() const {
+	if (!_animation) {
+		_animation = std::make_unique<AnimationData>(animation(const_cast<HistoryFileMedia*>(this), &HistoryFileMedia::step_radial));
+	}
+}
+
+void HistoryFileMedia::checkAnimationFinished() const {
+	if (_animation && !_animation->a_thumbOver.animating() && !_animation->radial.animating()) {
+		if (dataLoaded()) {
+			_animation.reset();
+		}
+	}
+}
+void HistoryFileMedia::setDocumentLinks(
+		not_null<DocumentData*> document,
+		not_null<HistoryItem*> realParent,
+		bool inlinegif) {
+	FileClickHandlerPtr open, save;
+	const auto context = realParent->fullId();
+	if (inlinegif) {
+		open = std::make_shared<GifOpenClickHandler>(document, context);
+	} else {
+		open = std::make_shared<DocumentOpenClickHandler>(document, context);
+	}
+	if (inlinegif) {
+		save = std::make_shared<GifOpenClickHandler>(document, context);
+	} else if (document->isVoiceMessage()) {
+		save = std::make_shared<DocumentOpenClickHandler>(document, context);
+	} else {
+		save = std::make_shared<DocumentSaveClickHandler>(document, context);
+	}
+	setLinks(
+		std::move(open),
+		std::move(save),
+		std::make_shared<DocumentCancelClickHandler>(document, context));
+}
+
+HistoryFileMedia::~HistoryFileMedia() = default;
diff --git a/Telegram/SourceFiles/history/media/history_media_file.h b/Telegram/SourceFiles/history/media/history_media_file.h
new file mode 100644
index 000000000..3b24f2029
--- /dev/null
+++ b/Telegram/SourceFiles/history/media/history_media_file.h
@@ -0,0 +1,101 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+#include "history/media/history_media.h"
+#include "ui/effects/radial_animation.h"
+
+class HistoryFileMedia : public HistoryMedia {
+public:
+	HistoryFileMedia(
+		not_null<Element*> parent,
+		not_null<HistoryItem*> realParent)
+	: HistoryMedia(parent)
+	, _realParent(realParent) {
+	}
+
+	bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override {
+		return p == _openl || p == _savel || p == _cancell;
+	}
+	bool dragItemByHandler(const ClickHandlerPtr &p) const override {
+		return p == _openl || p == _savel || p == _cancell;
+	}
+
+	void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
+	void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
+
+	void refreshParentId(not_null<HistoryItem*> realParent) override;
+
+	bool allowsFastShare() const override {
+		return true;
+	}
+
+	~HistoryFileMedia();
+
+protected:
+	using FileClickHandlerPtr = std::shared_ptr<FileClickHandler>;
+
+	not_null<HistoryItem*> _realParent;
+	FileClickHandlerPtr _openl, _savel, _cancell;
+
+	void setLinks(
+		FileClickHandlerPtr &&openl,
+		FileClickHandlerPtr &&savel,
+		FileClickHandlerPtr &&cancell);
+	void setDocumentLinks(
+		not_null<DocumentData*> document,
+		not_null<HistoryItem*> realParent,
+		bool inlinegif = false);
+
+	// >= 0 will contain download / upload string, _statusSize = loaded bytes
+	// < 0 will contain played string, _statusSize = -(seconds + 1) played
+	// 0x7FFFFFF0 will contain status for not yet downloaded file
+	// 0x7FFFFFF1 will contain status for already downloaded file
+	// 0x7FFFFFF2 will contain status for failed to download / upload file
+	mutable int _statusSize;
+	mutable QString _statusText;
+
+	// duration = -1 - no duration, duration = -2 - "GIF" duration
+	void setStatusSize(int newSize, int fullSize, int duration, qint64 realDuration) const;
+
+	void step_radial(TimeMs ms, bool timer);
+	void thumbAnimationCallback();
+
+	void ensureAnimation() const;
+	void checkAnimationFinished() const;
+
+	bool isRadialAnimation(TimeMs ms) const {
+		if (!_animation || !_animation->radial.animating()) return false;
+
+		_animation->radial.step(ms);
+		return _animation && _animation->radial.animating();
+	}
+	bool isThumbAnimation(TimeMs ms) const {
+		if (_animation) {
+			if (_animation->a_thumbOver.animating(ms)) {
+				return true;
+			}
+			checkAnimationFinished();
+		}
+		return false;
+	}
+
+	virtual float64 dataProgress() const = 0;
+	virtual bool dataFinished() const = 0;
+	virtual bool dataLoaded() const = 0;
+
+	struct AnimationData {
+		AnimationData(AnimationCallbacks &&radialCallbacks)
+			: radial(std::move(radialCallbacks)) {
+		}
+		Animation a_thumbOver;
+		Ui::RadialAnimation radial;
+	};
+	mutable std::unique_ptr<AnimationData> _animation;
+
+};
diff --git a/Telegram/SourceFiles/history/media/history_media_game.cpp b/Telegram/SourceFiles/history/media/history_media_game.cpp
new file mode 100644
index 000000000..09072319d
--- /dev/null
+++ b/Telegram/SourceFiles/history/media/history_media_game.cpp
@@ -0,0 +1,437 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "history/media/history_media_game.h"
+
+#include "lang/lang_keys.h"
+#include "layout.h"
+#include "auth_session.h"
+#include "history/history_item_components.h"
+#include "history/view/history_view_element.h"
+#include "history/view/history_view_cursor_state.h"
+#include "history/media/history_media_common.h"
+#include "ui/text_options.h"
+#include "data/data_session.h"
+#include "data/data_game.h"
+#include "data/data_media_types.h"
+#include "styles/style_history.h"
+
+namespace {
+
+using TextState = HistoryView::TextState;
+
+} // namespace
+
+HistoryGame::HistoryGame(
+	not_null<Element*> parent,
+	not_null<GameData*> data,
+	const TextWithEntities &consumed)
+: HistoryMedia(parent)
+, _data(data)
+, _title(st::msgMinWidth - st::webPageLeft)
+, _description(st::msgMinWidth - st::webPageLeft) {
+	if (!consumed.text.isEmpty()) {
+		_description.setMarkedText(
+			st::webPageDescriptionStyle,
+			consumed,
+			Ui::ItemTextOptions(parent->data()));
+	}
+	Auth().data().registerGameView(_data, _parent);
+}
+
+QSize HistoryGame::countOptimalSize() {
+	auto lineHeight = unitedLineHeight();
+
+	const auto item = _parent->data();
+	if (!_openl && IsServerMsgId(item->id)) {
+		const auto row = 0;
+		const auto column = 0;
+		_openl = std::make_shared<ReplyMarkupClickHandler>(
+			row,
+			column,
+			item->fullId());
+	}
+
+	auto title = TextUtilities::SingleLine(_data->title);
+
+	// init attach
+	if (!_attach) {
+		_attach = CreateAttach(_parent, _data->document, _data->photo);
+	}
+
+	// init strings
+	if (_description.isEmpty() && !_data->description.isEmpty()) {
+		auto text = _data->description;
+		if (!text.isEmpty()) {
+			if (!_attach) {
+				text += _parent->skipBlock();
+			}
+			auto marked = TextWithEntities { text };
+			auto parseFlags = TextParseLinks | TextParseMultiline | TextParseRichText;
+			TextUtilities::ParseEntities(marked, parseFlags);
+			_description.setMarkedText(
+				st::webPageDescriptionStyle,
+				marked,
+				Ui::WebpageTextDescriptionOptions());
+		}
+	}
+	if (_title.isEmpty() && !title.isEmpty()) {
+		_title.setText(
+			st::webPageTitleStyle,
+			title,
+			Ui::WebpageTextTitleOptions());
+	}
+
+	// init dimensions
+	auto l = st::msgPadding.left() + st::webPageLeft, r = st::msgPadding.right();
+	auto skipBlockWidth = _parent->skipBlockWidth();
+	auto maxWidth = skipBlockWidth;
+	auto minHeight = 0;
+
+	auto titleMinHeight = _title.isEmpty() ? 0 : lineHeight;
+	// enable any count of lines in game description / message
+	auto descMaxLines = 4096;
+	auto descriptionMinHeight = _description.isEmpty() ? 0 : qMin(_description.minHeight(), descMaxLines * lineHeight);
+
+	if (!_title.isEmpty()) {
+		accumulate_max(maxWidth, _title.maxWidth());
+		minHeight += titleMinHeight;
+	}
+	if (!_description.isEmpty()) {
+		accumulate_max(maxWidth, _description.maxWidth());
+		minHeight += descriptionMinHeight;
+	}
+	if (_attach) {
+		auto attachAtTop = !_titleLines && !_descriptionLines;
+		if (!attachAtTop) minHeight += st::mediaInBubbleSkip;
+
+		_attach->initDimensions();
+		QMargins bubble(_attach->bubbleMargins());
+		auto maxMediaWidth = _attach->maxWidth() - bubble.left() - bubble.right();
+		if (isBubbleBottom() && _attach->customInfoLayout()) {
+			maxMediaWidth += skipBlockWidth;
+		}
+		accumulate_max(maxWidth, maxMediaWidth);
+		minHeight += _attach->minHeight() - bubble.top() - bubble.bottom();
+	}
+	maxWidth += st::msgPadding.left() + st::webPageLeft + st::msgPadding.right();
+	auto padding = inBubblePadding();
+	minHeight += padding.top() + padding.bottom();
+
+	if (!_gameTagWidth) {
+		_gameTagWidth = st::msgDateFont->width(lang(lng_game_tag).toUpper());
+	}
+	return { maxWidth, minHeight };
+}
+
+void HistoryGame::refreshParentId(not_null<HistoryItem*> realParent) {
+	if (_openl) {
+		_openl->setMessageId(realParent->fullId());
+	}
+	if (_attach) {
+		_attach->refreshParentId(realParent);
+	}
+}
+
+QSize HistoryGame::countCurrentSize(int newWidth) {
+	accumulate_min(newWidth, maxWidth());
+	auto innerWidth = newWidth - st::msgPadding.left() - st::webPageLeft - st::msgPadding.right();
+
+	// enable any count of lines in game description / message
+	auto linesMax = 4096;
+	auto lineHeight = unitedLineHeight();
+	auto newHeight = 0;
+	if (_title.isEmpty()) {
+		_titleLines = 0;
+	} else {
+		if (_title.countHeight(innerWidth) < 2 * st::webPageTitleFont->height) {
+			_titleLines = 1;
+		} else {
+			_titleLines = 2;
+		}
+		newHeight += _titleLines * lineHeight;
+	}
+
+	if (_description.isEmpty()) {
+		_descriptionLines = 0;
+	} else {
+		auto descriptionHeight = _description.countHeight(innerWidth);
+		if (descriptionHeight < (linesMax - _titleLines) * st::webPageDescriptionFont->height) {
+			_descriptionLines = (descriptionHeight / st::webPageDescriptionFont->height);
+		} else {
+			_descriptionLines = (linesMax - _titleLines);
+		}
+		newHeight += _descriptionLines * lineHeight;
+	}
+
+	if (_attach) {
+		auto attachAtTop = !_titleLines && !_descriptionLines;
+		if (!attachAtTop) newHeight += st::mediaInBubbleSkip;
+
+		QMargins bubble(_attach->bubbleMargins());
+
+		_attach->resizeGetHeight(innerWidth + bubble.left() + bubble.right());
+		newHeight += _attach->height() - bubble.top() - bubble.bottom();
+		if (isBubbleBottom() && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > innerWidth + bubble.left() + bubble.right()) {
+			newHeight += bottomInfoPadding();
+		}
+	}
+	auto padding = inBubblePadding();
+	newHeight += padding.top() + padding.bottom();
+
+	return { newWidth, newHeight };
+}
+
+TextSelection HistoryGame::toDescriptionSelection(
+		TextSelection selection) const {
+	return HistoryView::UnshiftItemSelection(selection, _title);
+}
+
+TextSelection HistoryGame::fromDescriptionSelection(
+		TextSelection selection) const {
+	return HistoryView::ShiftItemSelection(selection, _title);
+}
+
+void HistoryGame::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const {
+	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
+	auto paintw = width(), painth = height();
+
+	auto outbg = _parent->hasOutLayout();
+	bool selected = (selection == FullSelection);
+
+	auto &barfg = selected ? (outbg ? st::msgOutReplyBarSelColor : st::msgInReplyBarSelColor) : (outbg ? st::msgOutReplyBarColor : st::msgInReplyBarColor);
+	auto &semibold = selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg);
+	auto &regular = selected ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) : (outbg ? st::msgOutDateFg : st::msgInDateFg);
+
+	QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins());
+	auto padding = inBubblePadding();
+	auto tshift = padding.top();
+	auto bshift = padding.bottom();
+	paintw -= padding.left() + padding.right();
+	if (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > paintw + bubble.left() + bubble.right()) {
+		bshift += bottomInfoPadding();
+	}
+
+	QRect bar(rtlrect(st::msgPadding.left(), tshift, st::webPageBar, height() - tshift - bshift, width()));
+	p.fillRect(bar, barfg);
+
+	auto lineHeight = unitedLineHeight();
+	if (_titleLines) {
+		p.setPen(semibold);
+		auto endskip = 0;
+		if (_title.hasSkipBlock()) {
+			endskip = _parent->skipBlockWidth();
+		}
+		_title.drawLeftElided(p, padding.left(), tshift, paintw, width(), _titleLines, style::al_left, 0, -1, endskip, false, selection);
+		tshift += _titleLines * lineHeight;
+	}
+	if (_descriptionLines) {
+		p.setPen(outbg ? st::webPageDescriptionOutFg : st::webPageDescriptionInFg);
+		auto endskip = 0;
+		if (_description.hasSkipBlock()) {
+			endskip = _parent->skipBlockWidth();
+		}
+		_description.drawLeftElided(p, padding.left(), tshift, paintw, width(), _descriptionLines, style::al_left, 0, -1, endskip, false, toDescriptionSelection(selection));
+		tshift += _descriptionLines * lineHeight;
+	}
+	if (_attach) {
+		auto attachAtTop = !_titleLines && !_descriptionLines;
+		if (!attachAtTop) tshift += st::mediaInBubbleSkip;
+
+		auto attachLeft = padding.left() - bubble.left();
+		auto attachTop = tshift - bubble.top();
+		if (rtl()) attachLeft = width() - attachLeft - _attach->width();
+
+		auto attachSelection = selected ? FullSelection : TextSelection { 0, 0 };
+
+		p.translate(attachLeft, attachTop);
+		_attach->draw(p, r.translated(-attachLeft, -attachTop), attachSelection, ms);
+		auto pixwidth = _attach->width();
+		auto pixheight = _attach->height();
+
+		auto gameW = _gameTagWidth + 2 * st::msgDateImgPadding.x();
+		auto gameH = st::msgDateFont->height + 2 * st::msgDateImgPadding.y();
+		auto gameX = pixwidth - st::msgDateImgDelta - gameW;
+		auto gameY = pixheight - st::msgDateImgDelta - gameH;
+
+		App::roundRect(p, rtlrect(gameX, gameY, gameW, gameH, pixwidth), selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners);
+
+		p.setFont(st::msgDateFont);
+		p.setPen(st::msgDateImgFg);
+		p.drawTextLeft(gameX + st::msgDateImgPadding.x(), gameY + st::msgDateImgPadding.y(), pixwidth, lang(lng_game_tag).toUpper());
+
+		p.translate(-attachLeft, -attachTop);
+	}
+}
+
+TextState HistoryGame::textState(QPoint point, StateRequest request) const {
+	auto result = TextState(_parent);
+
+	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) {
+		return result;
+	}
+	auto paintw = width(), painth = height();
+
+	QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins());
+	auto padding = inBubblePadding();
+	auto tshift = padding.top();
+	auto bshift = padding.bottom();
+	if (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > paintw + bubble.left() + bubble.right()) {
+		bshift += bottomInfoPadding();
+	}
+	paintw -= padding.left() + padding.right();
+
+	auto inThumb = false;
+	auto symbolAdd = 0;
+	auto lineHeight = unitedLineHeight();
+	if (_titleLines) {
+		if (point.y() >= tshift && point.y() < tshift + _titleLines * lineHeight) {
+			Text::StateRequestElided titleRequest = request.forText();
+			titleRequest.lines = _titleLines;
+			result = TextState(_parent, _title.getStateElidedLeft(
+				point - QPoint(padding.left(), tshift),
+				paintw,
+				width(),
+				titleRequest));
+		} else if (point.y() >= tshift + _titleLines * lineHeight) {
+			symbolAdd += _title.length();
+		}
+		tshift += _titleLines * lineHeight;
+	}
+	if (_descriptionLines) {
+		if (point.y() >= tshift && point.y() < tshift + _descriptionLines * lineHeight) {
+			Text::StateRequestElided descriptionRequest = request.forText();
+			descriptionRequest.lines = _descriptionLines;
+			result = TextState(_parent, _description.getStateElidedLeft(
+				point - QPoint(padding.left(), tshift),
+				paintw,
+				width(),
+				descriptionRequest));
+		} else if (point.y() >= tshift + _descriptionLines * lineHeight) {
+			symbolAdd += _description.length();
+		}
+		tshift += _descriptionLines * lineHeight;
+	}
+	if (inThumb) {
+		if (!_parent->data()->isLogEntry()) {
+			result.link = _openl;
+		}
+	} else if (_attach) {
+		auto attachAtTop = !_titleLines && !_descriptionLines;
+		if (!attachAtTop) tshift += st::mediaInBubbleSkip;
+
+		auto attachLeft = padding.left() - bubble.left();
+		auto attachTop = tshift - bubble.top();
+		if (rtl()) attachLeft = width() - attachLeft - _attach->width();
+
+		if (QRect(attachLeft, tshift, _attach->width(), height() - tshift - bshift).contains(point)) {
+			if (_attach->isReadyForOpen()) {
+				if (!_parent->data()->isLogEntry()) {
+					result.link = _openl;
+				}
+			} else {
+				result = _attach->textState(point - QPoint(attachLeft, attachTop), request);
+			}
+		}
+	}
+
+	result.symbol += symbolAdd;
+	return result;
+}
+
+TextSelection HistoryGame::adjustSelection(TextSelection selection, TextSelectType type) const {
+	if (!_descriptionLines || selection.to <= _title.length()) {
+		return _title.adjustSelection(selection, type);
+	}
+	auto descriptionSelection = _description.adjustSelection(toDescriptionSelection(selection), type);
+	if (selection.from >= _title.length()) {
+		return fromDescriptionSelection(descriptionSelection);
+	}
+	auto titleSelection = _title.adjustSelection(selection, type);
+	return { titleSelection.from, fromDescriptionSelection(descriptionSelection).to };
+}
+
+void HistoryGame::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
+	if (_attach) {
+		_attach->clickHandlerActiveChanged(p, active);
+	}
+}
+
+void HistoryGame::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) {
+	if (_attach) {
+		_attach->clickHandlerPressedChanged(p, pressed);
+	}
+}
+
+TextWithEntities HistoryGame::selectedText(TextSelection selection) const {
+	auto titleResult = _title.originalTextWithEntities(
+		selection,
+		ExpandLinksAll);
+	auto descriptionResult = _description.originalTextWithEntities(
+		toDescriptionSelection(selection),
+		ExpandLinksAll);
+	if (titleResult.text.isEmpty()) {
+		return descriptionResult;
+	} else if (descriptionResult.text.isEmpty()) {
+		return titleResult;
+	}
+
+	titleResult.text += '\n';
+	TextUtilities::Append(titleResult, std::move(descriptionResult));
+	return titleResult;
+}
+
+void HistoryGame::playAnimation(bool autoplay) {
+	if (_attach) {
+		if (autoplay) {
+			_attach->autoplayAnimation();
+		} else {
+			_attach->playAnimation();
+		}
+	}
+}
+
+QMargins HistoryGame::inBubblePadding() const {
+	auto lshift = st::msgPadding.left() + st::webPageLeft;
+	auto rshift = st::msgPadding.right();
+	auto bshift = isBubbleBottom() ? st::msgPadding.left() : st::mediaInBubbleSkip;
+	auto tshift = isBubbleTop() ? st::msgPadding.left() : st::mediaInBubbleSkip;
+	return QMargins(lshift, tshift, rshift, bshift);
+}
+
+int HistoryGame::bottomInfoPadding() const {
+	if (!isBubbleBottom()) return 0;
+
+	auto result = st::msgDateFont->height;
+
+	// we use padding greater than st::msgPadding.bottom() in the
+	// bottom of the bubble so that the left line looks pretty.
+	// but if we have bottom skip because of the info display
+	// we don't need that additional padding so we replace it
+	// back with st::msgPadding.bottom() instead of left().
+	result += st::msgPadding.bottom() - st::msgPadding.left();
+	return result;
+}
+
+void HistoryGame::parentTextUpdated() {
+	if (const auto media = _parent->data()->media()) {
+		const auto consumed = media->consumedMessageText();
+		if (!consumed.text.isEmpty()) {
+			_description.setMarkedText(
+				st::webPageDescriptionStyle,
+				consumed,
+				Ui::ItemTextOptions(_parent->data()));
+		} else {
+			_description = Text(st::msgMinWidth - st::webPageLeft);
+		}
+		Auth().data().requestViewResize(_parent);
+	}
+}
+
+HistoryGame::~HistoryGame() {
+	Auth().data().unregisterGameView(_data, _parent);
+}
diff --git a/Telegram/SourceFiles/history/media/history_media_game.h b/Telegram/SourceFiles/history/media/history_media_game.h
new file mode 100644
index 000000000..a9cc3a766
--- /dev/null
+++ b/Telegram/SourceFiles/history/media/history_media_game.h
@@ -0,0 +1,104 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+#include "history/media/history_media.h"
+
+class ReplyMarkupClickHandler;
+
+class HistoryGame : public HistoryMedia {
+public:
+	HistoryGame(
+		not_null<Element*> parent,
+		not_null<GameData*> data,
+		const TextWithEntities &consumed);
+
+	HistoryMediaType type() const override {
+		return MediaTypeGame;
+	}
+
+	void refreshParentId(not_null<HistoryItem*> realParent) override;
+
+	void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override;
+	TextState textState(QPoint point, StateRequest request) const override;
+
+	[[nodiscard]] TextSelection adjustSelection(
+		TextSelection selection,
+		TextSelectType type) const override;
+	uint16 fullSelectionLength() const override {
+		return _title.length() + _description.length();
+	}
+	bool hasTextForCopy() const override {
+		return false; // we do not add _title and _description in FullSelection text copy.
+	}
+
+	bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override {
+		return _attach && _attach->toggleSelectionByHandlerClick(p);
+	}
+	bool dragItemByHandler(const ClickHandlerPtr &p) const override {
+		return _attach && _attach->dragItemByHandler(p);
+	}
+
+	TextWithEntities selectedText(TextSelection selection) const override;
+
+	void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
+	void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
+
+	PhotoData *getPhoto() const override {
+		return _attach ? _attach->getPhoto() : nullptr;
+	}
+	DocumentData *getDocument() const override {
+		return _attach ? _attach->getDocument() : nullptr;
+	}
+	void stopAnimation() override {
+		if (_attach) _attach->stopAnimation();
+	}
+
+	not_null<GameData*> game() {
+		return _data;
+	}
+
+	bool needsBubble() const override {
+		return true;
+	}
+	bool customInfoLayout() const override {
+		return false;
+	}
+	bool allowsFastShare() const override {
+		return true;
+	}
+
+	HistoryMedia *attach() const {
+		return _attach.get();
+	}
+
+	void parentTextUpdated() override;
+
+	~HistoryGame();
+
+private:
+	void playAnimation(bool autoplay) override;
+	QSize countOptimalSize() override;
+	QSize countCurrentSize(int newWidth) override;
+
+	TextSelection toDescriptionSelection(TextSelection selection) const;
+	TextSelection fromDescriptionSelection(TextSelection selection) const;
+	QMargins inBubblePadding() const;
+	int bottomInfoPadding() const;
+
+	not_null<GameData*> _data;
+	std::shared_ptr<ReplyMarkupClickHandler> _openl;
+	std::unique_ptr<HistoryMedia> _attach;
+
+	int _titleLines, _descriptionLines;
+
+	Text _title, _description;
+
+	int _gameTagWidth = 0;
+
+};
diff --git a/Telegram/SourceFiles/history/media/history_media_gif.cpp b/Telegram/SourceFiles/history/media/history_media_gif.cpp
new file mode 100644
index 000000000..0b5a01126
--- /dev/null
+++ b/Telegram/SourceFiles/history/media/history_media_gif.cpp
@@ -0,0 +1,924 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "history/media/history_media_gif.h"
+
+#include "lang/lang_keys.h"
+#include "layout.h"
+#include "mainwindow.h"
+#include "auth_session.h"
+#include "media/media_audio.h"
+#include "media/media_clip_reader.h"
+#include "media/player/media_player_round_controller.h"
+#include "media/view/media_clip_playback.h"
+#include "boxes/confirm_box.h"
+#include "history/history_item_components.h"
+#include "history/history_item.h"
+#include "history/view/history_view_element.h"
+#include "history/view/history_view_cursor_state.h"
+#include "window/window_controller.h"
+#include "ui/image/image.h"
+#include "data/data_session.h"
+#include "data/data_document.h"
+#include "styles/style_history.h"
+
+namespace {
+
+using TextState = HistoryView::TextState;
+
+} // namespace
+
+namespace {
+
+constexpr auto kMaxGifForwardedBarLines = 4;
+
+int gifMaxStatusWidth(DocumentData *document) {
+	auto result = st::normalFont->width(formatDownloadText(document->size, document->size));
+	accumulate_max(result, st::normalFont->width(formatGifAndSizeText(document->size)));
+	return result;
+}
+
+} // namespace
+
+HistoryGif::HistoryGif(
+	not_null<Element*> parent,
+	not_null<DocumentData*> document)
+: HistoryFileMedia(parent, parent->data())
+, _data(document)
+, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) {
+	const auto item = parent->data();
+	setDocumentLinks(_data, item, true);
+
+	setStatusSize(FileStatusSizeReady);
+
+	_caption = createCaption(item);
+	_data->thumb->load(item->fullId());
+}
+
+QSize HistoryGif::countOptimalSize() {
+	if (_parent->media() != this) {
+		_caption = Text();
+	} else if (_caption.hasSkipBlock()) {
+		_caption.updateSkipBlock(
+			_parent->skipBlockWidth(),
+			_parent->skipBlockHeight());
+	}
+	if (!_openInMediaviewLink) {
+		_openInMediaviewLink = std::make_shared<DocumentOpenClickHandler>(
+			_data,
+			_parent->data()->fullId());
+	}
+
+	auto tw = 0;
+	auto th = 0;
+	if (_gif && _gif->state() == Media::Clip::State::Error) {
+		if (!_gif->autoplay()) {
+			Ui::show(Box<InformBox>(lang(lng_gif_error)));
+		}
+		setClipReader(Media::Clip::ReaderPointer::Bad());
+	}
+
+	const auto reader = currentReader();
+	if (reader) {
+		tw = ConvertScale(reader->width());
+		th = ConvertScale(reader->height());
+	} else {
+		tw = ConvertScale(_data->dimensions.width()), th = ConvertScale(_data->dimensions.height());
+		if (!tw || !th) {
+			tw = ConvertScale(_data->thumb->width());
+			th = ConvertScale(_data->thumb->height());
+		}
+	}
+	const auto maxSize = _data->isVideoMessage()
+		? st::maxVideoMessageSize
+		: st::maxGifSize;
+	if (tw > maxSize) {
+		th = (maxSize * th) / tw;
+		tw = maxSize;
+	}
+	if (th > maxSize) {
+		tw = (maxSize * tw) / th;
+		th = maxSize;
+	}
+	if (!tw || !th) {
+		tw = th = 1;
+	}
+	_thumbw = tw;
+	_thumbh = th;
+	auto maxWidth = qMax(tw, st::minPhotoSize);
+	auto minHeight = qMax(th, st::minPhotoSize);
+	accumulate_max(maxWidth, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x()));
+	if (!reader) {
+		accumulate_max(maxWidth, gifMaxStatusWidth(_data) + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x()));
+	}
+	if (_parent->hasBubble()) {
+		if (!_caption.isEmpty()) {
+			auto captionw = maxWidth - st::msgPadding.left() - st::msgPadding.right();
+			minHeight += st::mediaCaptionSkip + _caption.countHeight(captionw);
+			if (isBubbleBottom()) {
+				minHeight += st::msgPadding.bottom();
+			}
+		}
+	} else if (isSeparateRoundVideo()) {
+		const auto item = _parent->data();
+		auto via = item->Get<HistoryMessageVia>();
+		auto reply = item->Get<HistoryMessageReply>();
+		auto forwarded = item->Get<HistoryMessageForwarded>();
+		if (forwarded) {
+			forwarded->create(via);
+		}
+		maxWidth += additionalWidth(via, reply, forwarded);
+	}
+	return { maxWidth, minHeight };
+}
+
+QSize HistoryGif::countCurrentSize(int newWidth) {
+	auto availableWidth = newWidth;
+
+	int tw = 0, th = 0;
+	const auto reader = currentReader();
+	if (reader) {
+		tw = ConvertScale(reader->width());
+		th = ConvertScale(reader->height());
+	} else {
+		tw = ConvertScale(_data->dimensions.width()), th = ConvertScale(_data->dimensions.height());
+		if (!tw || !th) {
+			tw = ConvertScale(_data->thumb->width());
+			th = ConvertScale(_data->thumb->height());
+		}
+	}
+	const auto maxSize = _data->isVideoMessage()
+		? st::maxVideoMessageSize
+		: st::maxGifSize;
+	if (tw > maxSize) {
+		th = (maxSize * th) / tw;
+		tw = maxSize;
+	}
+	if (th > maxSize) {
+		tw = (maxSize * tw) / th;
+		th = maxSize;
+	}
+	if (!tw || !th) {
+		tw = th = 1;
+	}
+
+	if (newWidth < tw) {
+		th = qRound((newWidth / float64(tw)) * th);
+		tw = newWidth;
+	}
+	_thumbw = tw;
+	_thumbh = th;
+
+	newWidth = qMax(tw, st::minPhotoSize);
+	auto newHeight = qMax(th, st::minPhotoSize);
+	accumulate_max(newWidth, _parent->infoWidth() + 2 * st::msgDateImgDelta + st::msgDateImgPadding.x());
+	if (reader) {
+		const auto own = (reader->mode() == Media::Clip::Reader::Mode::Gif);
+		if (own && !reader->started()) {
+			auto isRound = _data->isVideoMessage();
+			auto inWebPage = (_parent->media() != this);
+			auto roundRadius = isRound
+				? ImageRoundRadius::Ellipse
+				: inWebPage
+				? ImageRoundRadius::Small
+				: ImageRoundRadius::Large;
+			auto roundCorners = (isRound || inWebPage)
+				? RectPart::AllCorners
+				: ((isBubbleTop()
+					? (RectPart::TopLeft | RectPart::TopRight)
+					: RectPart::None)
+				| ((isBubbleBottom() && _caption.isEmpty())
+					? (RectPart::BottomLeft | RectPart::BottomRight)
+					: RectPart::None));
+			reader->start(
+				_thumbw,
+				_thumbh,
+				newWidth,
+				newHeight,
+				roundRadius,
+				roundCorners);
+		}
+	} else {
+		accumulate_max(newWidth, gifMaxStatusWidth(_data) + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x()));
+	}
+	if (_parent->hasBubble()) {
+		if (!_caption.isEmpty()) {
+			auto captionw = newWidth - st::msgPadding.left() - st::msgPadding.right();
+			newHeight += st::mediaCaptionSkip + _caption.countHeight(captionw);
+			if (isBubbleBottom()) {
+				newHeight += st::msgPadding.bottom();
+			}
+		}
+	} else if (isSeparateRoundVideo()) {
+		const auto item = _parent->data();
+		auto via = item->Get<HistoryMessageVia>();
+		auto reply = item->Get<HistoryMessageReply>();
+		auto forwarded = item->Get<HistoryMessageForwarded>();
+		if (via || reply || forwarded) {
+			auto additional = additionalWidth(via, reply, forwarded);
+			newWidth += additional;
+			accumulate_min(newWidth, availableWidth);
+			auto usew = maxWidth() - additional;
+			auto availw = newWidth - usew - st::msgReplyPadding.left() - st::msgReplyPadding.left() - st::msgReplyPadding.left();
+			if (!forwarded && via) {
+				via->resize(availw);
+			}
+			if (reply) {
+				reply->resize(availw);
+			}
+		}
+	}
+
+	return { newWidth, newHeight };
+}
+
+void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const {
+	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
+
+	const auto item = _parent->data();
+	_data->automaticLoad(_realParent->fullId(), item);
+	auto loaded = _data->loaded();
+	auto displayLoading = (item->id < 0) || _data->displayLoading();
+	auto selected = (selection == FullSelection);
+
+	if (loaded
+		&& cAutoPlayGif()
+		&& !_gif
+		&& !_gif.isBad()
+		&& !activeRoundVideo()) {
+		_parent->delegate()->elementAnimationAutoplayAsync(_parent);
+	}
+
+	auto paintx = 0, painty = 0, paintw = width(), painth = height();
+	bool bubble = _parent->hasBubble();
+	auto outbg = _parent->hasOutLayout();
+	auto inWebPage = (_parent->media() != this);
+
+	auto captionw = paintw - st::msgPadding.left() - st::msgPadding.right();
+
+	auto isRound = _data->isVideoMessage();
+	auto displayMute = false;
+	const auto reader = currentReader();
+	const auto playingVideo = reader
+		? (reader->mode() == Media::Clip::Reader::Mode::Video)
+		: false;
+	const auto animating = reader && reader->started();
+
+	if ((!animating || item->id < 0) && displayLoading) {
+		ensureAnimation();
+		if (!_animation->radial.animating()) {
+			_animation->radial.start(dataProgress());
+		}
+	}
+	updateStatusText();
+	auto radial = isRadialAnimation(ms);
+
+	if (bubble) {
+		if (!_caption.isEmpty()) {
+			painth -= st::mediaCaptionSkip + _caption.countHeight(captionw);
+			if (isBubbleBottom()) {
+				painth -= st::msgPadding.bottom();
+			}
+		}
+	} else if (!isRound) {
+		App::roundShadow(p, 0, 0, paintw, height(), selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners);
+	}
+
+	auto usex = 0, usew = paintw;
+	auto separateRoundVideo = isSeparateRoundVideo();
+	auto via = separateRoundVideo ? item->Get<HistoryMessageVia>() : nullptr;
+	auto reply = separateRoundVideo ? item->Get<HistoryMessageReply>() : nullptr;
+	auto forwarded = separateRoundVideo ? item->Get<HistoryMessageForwarded>() : nullptr;
+	if (via || reply || forwarded) {
+		usew = maxWidth() - additionalWidth(via, reply, forwarded);
+		if (outbg) {
+			usex = width() - usew;
+		}
+	}
+	if (rtl()) usex = width() - usex - usew;
+
+	QRect rthumb(rtlrect(usex + paintx, painty, usew, painth, width()));
+
+	auto roundRadius = isRound ? ImageRoundRadius::Ellipse : inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large;
+	auto roundCorners = (isRound || inWebPage) ? RectPart::AllCorners : ((isBubbleTop() ? (RectPart::TopLeft | RectPart::TopRight) : RectPart::None)
+		| ((isBubbleBottom() && _caption.isEmpty()) ? (RectPart::BottomLeft | RectPart::BottomRight) : RectPart::None));
+	if (animating) {
+		auto paused = App::wnd()->controller()->isGifPausedAtLeastFor(Window::GifPauseReason::Any);
+		if (isRound) {
+			if (playingVideo) {
+				paused = false;
+			} else {
+				displayMute = true;
+			}
+		}
+		p.drawPixmap(rthumb.topLeft(), reader->current(_thumbw, _thumbh, usew, painth, roundRadius, roundCorners, paused ? 0 : ms));
+
+		if (const auto playback = videoPlayback()) {
+			const auto value = playback->value(ms);
+			if (value > 0.) {
+				auto pen = st::historyVideoMessageProgressFg->p;
+				auto was = p.pen();
+				pen.setWidth(st::radialLine);
+				pen.setCapStyle(Qt::RoundCap);
+				p.setPen(pen);
+				p.setOpacity(st::historyVideoMessageProgressOpacity);
+
+				auto from = QuarterArcLength;
+				auto len = -qRound(FullArcLength * value);
+				auto stepInside = st::radialLine / 2;
+				{
+					PainterHighQualityEnabler hq(p);
+					p.drawArc(rthumb.marginsRemoved(QMargins(stepInside, stepInside, stepInside, stepInside)), from, len);
+				}
+
+				p.setPen(was);
+				p.setOpacity(1.);
+			}
+		}
+	} else {
+		const auto good = _data->goodThumbnail();
+		if (good && good->loaded()) {
+			p.drawPixmap(rthumb.topLeft(), good->pixSingle({}, _thumbw, _thumbh, usew, painth, roundRadius, roundCorners));
+		} else {
+			if (good) {
+				good->load({});
+			}
+			p.drawPixmap(rthumb.topLeft(), _data->thumb->pixBlurredSingle(_realParent->fullId(), _thumbw, _thumbh, usew, painth, roundRadius, roundCorners));
+		}
+	}
+
+	if (selected) {
+		App::complexOverlayRect(p, rthumb, roundRadius, roundCorners);
+	}
+
+	if (radial || (!reader && (_gif.isBad() || (!loaded && !_data->loading()) || !cAutoPlayGif()))) {
+		auto radialOpacity = (radial && loaded && item->id > 0) ? _animation->radial.opacity() : 1.;
+		auto inner = QRect(rthumb.x() + (rthumb.width() - st::msgFileSize) / 2, rthumb.y() + (rthumb.height() - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize);
+		p.setPen(Qt::NoPen);
+		if (selected) {
+			p.setBrush(st::msgDateImgBgSelected);
+		} else if (isThumbAnimation(ms)) {
+			auto over = _animation->a_thumbOver.current();
+			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(radialOpacity * p.opacity());
+
+		{
+			PainterHighQualityEnabler hq(p);
+			p.drawEllipse(inner);
+		}
+
+		p.setOpacity(radialOpacity);
+		auto icon = [&]() -> const style::icon * {
+			if (_data->loaded() && !radial) {
+				return &(selected ? st::historyFileThumbPlaySelected : st::historyFileThumbPlay);
+			} else if (radial || _data->loading()) {
+				if (item->id > 0 || _data->uploading()) {
+					return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel);
+				}
+				return nullptr;
+			}
+			return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload);
+		}();
+		if (icon) {
+			icon->paintInCenter(p, inner);
+		}
+		if (radial) {
+			p.setOpacity(1);
+			QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine)));
+			_animation->radial.draw(p, rinner, st::msgFileRadialLine, selected ? st::historyFileThumbRadialFgSelected : st::historyFileThumbRadialFg);
+		}
+
+		if (!isRound && (!animating || item->id < 0)) {
+			auto statusX = paintx + st::msgDateImgDelta + st::msgDateImgPadding.x();
+			auto statusY = painty + st::msgDateImgDelta + st::msgDateImgPadding.y();
+			auto statusW = st::normalFont->width(_statusText) + 2 * st::msgDateImgPadding.x();
+			auto statusH = st::normalFont->height + 2 * st::msgDateImgPadding.y();
+			App::roundRect(p, rtlrect(statusX - st::msgDateImgPadding.x(), statusY - st::msgDateImgPadding.y(), statusW, statusH, width()), selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners);
+			p.setFont(st::normalFont);
+			p.setPen(st::msgDateImgFg);
+			p.drawTextLeft(statusX, statusY, width(), _statusText, statusW - 2 * st::msgDateImgPadding.x());
+		}
+	}
+	if (displayMute) {
+		auto muteRect = rtlrect(rthumb.x() + (rthumb.width() - st::historyVideoMessageMuteSize) / 2, rthumb.y() + st::msgDateImgDelta, st::historyVideoMessageMuteSize, st::historyVideoMessageMuteSize, width());
+		p.setPen(Qt::NoPen);
+		p.setBrush(selected ? st::msgDateImgBgSelected : st::msgDateImgBg);
+		PainterHighQualityEnabler hq(p);
+		p.drawEllipse(muteRect);
+		(selected ? st::historyVideoMessageMuteSelected : st::historyVideoMessageMute).paintInCenter(p, muteRect);
+	}
+
+	if (!inWebPage && isRound) {
+		auto mediaUnread = item->isMediaUnread();
+		auto statusW = st::normalFont->width(_statusText) + 2 * st::msgDateImgPadding.x();
+		auto statusH = st::normalFont->height + 2 * st::msgDateImgPadding.y();
+		auto statusX = usex + paintx + st::msgDateImgDelta + st::msgDateImgPadding.x();
+		auto statusY = painty + painth - st::msgDateImgDelta - statusH + st::msgDateImgPadding.y();
+		if (item->isMediaUnread()) {
+			statusW += st::mediaUnreadSkip + st::mediaUnreadSize;
+		}
+		App::roundRect(p, rtlrect(statusX - st::msgDateImgPadding.x(), statusY - st::msgDateImgPadding.y(), statusW, statusH, width()), selected ? st::msgServiceBgSelected : st::msgServiceBg, selected ? StickerSelectedCorners : StickerCorners);
+		p.setFont(st::normalFont);
+		p.setPen(st::msgServiceFg);
+		p.drawTextLeft(statusX, statusY, width(), _statusText, statusW - 2 * st::msgDateImgPadding.x());
+		if (mediaUnread) {
+			p.setPen(Qt::NoPen);
+			p.setBrush(st::msgServiceFg);
+
+			{
+				PainterHighQualityEnabler hq(p);
+				p.drawEllipse(rtlrect(statusX - st::msgDateImgPadding.x() + statusW - st::msgDateImgPadding.x() - st::mediaUnreadSize, statusY + st::mediaUnreadTop, st::mediaUnreadSize, st::mediaUnreadSize, width()));
+			}
+		}
+		if (via || reply || forwarded) {
+			auto rectw = width() - usew - st::msgReplyPadding.left();
+			auto innerw = rectw - (st::msgReplyPadding.left() + st::msgReplyPadding.right());
+			auto recth = st::msgReplyPadding.top() + st::msgReplyPadding.bottom();
+			auto forwardedHeightReal = forwarded ? forwarded->text.countHeight(innerw) : 0;
+			auto forwardedHeight = qMin(forwardedHeightReal, kMaxGifForwardedBarLines * st::msgServiceNameFont->height);
+			if (forwarded) {
+				recth += forwardedHeight;
+			} else if (via) {
+				recth += st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0);
+			}
+			if (reply) {
+				recth += st::msgReplyBarSize.height();
+			}
+			int rectx = outbg ? 0 : (usew + st::msgReplyPadding.left());
+			int recty = painty;
+			if (rtl()) rectx = width() - rectx - rectw;
+
+			App::roundRect(p, rectx, recty, rectw, recth, selected ? st::msgServiceBgSelected : st::msgServiceBg, selected ? StickerSelectedCorners : StickerCorners);
+			p.setPen(st::msgServiceFg);
+			rectx += st::msgReplyPadding.left();
+			rectw = innerw;
+			if (forwarded) {
+				p.setTextPalette(st::serviceTextPalette);
+				auto breakEverywhere = (forwardedHeightReal > forwardedHeight);
+				forwarded->text.drawElided(p, rectx, recty + st::msgReplyPadding.top(), rectw, kMaxGifForwardedBarLines, style::al_left, 0, -1, 0, breakEverywhere);
+				p.restoreTextPalette();
+			} else if (via) {
+				p.setFont(st::msgDateFont);
+				p.drawTextLeft(rectx, recty + st::msgReplyPadding.top(), 2 * rectx + rectw, via->text);
+				int skip = st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0);
+				recty += skip;
+			}
+			if (reply) {
+				HistoryMessageReply::PaintFlags flags = 0;
+				if (selected) {
+					flags |= HistoryMessageReply::PaintFlag::Selected;
+				}
+				reply->paint(p, _parent, rectx, recty, rectw, flags);
+			}
+		}
+	}
+	if (!isRound && !_caption.isEmpty()) {
+		p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg));
+		_caption.draw(p, st::msgPadding.left(), painty + painth + st::mediaCaptionSkip, captionw, style::al_left, 0, -1, selection);
+	} else if (!inWebPage) {
+		auto fullRight = paintx + usex + usew;
+		auto fullBottom = painty + painth;
+		auto maxRight = _parent->width() - st::msgMargin.left();
+		if (_parent->hasFromPhoto()) {
+			maxRight -= st::msgMargin.right();
+		} else {
+			maxRight -= st::msgMargin.left();
+		}
+		if (isRound && !outbg) {
+			auto infoWidth = _parent->infoWidth();
+
+			// This is just some arbitrary point,
+			// the main idea is to make info left aligned here.
+			fullRight += infoWidth - st::normalFont->height;
+			if (fullRight > maxRight) {
+				fullRight = maxRight;
+			}
+		}
+		if (isRound || needInfoDisplay()) {
+			_parent->drawInfo(p, fullRight, fullBottom, 2 * paintx + paintw, selected, isRound ? InfoDisplayType::Background : InfoDisplayType::Image);
+		}
+		if (!bubble && _parent->displayRightAction()) {
+			auto fastShareLeft = (fullRight + st::historyFastShareLeft);
+			auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize);
+			if (fastShareLeft + st::historyFastShareSize > maxRight) {
+				fastShareLeft = (fullRight - st::historyFastShareSize - st::msgDateImgDelta);
+				fastShareTop -= (st::msgDateImgDelta + st::msgDateImgPadding.y() + st::msgDateFont->height + st::msgDateImgPadding.y());
+			}
+			_parent->drawRightAction(p, fastShareLeft, fastShareTop, 2 * paintx + paintw);
+		}
+	}
+}
+
+TextState HistoryGif::textState(QPoint point, StateRequest request) const {
+	auto result = TextState(_parent);
+
+	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) {
+		return result;
+	}
+	auto paintx = 0, painty = 0, paintw = width(), painth = height();
+	auto bubble = _parent->hasBubble();
+
+	if (bubble && !_caption.isEmpty()) {
+		auto captionw = paintw - st::msgPadding.left() - st::msgPadding.right();
+		painth -= _caption.countHeight(captionw);
+		if (isBubbleBottom()) {
+			painth -= st::msgPadding.bottom();
+		}
+		if (QRect(st::msgPadding.left(), painth, captionw, height() - painth).contains(point)) {
+			result = TextState(_parent, _caption.getState(
+				point - QPoint(st::msgPadding.left(), painth),
+				captionw,
+				request.forText()));
+			return result;
+		}
+		painth -= st::mediaCaptionSkip;
+	}
+	auto outbg = _parent->hasOutLayout();
+	auto inWebPage = (_parent->media() != this);
+	auto isRound = _data->isVideoMessage();
+	auto usew = paintw, usex = 0;
+	auto separateRoundVideo = isSeparateRoundVideo();
+	const auto item = _parent->data();
+	auto via = separateRoundVideo ? item->Get<HistoryMessageVia>() : nullptr;
+	auto reply = separateRoundVideo ? item->Get<HistoryMessageReply>() : nullptr;
+	auto forwarded = separateRoundVideo ? item->Get<HistoryMessageForwarded>() : nullptr;
+	if (via || reply || forwarded) {
+		usew = maxWidth() - additionalWidth(via, reply, forwarded);
+		if (outbg) {
+			usex = width() - usew;
+		}
+	}
+	if (rtl()) usex = width() - usex - usew;
+
+	if (via || reply || forwarded) {
+		auto rectw = paintw - usew - st::msgReplyPadding.left();
+		auto innerw = rectw - (st::msgReplyPadding.left() + st::msgReplyPadding.right());
+		auto recth = st::msgReplyPadding.top() + st::msgReplyPadding.bottom();
+		auto forwardedHeightReal = forwarded ? forwarded->text.countHeight(innerw) : 0;
+		auto forwardedHeight = qMin(forwardedHeightReal, kMaxGifForwardedBarLines * st::msgServiceNameFont->height);
+		if (forwarded) {
+			recth += forwardedHeight;
+		} else if (via) {
+			recth += st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0);
+		}
+		if (reply) {
+			recth += st::msgReplyBarSize.height();
+		}
+		auto rectx = outbg ? 0 : (usew + st::msgReplyPadding.left());
+		auto recty = painty;
+		if (rtl()) rectx = width() - rectx - rectw;
+
+		if (forwarded) {
+			if (QRect(rectx, recty, rectw, st::msgReplyPadding.top() + forwardedHeight).contains(point)) {
+				auto breakEverywhere = (forwardedHeightReal > forwardedHeight);
+				auto textRequest = request.forText();
+				if (breakEverywhere) {
+					textRequest.flags |= Text::StateRequest::Flag::BreakEverywhere;
+				}
+				result = TextState(_parent, forwarded->text.getState(
+					point - QPoint(rectx + st::msgReplyPadding.left(), recty + st::msgReplyPadding.top()),
+					innerw,
+					textRequest));
+				result.symbol = 0;
+				result.afterSymbol = false;
+				if (breakEverywhere) {
+					result.cursor = CursorState::Forwarded;
+				} else {
+					result.cursor = CursorState::None;
+				}
+				return result;
+			}
+			recty += forwardedHeight;
+			recth -= forwardedHeight;
+		} else if (via) {
+			auto viah = st::msgReplyPadding.top() + st::msgServiceNameFont->height + (reply ? 0 : st::msgReplyPadding.bottom());
+			if (QRect(rectx, recty, rectw, viah).contains(point)) {
+				result.link = via->link;
+				return result;
+			}
+			auto skip = st::msgServiceNameFont->height + (reply ? 2 * st::msgReplyPadding.top() : 0);
+			recty += skip;
+			recth -= skip;
+		}
+		if (reply) {
+			if (QRect(rectx, recty, rectw, recth).contains(point)) {
+				result.link = reply->replyToLink();
+				return result;
+			}
+		}
+	}
+	if (QRect(usex + paintx, painty, usew, painth).contains(point)) {
+		if (_data->uploading()) {
+			result.link = _cancell;
+		} else if (!_gif || !cAutoPlayGif() || _data->isVideoMessage()) {
+			result.link = _data->loaded() ? _openl : (_data->loading() ? _cancell : _savel);
+		} else {
+			result.link = _openInMediaviewLink;
+		}
+	}
+	if (isRound || _caption.isEmpty()) {
+		auto fullRight = usex + paintx + usew;
+		auto fullBottom = painty + painth;
+		auto maxRight = _parent->width() - st::msgMargin.left();
+		if (_parent->hasFromPhoto()) {
+			maxRight -= st::msgMargin.right();
+		} else {
+			maxRight -= st::msgMargin.left();
+		}
+		if (isRound && !outbg) {
+			auto infoWidth = _parent->infoWidth();
+
+			// This is just some arbitrary point,
+			// the main idea is to make info left aligned here.
+			fullRight += infoWidth - st::normalFont->height;
+			if (fullRight > maxRight) {
+				fullRight = maxRight;
+			}
+		}
+		if (!inWebPage) {
+			if (_parent->pointInTime(fullRight, fullBottom, point, isRound ? InfoDisplayType::Background : InfoDisplayType::Image)) {
+				result.cursor = CursorState::Date;
+			}
+		}
+		if (!bubble && _parent->displayRightAction()) {
+			auto fastShareLeft = (fullRight + st::historyFastShareLeft);
+			auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize);
+			if (fastShareLeft + st::historyFastShareSize > maxRight) {
+				fastShareLeft = (fullRight - st::historyFastShareSize - st::msgDateImgDelta);
+				fastShareTop -= st::msgDateImgDelta + st::msgDateImgPadding.y() + st::msgDateFont->height + st::msgDateImgPadding.y();
+			}
+			if (QRect(fastShareLeft, fastShareTop, st::historyFastShareSize, st::historyFastShareSize).contains(point)) {
+				result.link = _parent->rightActionLink();
+			}
+		}
+	}
+	return result;
+}
+
+TextWithEntities HistoryGif::selectedText(TextSelection selection) const {
+	return _caption.originalTextWithEntities(selection, ExpandLinksAll);
+}
+
+bool HistoryGif::uploading() const {
+	return _data->uploading();
+}
+
+bool HistoryGif::needsBubble() const {
+	if (_data->isVideoMessage()) {
+		return false;
+	}
+	if (!_caption.isEmpty()) {
+		return true;
+	}
+	const auto item = _parent->data();
+	return item->viaBot()
+		|| item->Has<HistoryMessageReply>()
+		|| _parent->displayForwardedFrom()
+		|| _parent->displayFromName();
+	return false;
+}
+
+int HistoryGif::additionalWidth() const {
+	const auto item = _parent->data();
+	return additionalWidth(
+		item->Get<HistoryMessageVia>(),
+		item->Get<HistoryMessageReply>(),
+		item->Get<HistoryMessageForwarded>());
+}
+
+QString HistoryGif::mediaTypeString() const {
+	return _data->isVideoMessage()
+		? lang(lng_in_dlg_video_message)
+		: qsl("GIF");
+}
+
+bool HistoryGif::isSeparateRoundVideo() const {
+	return _data->isVideoMessage()
+		&& (_parent->media() == this)
+		&& !_parent->hasBubble();
+}
+
+void HistoryGif::setStatusSize(int newSize) const {
+	if (_data->isVideoMessage()) {
+		_statusSize = newSize;
+		if (newSize < 0) {
+			_statusText = formatDurationText(-newSize - 1);
+		} else {
+			_statusText = formatDurationText(_data->duration());
+		}
+	} else {
+		HistoryFileMedia::setStatusSize(newSize, _data->size, -2, 0);
+	}
+}
+
+void HistoryGif::updateStatusText() const {
+	auto showPause = false;
+	auto statusSize = 0;
+	auto realDuration = 0;
+	if (_data->status == FileDownloadFailed || _data->status == FileUploadFailed) {
+		statusSize = FileStatusSizeFailed;
+	} else if (_data->uploading()) {
+		statusSize = _data->uploadingData->offset;
+	} else if (_data->loading()) {
+		statusSize = _data->loadOffset();
+	} else if (_data->loaded()) {
+		statusSize = FileStatusSizeLoaded;
+		if (const auto video = activeRoundPlayer()) {
+			statusSize = -1 - _data->duration();
+
+			const auto state = Media::Player::mixer()->currentState(
+				AudioMsgId::Type::Voice);
+			if (state.id == video->audioMsgId() && state.length) {
+				auto position = int64(0);
+				if (Media::Player::IsStoppedAtEnd(state.state)) {
+					position = state.length;
+				} else if (!Media::Player::IsStoppedOrStopping(state.state)) {
+					position = state.position;
+				}
+				accumulate_max(statusSize, -1 - int((state.length - position) / state.frequency + 1));
+			}
+		}
+	} else {
+		statusSize = FileStatusSizeReady;
+	}
+	if (statusSize != _statusSize) {
+		setStatusSize(statusSize);
+	}
+}
+
+void HistoryGif::refreshParentId(not_null<HistoryItem*> realParent) {
+	HistoryFileMedia::refreshParentId(realParent);
+
+	const auto fullId = realParent->fullId();
+	if (_openInMediaviewLink) {
+		_openInMediaviewLink->setMessageId(fullId);
+	}
+}
+
+QString HistoryGif::additionalInfoString() const {
+	if (_data->isVideoMessage()) {
+		updateStatusText();
+		return _statusText;
+	}
+	return QString();
+}
+
+bool HistoryGif::isReadyForOpen() const {
+	return _data->loaded();
+}
+
+void HistoryGif::parentTextUpdated() {
+	_caption = (_parent->media() == this)
+		? createCaption(_parent->data())
+		: Text();
+	Auth().data().requestViewResize(_parent);
+}
+
+int HistoryGif::additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply *reply, const HistoryMessageForwarded *forwarded) const {
+	int result = 0;
+	if (forwarded) {
+		accumulate_max(result, st::msgReplyPadding.left() + st::msgReplyPadding.left() + forwarded->text.maxWidth() + st::msgReplyPadding.right());
+	} else if (via) {
+		accumulate_max(result, st::msgReplyPadding.left() + st::msgReplyPadding.left() + via->maxWidth + st::msgReplyPadding.left());
+	}
+	if (reply) {
+		accumulate_max(result, st::msgReplyPadding.left() + reply->replyToWidth());
+	}
+	return result;
+}
+
+Media::Player::RoundController *HistoryGif::activeRoundVideo() const {
+	return App::wnd()->controller()->roundVideo(_parent->data());
+}
+
+Media::Clip::Reader *HistoryGif::activeRoundPlayer() const {
+	if (const auto video = activeRoundVideo()) {
+		if (const auto result = video->reader()) {
+			if (result->ready()) {
+				return result;
+			}
+		}
+	}
+	return nullptr;
+}
+
+Media::Clip::Reader *HistoryGif::currentReader() const {
+	if (const auto result = activeRoundPlayer()) {
+		return result;
+	}
+	return (_gif && _gif->ready()) ? _gif.get() : nullptr;
+}
+
+Media::Clip::Playback *HistoryGif::videoPlayback() const {
+	if (const auto video = activeRoundVideo()) {
+		return video->playback();
+	}
+	return nullptr;
+}
+
+void HistoryGif::clipCallback(Media::Clip::Notification notification) {
+	using namespace Media::Clip;
+
+	const auto reader = _gif.get();
+	if (!reader) {
+		return;
+	}
+	switch (notification) {
+	case NotificationReinit: {
+		auto stopped = false;
+		if (reader->autoPausedGif()) {
+			auto amVisible = false;
+			Auth().data().queryItemVisibility().notify(
+				{ _parent->data(), &amVisible },
+				true);
+			if (!amVisible) { // Stop animation if it is not visible.
+				stopAnimation();
+				stopped = true;
+			}
+		}
+		if (!stopped) {
+			Auth().data().requestViewResize(_parent);
+		}
+	} break;
+
+	case NotificationRepaint: {
+		if (!reader->currentDisplayed()) {
+			Auth().data().requestViewRepaint(_parent);
+		}
+	} break;
+	}
+}
+
+void HistoryGif::playAnimation(bool autoplay) {
+	if (_data->isVideoMessage() && !autoplay) {
+		return;
+	} else if (_gif && autoplay) {
+		return;
+	}
+	using Mode = Media::Clip::Reader::Mode;
+	if (_gif) {
+		stopAnimation();
+	} else if (_data->loaded(DocumentData::FilePathResolveChecked)) {
+		if (!cAutoPlayGif()) {
+			Auth().data().stopAutoplayAnimations();
+		}
+		setClipReader(Media::Clip::MakeReader(
+			_data,
+			_parent->data()->fullId(),
+			[=](auto notification) { clipCallback(notification); },
+			Mode::Gif));
+		if (_gif && autoplay) {
+			_gif->setAutoplay();
+		}
+	}
+}
+
+void HistoryGif::stopAnimation() {
+	if (_gif) {
+		clearClipReader();
+		Auth().data().requestViewResize(_parent);
+		_data->unload();
+	}
+}
+
+void HistoryGif::setClipReader(Media::Clip::ReaderPointer gif) {
+	if (_gif) {
+		Auth().data().unregisterAutoplayAnimation(_gif.get());
+	}
+	_gif = std::move(gif);
+	if (_gif) {
+		Auth().data().registerAutoplayAnimation(_gif.get(), _parent);
+	}
+}
+
+HistoryGif::~HistoryGif() {
+	clearClipReader();
+}
+
+float64 HistoryGif::dataProgress() const {
+	return (_data->uploading() || _parent->data()->id > 0)
+		? _data->progress()
+		: 0;
+}
+
+bool HistoryGif::dataFinished() const {
+	return (_parent->data()->id > 0)
+		? (!_data->loading() && !_data->uploading())
+		: false;
+}
+
+bool HistoryGif::dataLoaded() const {
+	return (_parent->data()->id > 0) ? _data->loaded() : false;
+}
+
+bool HistoryGif::needInfoDisplay() const {
+	return (_parent->data()->id < 0 || _parent->isUnderCursor());
+}
diff --git a/Telegram/SourceFiles/history/media/history_media_gif.h b/Telegram/SourceFiles/history/media/history_media_gif.h
new file mode 100644
index 000000000..ad0f78f93
--- /dev/null
+++ b/Telegram/SourceFiles/history/media/history_media_gif.h
@@ -0,0 +1,120 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+#include "history/media/history_media_file.h"
+
+struct HistoryMessageVia;
+struct HistoryMessageReply;
+struct HistoryMessageForwarded;
+
+namespace Media {
+namespace Clip {
+class Playback;
+} // namespace Clip
+
+namespace Player {
+class RoundController;
+} // namespace Player
+} // namespace Media
+
+class HistoryGif : public HistoryFileMedia {
+public:
+	HistoryGif(
+		not_null<Element*> parent,
+		not_null<DocumentData*> document);
+
+	HistoryMediaType type() const override {
+		return MediaTypeGif;
+	}
+
+	void refreshParentId(not_null<HistoryItem*> realParent) override;
+
+	void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override;
+	TextState textState(QPoint point, StateRequest request) const override;
+
+	[[nodiscard]] TextSelection adjustSelection(
+			TextSelection selection,
+			TextSelectType type) const override {
+		return _caption.adjustSelection(selection, type);
+	}
+	uint16 fullSelectionLength() const override {
+		return _caption.length();
+	}
+	bool hasTextForCopy() const override {
+		return !_caption.isEmpty();
+	}
+
+	TextWithEntities selectedText(TextSelection selection) const override;
+
+	bool uploading() const override;
+
+	DocumentData *getDocument() const override {
+		return _data;
+	}
+
+	void stopAnimation() override;
+
+	TextWithEntities getCaption() const override {
+		return _caption.originalTextWithEntities();
+	}
+	bool needsBubble() const override;
+	bool customInfoLayout() const override {
+		return _caption.isEmpty();
+	}
+	QString additionalInfoString() const override;
+
+	bool skipBubbleTail() const override {
+		return isBubbleBottom() && _caption.isEmpty();
+	}
+	bool isReadyForOpen() const override;
+
+	void parentTextUpdated() override;
+
+	~HistoryGif();
+
+protected:
+	float64 dataProgress() const override;
+	bool dataFinished() const override;
+	bool dataLoaded() const override;
+
+	void setClipReader(Media::Clip::ReaderPointer gif);
+	void clearClipReader() {
+		setClipReader(Media::Clip::ReaderPointer());
+	}
+
+private:
+	void playAnimation(bool autoplay) override;
+	QSize countOptimalSize() override;
+	QSize countCurrentSize(int newWidth) override;
+	Media::Player::RoundController *activeRoundVideo() const;
+	Media::Clip::Reader *activeRoundPlayer() const;
+	Media::Clip::Reader *currentReader() const;
+	Media::Clip::Playback *videoPlayback() const;
+	void clipCallback(Media::Clip::Notification notification);
+
+	bool needInfoDisplay() const;
+	int additionalWidth(
+		const HistoryMessageVia *via,
+		const HistoryMessageReply *reply,
+		const HistoryMessageForwarded *forwarded) const;
+	int additionalWidth() const;
+	QString mediaTypeString() const;
+	bool isSeparateRoundVideo() const;
+
+	not_null<DocumentData*> _data;
+	FileClickHandlerPtr _openInMediaviewLink;
+	int _thumbw = 1;
+	int _thumbh = 1;
+	Text _caption;
+	Media::Clip::ReaderPointer _gif;
+
+	void setStatusSize(int newSize) const;
+	void updateStatusText() const;
+
+};
diff --git a/Telegram/SourceFiles/history/history_media_grouped.cpp b/Telegram/SourceFiles/history/media/history_media_grouped.cpp
similarity index 99%
rename from Telegram/SourceFiles/history/history_media_grouped.cpp
rename to Telegram/SourceFiles/history/media/history_media_grouped.cpp
index 0bdc58281..7104b32e0 100644
--- a/Telegram/SourceFiles/history/history_media_grouped.cpp
+++ b/Telegram/SourceFiles/history/media/history_media_grouped.cpp
@@ -5,10 +5,9 @@ the official desktop application for the Telegram messaging service.
 For license and copyright information please follow this link:
 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
-#include "history/history_media_grouped.h"
+#include "history/media/history_media_grouped.h"
 
 #include "history/history_item_components.h"
-#include "history/history_media_types.h"
 #include "history/history_message.h"
 #include "history/view/history_view_element.h"
 #include "history/view/history_view_cursor_state.h"
diff --git a/Telegram/SourceFiles/history/history_media_grouped.h b/Telegram/SourceFiles/history/media/history_media_grouped.h
similarity index 98%
rename from Telegram/SourceFiles/history/history_media_grouped.h
rename to Telegram/SourceFiles/history/media/history_media_grouped.h
index 8320312f0..211c81507 100644
--- a/Telegram/SourceFiles/history/history_media_grouped.h
+++ b/Telegram/SourceFiles/history/media/history_media_grouped.h
@@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #pragma once
 
-#include "history/history_media.h"
+#include "history/media/history_media.h"
 #include "data/data_document.h"
 #include "data/data_photo.h"
 
diff --git a/Telegram/SourceFiles/history/media/history_media_invoice.cpp b/Telegram/SourceFiles/history/media/history_media_invoice.cpp
new file mode 100644
index 000000000..501c5dfa1
--- /dev/null
+++ b/Telegram/SourceFiles/history/media/history_media_invoice.cpp
@@ -0,0 +1,447 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "history/media/history_media_invoice.h"
+
+#include "lang/lang_keys.h"
+#include "layout.h"
+#include "history/view/history_view_element.h"
+#include "history/view/history_view_cursor_state.h"
+#include "history/media/history_media_photo.h"
+#include "history/media/history_media_common.h"
+#include "ui/text_options.h"
+#include "data/data_media_types.h"
+#include "styles/style_history.h"
+
+namespace {
+
+using TextState = HistoryView::TextState;
+
+} // namespace
+
+HistoryInvoice::HistoryInvoice(
+	not_null<Element*> parent,
+	not_null<Data::Invoice*> invoice)
+: HistoryMedia(parent)
+, _title(st::msgMinWidth)
+, _description(st::msgMinWidth)
+, _status(st::msgMinWidth) {
+	fillFromData(invoice);
+}
+
+void HistoryInvoice::fillFromData(not_null<Data::Invoice*> invoice) {
+	if (invoice->photo) {
+		_attach = std::make_unique<HistoryPhoto>(
+			_parent,
+			_parent->data(),
+			invoice->photo);
+	} else {
+		_attach = nullptr;
+	}
+	auto labelText = [&] {
+		if (invoice->receiptMsgId) {
+			if (invoice->isTest) {
+				return lang(lng_payments_receipt_label_test);
+			}
+			return lang(lng_payments_receipt_label);
+		} else if (invoice->isTest) {
+			return lang(lng_payments_invoice_label_test);
+		}
+		return lang(lng_payments_invoice_label);
+	};
+	auto statusText = TextWithEntities {
+		FillAmountAndCurrency(invoice->amount, invoice->currency),
+		EntitiesInText()
+	};
+	statusText.entities.push_back(EntityInText(EntityInTextBold, 0, statusText.text.size()));
+	statusText.text += ' ' + labelText().toUpper();
+	_status.setMarkedText(
+		st::defaultTextStyle,
+		statusText,
+		Ui::ItemTextOptions(_parent->data()));
+
+	_receiptMsgId = invoice->receiptMsgId;
+
+	// init strings
+	if (!invoice->description.isEmpty()) {
+		auto marked = TextWithEntities { invoice->description };
+		auto parseFlags = TextParseLinks | TextParseMultiline | TextParseRichText;
+		TextUtilities::ParseEntities(marked, parseFlags);
+		_description.setMarkedText(
+			st::webPageDescriptionStyle,
+			marked,
+			Ui::WebpageTextDescriptionOptions());
+	}
+	if (!invoice->title.isEmpty()) {
+		_title.setText(
+			st::webPageTitleStyle,
+			invoice->title,
+			Ui::WebpageTextTitleOptions());
+	}
+}
+
+QSize HistoryInvoice::countOptimalSize() {
+	auto lineHeight = unitedLineHeight();
+
+	if (_attach) {
+		if (_status.hasSkipBlock()) {
+			_status.removeSkipBlock();
+		}
+	} else {
+		_status.updateSkipBlock(
+			_parent->skipBlockWidth(),
+			_parent->skipBlockHeight());
+	}
+
+	// init dimensions
+	auto l = st::msgPadding.left(), r = st::msgPadding.right();
+	auto skipBlockWidth = _parent->skipBlockWidth();
+	auto maxWidth = skipBlockWidth;
+	auto minHeight = 0;
+
+	auto titleMinHeight = _title.isEmpty() ? 0 : lineHeight;
+	// enable any count of lines in game description / message
+	auto descMaxLines = 4096;
+	auto descriptionMinHeight = _description.isEmpty() ? 0 : qMin(_description.minHeight(), descMaxLines * lineHeight);
+
+	if (!_title.isEmpty()) {
+		accumulate_max(maxWidth, _title.maxWidth());
+		minHeight += titleMinHeight;
+	}
+	if (!_description.isEmpty()) {
+		accumulate_max(maxWidth, _description.maxWidth());
+		minHeight += descriptionMinHeight;
+	}
+	if (_attach) {
+		auto attachAtTop = _title.isEmpty() && _description.isEmpty();
+		if (!attachAtTop) minHeight += st::mediaInBubbleSkip;
+
+		_attach->initDimensions();
+		auto bubble = _attach->bubbleMargins();
+		auto maxMediaWidth = _attach->maxWidth() - bubble.left() - bubble.right();
+		if (isBubbleBottom() && _attach->customInfoLayout()) {
+			maxMediaWidth += skipBlockWidth;
+		}
+		accumulate_max(maxWidth, maxMediaWidth);
+		minHeight += _attach->minHeight() - bubble.top() - bubble.bottom();
+	} else {
+		accumulate_max(maxWidth, _status.maxWidth());
+		minHeight += st::mediaInBubbleSkip + _status.minHeight();
+	}
+	auto padding = inBubblePadding();
+	maxWidth += padding.left() + padding.right();
+	minHeight += padding.top() + padding.bottom();
+	return { maxWidth, minHeight };
+}
+
+QSize HistoryInvoice::countCurrentSize(int newWidth) {
+	accumulate_min(newWidth, maxWidth());
+	auto innerWidth = newWidth - st::msgPadding.left() - st::msgPadding.right();
+
+	auto lineHeight = unitedLineHeight();
+
+	auto newHeight = 0;
+	if (_title.isEmpty()) {
+		_titleHeight = 0;
+	} else {
+		if (_title.countHeight(innerWidth) < 2 * st::webPageTitleFont->height) {
+			_titleHeight = lineHeight;
+		} else {
+			_titleHeight = 2 * lineHeight;
+		}
+		newHeight += _titleHeight;
+	}
+
+	if (_description.isEmpty()) {
+		_descriptionHeight = 0;
+	} else {
+		_descriptionHeight = _description.countHeight(innerWidth);
+		newHeight += _descriptionHeight;
+	}
+
+	if (_attach) {
+		auto attachAtTop = !_title.isEmpty() && _description.isEmpty();
+		if (!attachAtTop) newHeight += st::mediaInBubbleSkip;
+
+		QMargins bubble(_attach->bubbleMargins());
+
+		_attach->resizeGetHeight(innerWidth + bubble.left() + bubble.right());
+		newHeight += _attach->height() - bubble.top() - bubble.bottom();
+		if (isBubbleBottom() && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > innerWidth + bubble.left() + bubble.right()) {
+			newHeight += bottomInfoPadding();
+		}
+	} else {
+		newHeight += st::mediaInBubbleSkip + _status.countHeight(innerWidth);
+	}
+	auto padding = inBubblePadding();
+	newHeight += padding.top() + padding.bottom();
+
+	return { newWidth, newHeight };
+}
+
+TextSelection HistoryInvoice::toDescriptionSelection(
+		TextSelection selection) const {
+	return HistoryView::UnshiftItemSelection(selection, _title);
+}
+
+TextSelection HistoryInvoice::fromDescriptionSelection(
+		TextSelection selection) const {
+	return HistoryView::ShiftItemSelection(selection, _title);
+}
+
+void HistoryInvoice::refreshParentId(not_null<HistoryItem*> realParent) {
+	if (_attach) {
+		_attach->refreshParentId(realParent);
+	}
+}
+
+void HistoryInvoice::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const {
+	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
+	auto paintw = width(), painth = height();
+
+	auto outbg = _parent->hasOutLayout();
+	bool selected = (selection == FullSelection);
+
+	auto &barfg = selected ? (outbg ? st::msgOutReplyBarSelColor : st::msgInReplyBarSelColor) : (outbg ? st::msgOutReplyBarColor : st::msgInReplyBarColor);
+	auto &semibold = selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg);
+	auto &regular = selected ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) : (outbg ? st::msgOutDateFg : st::msgInDateFg);
+
+	QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins());
+	auto padding = inBubblePadding();
+	auto tshift = padding.top();
+	auto bshift = padding.bottom();
+	paintw -= padding.left() + padding.right();
+	if (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > paintw + bubble.left() + bubble.right()) {
+		bshift += bottomInfoPadding();
+	}
+
+	auto lineHeight = unitedLineHeight();
+	if (_titleHeight) {
+		p.setPen(semibold);
+		p.setTextPalette(selected ? (outbg ? st::outTextPaletteSelected : st::inTextPaletteSelected) : (outbg ? st::outSemiboldPalette : st::inSemiboldPalette));
+
+		auto endskip = 0;
+		if (_title.hasSkipBlock()) {
+			endskip = _parent->skipBlockWidth();
+		}
+		_title.drawLeftElided(p, padding.left(), tshift, paintw, width(), _titleHeight / lineHeight, style::al_left, 0, -1, endskip, false, selection);
+		tshift += _titleHeight;
+
+		p.setTextPalette(selected ? (outbg ? st::outTextPaletteSelected : st::inTextPaletteSelected) : (outbg ? st::outTextPalette : st::inTextPalette));
+	}
+	if (_descriptionHeight) {
+		p.setPen(outbg ? st::webPageDescriptionOutFg : st::webPageDescriptionInFg);
+		_description.drawLeft(p, padding.left(), tshift, paintw, width(), style::al_left, 0, -1, toDescriptionSelection(selection));
+		tshift += _descriptionHeight;
+	}
+	if (_attach) {
+		auto attachAtTop = !_titleHeight && !_descriptionHeight;
+		if (!attachAtTop) tshift += st::mediaInBubbleSkip;
+
+		auto attachLeft = padding.left() - bubble.left();
+		auto attachTop = tshift - bubble.top();
+		if (rtl()) attachLeft = width() - attachLeft - _attach->width();
+
+		auto attachSelection = selected ? FullSelection : TextSelection { 0, 0 };
+
+		p.translate(attachLeft, attachTop);
+		_attach->draw(p, r.translated(-attachLeft, -attachTop), attachSelection, ms);
+		auto pixwidth = _attach->width();
+		auto pixheight = _attach->height();
+
+		auto available = _status.maxWidth();
+		auto statusW = available + 2 * st::msgDateImgPadding.x();
+		auto statusH = st::msgDateFont->height + 2 * st::msgDateImgPadding.y();
+		auto statusX = st::msgDateImgDelta;
+		auto statusY = st::msgDateImgDelta;
+
+		App::roundRect(p, rtlrect(statusX, statusY, statusW, statusH, pixwidth), selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners);
+
+		p.setFont(st::msgDateFont);
+		p.setPen(st::msgDateImgFg);
+		_status.drawLeftElided(p, statusX + st::msgDateImgPadding.x(), statusY + st::msgDateImgPadding.y(), available, pixwidth);
+
+		p.translate(-attachLeft, -attachTop);
+	} else {
+		p.setPen(outbg ? st::webPageDescriptionOutFg : st::webPageDescriptionInFg);
+		_status.drawLeft(p, padding.left(), tshift + st::mediaInBubbleSkip, paintw, width());
+	}
+}
+
+TextState HistoryInvoice::textState(QPoint point, StateRequest request) const {
+	auto result = TextState(_parent);
+
+	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) {
+		return result;
+	}
+	auto paintw = width(), painth = height();
+
+	QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins());
+	auto padding = inBubblePadding();
+	auto tshift = padding.top();
+	auto bshift = padding.bottom();
+	if (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > paintw + bubble.left() + bubble.right()) {
+		bshift += bottomInfoPadding();
+	}
+	paintw -= padding.left() + padding.right();
+
+	auto lineHeight = unitedLineHeight();
+	auto symbolAdd = 0;
+	if (_titleHeight) {
+		if (point.y() >= tshift && point.y() < tshift + _titleHeight) {
+			Text::StateRequestElided titleRequest = request.forText();
+			titleRequest.lines = _titleHeight / lineHeight;
+			result = TextState(_parent, _title.getStateElidedLeft(
+				point - QPoint(padding.left(), tshift),
+				paintw,
+				width(),
+				titleRequest));
+		} else if (point.y() >= tshift + _titleHeight) {
+			symbolAdd += _title.length();
+		}
+		tshift += _titleHeight;
+	}
+	if (_descriptionHeight) {
+		if (point.y() >= tshift && point.y() < tshift + _descriptionHeight) {
+			result = TextState(_parent, _description.getStateLeft(
+				point - QPoint(padding.left(), tshift),
+				paintw,
+				width(),
+				request.forText()));
+		} else if (point.y() >= tshift + _descriptionHeight) {
+			symbolAdd += _description.length();
+		}
+		tshift += _descriptionHeight;
+	}
+	if (_attach) {
+		auto attachAtTop = !_titleHeight && !_descriptionHeight;
+		if (!attachAtTop) tshift += st::mediaInBubbleSkip;
+
+		auto attachLeft = padding.left() - bubble.left();
+		auto attachTop = tshift - bubble.top();
+		if (rtl()) attachLeft = width() - attachLeft - _attach->width();
+
+		if (QRect(attachLeft, tshift, _attach->width(), height() - tshift - bshift).contains(point)) {
+			result = _attach->textState(point - QPoint(attachLeft, attachTop), request);
+		}
+	}
+
+	result.symbol += symbolAdd;
+	return result;
+}
+
+TextSelection HistoryInvoice::adjustSelection(TextSelection selection, TextSelectType type) const {
+	if (!_descriptionHeight || selection.to <= _title.length()) {
+		return _title.adjustSelection(selection, type);
+	}
+	auto descriptionSelection = _description.adjustSelection(toDescriptionSelection(selection), type);
+	if (selection.from >= _title.length()) {
+		return fromDescriptionSelection(descriptionSelection);
+	}
+	auto titleSelection = _title.adjustSelection(selection, type);
+	return { titleSelection.from, fromDescriptionSelection(descriptionSelection).to };
+}
+
+void HistoryInvoice::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
+	if (_attach) {
+		_attach->clickHandlerActiveChanged(p, active);
+	}
+}
+
+void HistoryInvoice::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) {
+	if (_attach) {
+		_attach->clickHandlerPressedChanged(p, pressed);
+	}
+}
+
+TextWithEntities HistoryInvoice::selectedText(TextSelection selection) const {
+	auto titleResult = _title.originalTextWithEntities(
+		selection,
+		ExpandLinksAll);
+	auto descriptionResult = _description.originalTextWithEntities(
+		toDescriptionSelection(selection),
+		ExpandLinksAll);
+	if (titleResult.text.isEmpty()) {
+		return descriptionResult;
+	} else if (descriptionResult.text.isEmpty()) {
+		return titleResult;
+	}
+
+	titleResult.text += '\n';
+	TextUtilities::Append(titleResult, std::move(descriptionResult));
+	return titleResult;
+}
+
+QMargins HistoryInvoice::inBubblePadding() const {
+	auto lshift = st::msgPadding.left();
+	auto rshift = st::msgPadding.right();
+	auto bshift = isBubbleBottom() ? st::msgPadding.top() : st::mediaInBubbleSkip;
+	auto tshift = isBubbleTop() ? st::msgPadding.bottom() : st::mediaInBubbleSkip;
+	return QMargins(lshift, tshift, rshift, bshift);
+}
+
+int HistoryInvoice::bottomInfoPadding() const {
+	if (!isBubbleBottom()) return 0;
+
+	auto result = st::msgDateFont->height;
+	return result;
+}
+
+QString FillAmountAndCurrency(uint64 amount, const QString &currency) {
+	static const auto ShortCurrencyNames = QMap<QString, QString> {
+		{ qsl("USD"), QString::fromUtf8("\x24") },
+		{ qsl("GBP"), QString::fromUtf8("\xC2\xA3") },
+		{ qsl("EUR"), QString::fromUtf8("\xE2\x82\xAC") },
+		{ qsl("JPY"), QString::fromUtf8("\xC2\xA5") },
+	};
+	static const auto Denominators = QMap<QString, int> {
+		{ qsl("CLF"), 10000 },
+		{ qsl("BHD"), 1000 },
+		{ qsl("IQD"), 1000 },
+		{ qsl("JOD"), 1000 },
+		{ qsl("KWD"), 1000 },
+		{ qsl("LYD"), 1000 },
+		{ qsl("OMR"), 1000 },
+		{ qsl("TND"), 1000 },
+		{ qsl("BIF"), 1 },
+		{ qsl("BYR"), 1 },
+		{ qsl("CLP"), 1 },
+		{ qsl("CVE"), 1 },
+		{ qsl("DJF"), 1 },
+		{ qsl("GNF"), 1 },
+		{ qsl("ISK"), 1 },
+		{ qsl("JPY"), 1 },
+		{ qsl("KMF"), 1 },
+		{ qsl("KRW"), 1 },
+		{ qsl("MGA"), 1 },
+		{ qsl("PYG"), 1 },
+		{ qsl("RWF"), 1 },
+		{ qsl("UGX"), 1 },
+		{ qsl("UYI"), 1 },
+		{ qsl("VND"), 1 },
+		{ qsl("VUV"), 1 },
+		{ qsl("XAF"), 1 },
+		{ qsl("XOF"), 1 },
+		{ qsl("XPF"), 1 },
+		{ qsl("MRO"), 10 },
+	};
+	const auto currencyText = ShortCurrencyNames.value(currency, currency);
+	const auto denominator = Denominators.value(currency, 100);
+	const auto currencyValue = amount / float64(denominator);
+	const auto digits = [&] {
+		auto result = 0;
+		for (auto test = 1; test < denominator; test *= 10) {
+			++result;
+		}
+		return result;
+	}();
+	return QLocale::system().toCurrencyString(currencyValue, currencyText);
+	//auto amountBucks = amount / 100;
+	//auto amountCents = amount % 100;
+	//auto amountText = qsl("%1,%2").arg(amountBucks).arg(amountCents, 2, 10, QChar('0'));
+	//return currencyText + amountText;
+}
diff --git a/Telegram/SourceFiles/history/media/history_media_invoice.h b/Telegram/SourceFiles/history/media/history_media_invoice.h
new file mode 100644
index 000000000..36c9d73f7
--- /dev/null
+++ b/Telegram/SourceFiles/history/media/history_media_invoice.h
@@ -0,0 +1,98 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+#include "history/media/history_media.h"
+
+namespace Data {
+struct Invoice;
+} // namespace Data
+
+class HistoryInvoice : public HistoryMedia {
+public:
+	HistoryInvoice(
+		not_null<Element*> parent,
+		not_null<Data::Invoice*> invoice);
+
+	HistoryMediaType type() const override {
+		return MediaTypeInvoice;
+	}
+
+	void refreshParentId(not_null<HistoryItem*> realParent) override;
+
+	MsgId getReceiptMsgId() const {
+		return _receiptMsgId;
+	}
+	QString getTitle() const {
+		return _title.originalText();
+	}
+
+	bool hideMessageText() const override {
+		return false;
+	}
+
+	void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override;
+	TextState textState(QPoint point, StateRequest request) const override;
+
+	[[nodiscard]] TextSelection adjustSelection(
+		TextSelection selection,
+		TextSelectType type) const override;
+	uint16 fullSelectionLength() const override {
+		return _title.length() + _description.length();
+	}
+	bool hasTextForCopy() const override {
+		return false; // we do not add _title and _description in FullSelection text copy.
+	}
+
+	bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override {
+		return _attach && _attach->toggleSelectionByHandlerClick(p);
+	}
+	bool dragItemByHandler(const ClickHandlerPtr &p) const override {
+		return _attach && _attach->dragItemByHandler(p);
+	}
+
+	TextWithEntities selectedText(TextSelection selection) const override;
+
+	void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
+	void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
+
+	bool needsBubble() const override {
+		return true;
+	}
+	bool customInfoLayout() const override {
+		return false;
+	}
+
+	HistoryMedia *attach() const {
+		return _attach.get();
+	}
+
+private:
+	QSize countOptimalSize() override;
+	QSize countCurrentSize(int newWidth) override;
+
+	void fillFromData(not_null<Data::Invoice*> invoice);
+
+	TextSelection toDescriptionSelection(TextSelection selection) const;
+	TextSelection fromDescriptionSelection(TextSelection selection) const;
+	QMargins inBubblePadding() const;
+	int bottomInfoPadding() const;
+
+	std::unique_ptr<HistoryMedia> _attach;
+
+	int _titleHeight = 0;
+	int _descriptionHeight = 0;
+	Text _title;
+	Text _description;
+	Text _status;
+
+	MsgId _receiptMsgId = 0;
+
+};
+
+QString FillAmountAndCurrency(uint64 amount, const QString &currency);
diff --git a/Telegram/SourceFiles/history/media/history_media_location.cpp b/Telegram/SourceFiles/history/media/history_media_location.cpp
new file mode 100644
index 000000000..2055310d1
--- /dev/null
+++ b/Telegram/SourceFiles/history/media/history_media_location.cpp
@@ -0,0 +1,311 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "history/media/history_media_location.h"
+
+#include "layout.h"
+#include "history/history_item_components.h"
+#include "history/history_item.h"
+#include "history/history_location_manager.h"
+#include "history/view/history_view_element.h"
+#include "history/view/history_view_cursor_state.h"
+#include "ui/image/image.h"
+#include "ui/text_options.h"
+#include "styles/style_history.h"
+
+namespace {
+
+using TextState = HistoryView::TextState;
+
+} // namespace
+
+HistoryLocation::HistoryLocation(
+	not_null<Element*> parent,
+	not_null<LocationData*> location,
+	const QString &title,
+	const QString &description)
+: HistoryMedia(parent)
+, _data(location)
+, _title(st::msgMinWidth)
+, _description(st::msgMinWidth)
+, _link(std::make_shared<LocationClickHandler>(_data->coords)) {
+	if (!title.isEmpty()) {
+		_title.setText(
+			st::webPageTitleStyle,
+			TextUtilities::Clean(title),
+			Ui::WebpageTextTitleOptions());
+	}
+	if (!description.isEmpty()) {
+		_description.setMarkedText(
+			st::webPageDescriptionStyle,
+			TextUtilities::ParseEntities(
+				TextUtilities::Clean(description),
+				TextParseLinks | TextParseMultiline | TextParseRichText),
+			Ui::WebpageTextDescriptionOptions());
+	}
+}
+
+QSize HistoryLocation::countOptimalSize() {
+	auto tw = fullWidth();
+	auto th = fullHeight();
+	if (tw > st::maxMediaSize) {
+		th = (st::maxMediaSize * th) / tw;
+		tw = st::maxMediaSize;
+	}
+	auto minWidth = qMax(st::minPhotoSize, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x()));
+	auto maxWidth = qMax(tw, minWidth);
+	auto minHeight = qMax(th, st::minPhotoSize);
+
+	if (_parent->hasBubble()) {
+		if (!_title.isEmpty()) {
+			minHeight += qMin(_title.countHeight(maxWidth - st::msgPadding.left() - st::msgPadding.right()), 2 * st::webPageTitleFont->height);
+		}
+		if (!_description.isEmpty()) {
+			minHeight += qMin(_description.countHeight(maxWidth - st::msgPadding.left() - st::msgPadding.right()), 3 * st::webPageDescriptionFont->height);
+		}
+		if (!_title.isEmpty() || !_description.isEmpty()) {
+			minHeight += st::mediaInBubbleSkip;
+			if (isBubbleTop()) {
+				minHeight += st::msgPadding.top();
+			}
+		}
+	}
+	return { maxWidth, minHeight };
+}
+
+QSize HistoryLocation::countCurrentSize(int newWidth) {
+	accumulate_min(newWidth, maxWidth());
+
+	auto tw = fullWidth();
+	auto th = fullHeight();
+	if (tw > st::maxMediaSize) {
+		th = (st::maxMediaSize * th) / tw;
+		tw = st::maxMediaSize;
+	}
+	auto newHeight = th;
+	if (tw > newWidth) {
+		newHeight = (newWidth * newHeight / tw);
+	} else {
+		newWidth = tw;
+	}
+	auto minWidth = qMax(st::minPhotoSize, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x()));
+	accumulate_max(newWidth, minWidth);
+	accumulate_max(newHeight, st::minPhotoSize);
+	if (_parent->hasBubble()) {
+		if (!_title.isEmpty()) {
+			newHeight += qMin(_title.countHeight(newWidth - st::msgPadding.left() - st::msgPadding.right()), st::webPageTitleFont->height * 2);
+		}
+		if (!_description.isEmpty()) {
+			newHeight += qMin(_description.countHeight(newWidth - st::msgPadding.left() - st::msgPadding.right()), st::webPageDescriptionFont->height * 3);
+		}
+		if (!_title.isEmpty() || !_description.isEmpty()) {
+			newHeight += st::mediaInBubbleSkip;
+			if (isBubbleTop()) {
+				newHeight += st::msgPadding.top();
+			}
+		}
+	}
+	return { newWidth, newHeight };
+}
+
+TextSelection HistoryLocation::toDescriptionSelection(
+		TextSelection selection) const {
+	return HistoryView::UnshiftItemSelection(selection, _title);
+}
+
+TextSelection HistoryLocation::fromDescriptionSelection(
+		TextSelection selection) const {
+	return HistoryView::ShiftItemSelection(selection, _title);
+}
+
+void HistoryLocation::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const {
+	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
+	auto paintx = 0, painty = 0, paintw = width(), painth = height();
+	bool bubble = _parent->hasBubble();
+	auto outbg = _parent->hasOutLayout();
+	bool selected = (selection == FullSelection);
+
+	if (bubble) {
+		if (!_title.isEmpty() || !_description.isEmpty()) {
+			if (isBubbleTop()) {
+				painty += st::msgPadding.top();
+			}
+		}
+
+		auto textw = width() - st::msgPadding.left() - st::msgPadding.right();
+
+		if (!_title.isEmpty()) {
+			p.setPen(outbg ? st::webPageTitleOutFg : st::webPageTitleInFg);
+			_title.drawLeftElided(p, paintx + st::msgPadding.left(), painty, textw, width(), 2, style::al_left, 0, -1, 0, false, selection);
+			painty += qMin(_title.countHeight(textw), 2 * st::webPageTitleFont->height);
+		}
+		if (!_description.isEmpty()) {
+			p.setPen(outbg ? st::webPageDescriptionOutFg : st::webPageDescriptionInFg);
+			_description.drawLeftElided(p, paintx + st::msgPadding.left(), painty, textw, width(), 3, style::al_left, 0, -1, 0, false, toDescriptionSelection(selection));
+			painty += qMin(_description.countHeight(textw), 3 * st::webPageDescriptionFont->height);
+		}
+		if (!_title.isEmpty() || !_description.isEmpty()) {
+			painty += st::mediaInBubbleSkip;
+		}
+		painth -= painty;
+	} else {
+		App::roundShadow(p, 0, 0, paintw, painth, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners);
+	}
+
+	const auto contextId = _parent->data()->fullId();
+	_data->load(contextId);
+	auto roundRadius = ImageRoundRadius::Large;
+	auto roundCorners = ((isBubbleTop() && _title.isEmpty() && _description.isEmpty()) ? (RectPart::TopLeft | RectPart::TopRight) : RectPart::None)
+		| (isBubbleBottom() ? (RectPart::BottomLeft | RectPart::BottomRight) : RectPart::None);
+	auto rthumb = QRect(paintx, painty, paintw, painth);
+	if (_data && !_data->thumb->isNull()) {
+		const auto &pix = _data->thumb->pixSingle(contextId, paintw, painth, paintw, painth, roundRadius, roundCorners);
+		p.drawPixmap(rthumb.topLeft(), pix);
+	} else {
+		App::complexLocationRect(p, rthumb, roundRadius, roundCorners);
+	}
+	const auto paintMarker = [&](const style::icon &icon) {
+		icon.paint(
+			p,
+			rthumb.x() + ((rthumb.width() - icon.width()) / 2),
+			rthumb.y() + (rthumb.height() / 2) - icon.height(),
+			width());
+	};
+	paintMarker(st::historyMapPoint);
+	paintMarker(st::historyMapPointInner);
+	if (selected) {
+		App::complexOverlayRect(p, rthumb, roundRadius, roundCorners);
+	}
+
+	if (_parent->media() == this) {
+		auto fullRight = paintx + paintw;
+		auto fullBottom = height();
+		_parent->drawInfo(p, fullRight, fullBottom, paintx * 2 + paintw, selected, InfoDisplayType::Image);
+		if (!bubble && _parent->displayRightAction()) {
+			auto fastShareLeft = (fullRight + st::historyFastShareLeft);
+			auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize);
+			_parent->drawRightAction(p, fastShareLeft, fastShareTop, 2 * paintx + paintw);
+		}
+	}
+}
+
+TextState HistoryLocation::textState(QPoint point, StateRequest request) const {
+	auto result = TextState(_parent);
+	auto symbolAdd = 0;
+
+	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) {
+		return result;
+	}
+	auto paintx = 0, painty = 0, paintw = width(), painth = height();
+	bool bubble = _parent->hasBubble();
+
+	if (bubble) {
+		if (!_title.isEmpty() || !_description.isEmpty()) {
+			if (isBubbleTop()) {
+				painty += st::msgPadding.top();
+			}
+		}
+
+		auto textw = width() - st::msgPadding.left() - st::msgPadding.right();
+
+		if (!_title.isEmpty()) {
+			auto titleh = qMin(_title.countHeight(textw), 2 * st::webPageTitleFont->height);
+			if (point.y() >= painty && point.y() < painty + titleh) {
+				result = TextState(_parent, _title.getStateLeft(
+					point - QPoint(paintx + st::msgPadding.left(), painty),
+					textw,
+					width(),
+					request.forText()));
+				return result;
+			} else if (point.y() >= painty + titleh) {
+				symbolAdd += _title.length();
+			}
+			painty += titleh;
+		}
+		if (!_description.isEmpty()) {
+			auto descriptionh = qMin(_description.countHeight(textw), 3 * st::webPageDescriptionFont->height);
+			if (point.y() >= painty && point.y() < painty + descriptionh) {
+				result = TextState(_parent, _description.getStateLeft(
+					point - QPoint(paintx + st::msgPadding.left(), painty),
+					textw,
+					width(),
+					request.forText()));
+			} else if (point.y() >= painty + descriptionh) {
+				symbolAdd += _description.length();
+			}
+			painty += descriptionh;
+		}
+		if (!_title.isEmpty() || !_description.isEmpty()) {
+			painty += st::mediaInBubbleSkip;
+		}
+		painth -= painty;
+	}
+	if (QRect(paintx, painty, paintw, painth).contains(point) && _data) {
+		result.link = _link;
+	}
+	if (_parent->media() == this) {
+		auto fullRight = paintx + paintw;
+		auto fullBottom = height();
+		if (_parent->pointInTime(fullRight, fullBottom, point, InfoDisplayType::Image)) {
+			result.cursor = CursorState::Date;
+		}
+		if (!bubble && _parent->displayRightAction()) {
+			auto fastShareLeft = (fullRight + st::historyFastShareLeft);
+			auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize);
+			if (QRect(fastShareLeft, fastShareTop, st::historyFastShareSize, st::historyFastShareSize).contains(point)) {
+				result.link = _parent->rightActionLink();
+			}
+		}
+	}
+	result.symbol += symbolAdd;
+	return result;
+}
+
+TextSelection HistoryLocation::adjustSelection(TextSelection selection, TextSelectType type) const {
+	if (_description.isEmpty() || selection.to <= _title.length()) {
+		return _title.adjustSelection(selection, type);
+	}
+	auto descriptionSelection = _description.adjustSelection(toDescriptionSelection(selection), type);
+	if (selection.from >= _title.length()) {
+		return fromDescriptionSelection(descriptionSelection);
+	}
+	auto titleSelection = _title.adjustSelection(selection, type);
+	return { titleSelection.from, fromDescriptionSelection(descriptionSelection).to };
+}
+
+TextWithEntities HistoryLocation::selectedText(TextSelection selection) const {
+	auto titleResult = _title.originalTextWithEntities(selection);
+	auto descriptionResult = _description.originalTextWithEntities(toDescriptionSelection(selection));
+	if (titleResult.text.isEmpty()) {
+		return descriptionResult;
+	} else if (descriptionResult.text.isEmpty()) {
+		return titleResult;
+	}
+	titleResult.text += '\n';
+	TextUtilities::Append(titleResult, std::move(descriptionResult));
+	return titleResult;
+}
+
+bool HistoryLocation::needsBubble() const {
+	if (!_title.isEmpty() || !_description.isEmpty()) {
+		return true;
+	}
+	const auto item = _parent->data();
+	return item->viaBot()
+		|| item->Has<HistoryMessageReply>()
+		|| _parent->displayForwardedFrom()
+		|| _parent->displayFromName();
+	return false;
+}
+
+int HistoryLocation::fullWidth() const {
+	return st::locationSize.width();
+}
+
+int HistoryLocation::fullHeight() const {
+	return st::locationSize.height();
+}
diff --git a/Telegram/SourceFiles/history/media/history_media_location.h b/Telegram/SourceFiles/history/media/history_media_location.h
new file mode 100644
index 000000000..1fcbd0b2c
--- /dev/null
+++ b/Telegram/SourceFiles/history/media/history_media_location.h
@@ -0,0 +1,72 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+#include "history/media/history_media.h"
+
+class LocationCoords;
+struct LocationData;
+
+class HistoryLocation : public HistoryMedia {
+public:
+	HistoryLocation(
+		not_null<Element*> parent,
+		not_null<LocationData*> location,
+		const QString &title = QString(),
+		const QString &description = QString());
+
+	HistoryMediaType type() const override {
+		return MediaTypeLocation;
+	}
+
+	void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override;
+	TextState textState(QPoint point, StateRequest request) const override;
+
+	[[nodiscard]] TextSelection adjustSelection(
+		TextSelection selection,
+		TextSelectType type) const override;
+	uint16 fullSelectionLength() const override {
+		return _title.length() + _description.length();
+	}
+	bool hasTextForCopy() const override {
+		return !_title.isEmpty() || !_description.isEmpty();
+	}
+
+	bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override {
+		return p == _link;
+	}
+	bool dragItemByHandler(const ClickHandlerPtr &p) const override {
+		return p == _link;
+	}
+
+	TextWithEntities selectedText(TextSelection selection) const override;
+
+	bool needsBubble() const override;
+	bool customInfoLayout() const override {
+		return true;
+	}
+
+	bool skipBubbleTail() const override {
+		return isBubbleBottom();
+	}
+
+private:
+	QSize countOptimalSize() override;
+	QSize countCurrentSize(int newWidth) override;
+
+	TextSelection toDescriptionSelection(TextSelection selection) const;
+	TextSelection fromDescriptionSelection(TextSelection selection) const;
+
+	LocationData *_data;
+	Text _title, _description;
+	ClickHandlerPtr _link;
+
+	int fullWidth() const;
+	int fullHeight() const;
+
+};
diff --git a/Telegram/SourceFiles/history/media/history_media_photo.cpp b/Telegram/SourceFiles/history/media/history_media_photo.cpp
new file mode 100644
index 000000000..244b3b9e5
--- /dev/null
+++ b/Telegram/SourceFiles/history/media/history_media_photo.cpp
@@ -0,0 +1,530 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "history/media/history_media_photo.h"
+
+#include "layout.h"
+#include "auth_session.h"
+#include "history/history_item_components.h"
+#include "history/history_item.h"
+#include "history/view/history_view_element.h"
+#include "history/view/history_view_cursor_state.h"
+#include "history/media/history_media_common.h"
+#include "ui/image/image.h"
+#include "ui/grouped_layout.h"
+#include "data/data_session.h"
+#include "data/data_photo.h"
+#include "styles/style_history.h"
+
+namespace {
+
+using TextState = HistoryView::TextState;
+
+} // namespace
+
+HistoryPhoto::HistoryPhoto(
+	not_null<Element*> parent,
+	not_null<HistoryItem*> realParent,
+	not_null<PhotoData*> photo)
+: HistoryFileMedia(parent, realParent)
+, _data(photo)
+, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) {
+	const auto fullId = realParent->fullId();
+	setLinks(
+		std::make_shared<PhotoOpenClickHandler>(_data, fullId),
+		std::make_shared<PhotoSaveClickHandler>(_data, fullId),
+		std::make_shared<PhotoCancelClickHandler>(_data, fullId));
+	_caption = createCaption(realParent);
+	create(realParent->fullId());
+}
+
+HistoryPhoto::HistoryPhoto(
+	not_null<Element*> parent,
+	not_null<PeerData*> chat,
+	not_null<PhotoData*> photo,
+	int width)
+: HistoryFileMedia(parent, parent->data())
+, _data(photo)
+, _serviceWidth(width) {
+	create(parent->data()->fullId(), chat);
+}
+
+void HistoryPhoto::create(FullMsgId contextId, PeerData *chat) {
+	setLinks(
+		std::make_shared<PhotoOpenClickHandler>(_data, contextId, chat),
+		std::make_shared<PhotoSaveClickHandler>(_data, contextId, chat),
+		std::make_shared<PhotoCancelClickHandler>(_data, contextId, chat));
+	_data->thumb->load(contextId);
+}
+
+QSize HistoryPhoto::countOptimalSize() {
+	if (_parent->media() != this) {
+		_caption = Text();
+	} else if (_caption.hasSkipBlock()) {
+		_caption.updateSkipBlock(
+			_parent->skipBlockWidth(),
+			_parent->skipBlockHeight());
+	}
+
+	auto maxWidth = 0;
+	auto minHeight = 0;
+
+	auto tw = ConvertScale(_data->full->width());
+	auto th = ConvertScale(_data->full->height());
+	if (!tw || !th) {
+		tw = th = 1;
+	}
+	if (tw > st::maxMediaSize) {
+		th = (st::maxMediaSize * th) / tw;
+		tw = st::maxMediaSize;
+	}
+	if (th > st::maxMediaSize) {
+		tw = (st::maxMediaSize * tw) / th;
+		th = st::maxMediaSize;
+	}
+
+	if (_serviceWidth > 0) {
+		return { _serviceWidth, _serviceWidth };
+	}
+	const auto minWidth = qMax(st::minPhotoSize, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x()));
+	const auto maxActualWidth = qMax(tw, minWidth);
+	maxWidth = qMax(maxActualWidth, th);
+	minHeight = qMax(th, st::minPhotoSize);
+	if (_parent->hasBubble() && !_caption.isEmpty()) {
+		auto captionw = maxActualWidth - st::msgPadding.left() - st::msgPadding.right();
+		minHeight += st::mediaCaptionSkip + _caption.countHeight(captionw);
+		if (isBubbleBottom()) {
+			minHeight += st::msgPadding.bottom();
+		}
+	}
+	return { maxWidth, minHeight };
+}
+
+QSize HistoryPhoto::countCurrentSize(int newWidth) {
+	int tw = ConvertScale(_data->full->width()), th = ConvertScale(_data->full->height());
+	if (tw > st::maxMediaSize) {
+		th = (st::maxMediaSize * th) / tw;
+		tw = st::maxMediaSize;
+	}
+	if (th > st::maxMediaSize) {
+		tw = (st::maxMediaSize * tw) / th;
+		th = st::maxMediaSize;
+	}
+
+	_pixw = qMin(newWidth, maxWidth());
+	_pixh = th;
+	if (tw > _pixw) {
+		_pixh = (_pixw * _pixh / tw);
+	} else {
+		_pixw = tw;
+	}
+	if (_pixh > newWidth) {
+		_pixw = (_pixw * newWidth) / _pixh;
+		_pixh = newWidth;
+	}
+	if (_pixw < 1) _pixw = 1;
+	if (_pixh < 1) _pixh = 1;
+
+	auto minWidth = qMax(st::minPhotoSize, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x()));
+	newWidth = qMax(_pixw, minWidth);
+	auto newHeight = qMax(_pixh, st::minPhotoSize);
+	if (_parent->hasBubble() && !_caption.isEmpty()) {
+		const auto captionw = newWidth
+			- st::msgPadding.left()
+			- st::msgPadding.right();
+		newHeight += st::mediaCaptionSkip + _caption.countHeight(captionw);
+		if (isBubbleBottom()) {
+			newHeight += st::msgPadding.bottom();
+		}
+	}
+	return { newWidth, newHeight };
+}
+
+void HistoryPhoto::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const {
+	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
+
+	_data->automaticLoad(_realParent->fullId(), _parent->data());
+	auto selected = (selection == FullSelection);
+	auto loaded = _data->loaded();
+	auto displayLoading = _data->displayLoading();
+
+	auto inWebPage = (_parent->media() != this);
+	auto paintx = 0, painty = 0, paintw = width(), painth = height();
+	auto bubble = _parent->hasBubble();
+
+	auto captionw = paintw - st::msgPadding.left() - st::msgPadding.right();
+
+	if (displayLoading) {
+		ensureAnimation();
+		if (!_animation->radial.animating()) {
+			_animation->radial.start(_data->progress());
+		}
+	}
+	bool radial = isRadialAnimation(ms);
+
+	auto rthumb = rtlrect(paintx, painty, paintw, painth, width());
+	if (_serviceWidth > 0) {
+		const auto pix = loaded
+			? _data->full->pixCircled(_realParent->fullId(), _pixw, _pixh)
+			: _data->thumb->pixBlurredCircled(_realParent->fullId(), _pixw, _pixh);
+		p.drawPixmap(rthumb.topLeft(), pix);
+	} else {
+		if (bubble) {
+			if (!_caption.isEmpty()) {
+				painth -= st::mediaCaptionSkip + _caption.countHeight(captionw);
+				if (isBubbleBottom()) {
+					painth -= st::msgPadding.bottom();
+				}
+				rthumb = rtlrect(paintx, painty, paintw, painth, width());
+			}
+		} else {
+			App::roundShadow(p, 0, 0, paintw, painth, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners);
+		}
+		auto inWebPage = (_parent->media() != this);
+		auto roundRadius = inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large;
+		auto roundCorners = inWebPage ? RectPart::AllCorners : ((isBubbleTop() ? (RectPart::TopLeft | RectPart::TopRight) : RectPart::None)
+			| ((isBubbleBottom() && _caption.isEmpty()) ? (RectPart::BottomLeft | RectPart::BottomRight) : RectPart::None));
+		const auto pix = loaded
+			? _data->full->pixSingle(_realParent->fullId(), _pixw, _pixh, paintw, painth, roundRadius, roundCorners)
+			: _data->thumb->pixBlurredSingle(_realParent->fullId(), _pixw, _pixh, paintw, painth, roundRadius, roundCorners);
+		p.drawPixmap(rthumb.topLeft(), pix);
+		if (selected) {
+			App::complexOverlayRect(p, rthumb, roundRadius, roundCorners);
+		}
+	}
+	if (radial || (!loaded && !_data->loading())) {
+		const auto radialOpacity = (radial && loaded && !_data->uploading())
+			? _animation->radial.opacity() :
+			1.;
+		QRect inner(rthumb.x() + (rthumb.width() - st::msgFileSize) / 2, rthumb.y() + (rthumb.height() - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize);
+		p.setPen(Qt::NoPen);
+		if (selected) {
+			p.setBrush(st::msgDateImgBgSelected);
+		} else if (isThumbAnimation(ms)) {
+			auto over = _animation->a_thumbOver.current();
+			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(radialOpacity * p.opacity());
+
+		{
+			PainterHighQualityEnabler hq(p);
+			p.drawEllipse(inner);
+		}
+
+		p.setOpacity(radialOpacity);
+		auto icon = ([radial, this, selected]() -> const style::icon* {
+			if (radial || _data->loading()) {
+				if (_data->uploading()
+					|| !_data->full->location().isNull()) {
+					return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel);
+				}
+				return nullptr;
+			}
+			return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload);
+		})();
+		if (icon) {
+			icon->paintInCenter(p, inner);
+		}
+		p.setOpacity(1);
+		if (radial) {
+			QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine)));
+			_animation->radial.draw(p, rinner, st::msgFileRadialLine, selected ? st::historyFileThumbRadialFgSelected : st::historyFileThumbRadialFg);
+		}
+	}
+
+	// date
+	if (!_caption.isEmpty()) {
+		auto outbg = _parent->hasOutLayout();
+		p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg));
+		_caption.draw(p, st::msgPadding.left(), painty + painth + st::mediaCaptionSkip, captionw, style::al_left, 0, -1, selection);
+	} else if (!inWebPage) {
+		auto fullRight = paintx + paintw;
+		auto fullBottom = painty + painth;
+		if (needInfoDisplay()) {
+			_parent->drawInfo(p, fullRight, fullBottom, 2 * paintx + paintw, selected, InfoDisplayType::Image);
+		}
+		if (!bubble && _parent->displayRightAction()) {
+			auto fastShareLeft = (fullRight + st::historyFastShareLeft);
+			auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize);
+			_parent->drawRightAction(p, fastShareLeft, fastShareTop, 2 * paintx + paintw);
+		}
+	}
+}
+
+TextState HistoryPhoto::textState(QPoint point, StateRequest request) const {
+	auto result = TextState(_parent);
+
+	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) {
+		return result;
+	}
+	auto paintx = 0, painty = 0, paintw = width(), painth = height();
+	auto bubble = _parent->hasBubble();
+
+	if (bubble && !_caption.isEmpty()) {
+		const auto captionw = paintw
+			- st::msgPadding.left()
+			- st::msgPadding.right();
+		painth -= _caption.countHeight(captionw);
+		if (isBubbleBottom()) {
+			painth -= st::msgPadding.bottom();
+		}
+		if (QRect(st::msgPadding.left(), painth, captionw, height() - painth).contains(point)) {
+			result = TextState(_parent, _caption.getState(
+				point - QPoint(st::msgPadding.left(), painth),
+				captionw,
+				request.forText()));
+			return result;
+		}
+		painth -= st::mediaCaptionSkip;
+	}
+	if (QRect(paintx, painty, paintw, painth).contains(point)) {
+		if (_data->uploading()) {
+			result.link = _cancell;
+		} else if (_data->loaded()) {
+			result.link = _openl;
+		} else if (_data->loading()) {
+			if (!_data->full->location().isNull()) {
+				result.link = _cancell;
+			}
+		} else {
+			result.link = _savel;
+		}
+	}
+	if (_caption.isEmpty() && _parent->media() == this) {
+		auto fullRight = paintx + paintw;
+		auto fullBottom = painty + painth;
+		if (_parent->pointInTime(fullRight, fullBottom, point, InfoDisplayType::Image)) {
+			result.cursor = CursorState::Date;
+		}
+		if (!bubble && _parent->displayRightAction()) {
+			auto fastShareLeft = (fullRight + st::historyFastShareLeft);
+			auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize);
+			if (QRect(fastShareLeft, fastShareTop, st::historyFastShareSize, st::historyFastShareSize).contains(point)) {
+				result.link = _parent->rightActionLink();
+			}
+		}
+	}
+	return result;
+}
+
+QSize HistoryPhoto::sizeForGrouping() const {
+	const auto width = _data->full->width();
+	const auto height = _data->full->height();
+	return { std::max(width, 1), std::max(height, 1) };
+}
+
+void HistoryPhoto::drawGrouped(
+		Painter &p,
+		const QRect &clip,
+		TextSelection selection,
+		TimeMs 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(ms);
+
+	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 displayState = radial
+		|| (!loaded && !_data->loading())
+		|| _data->waitingForAlbum();
+	if (displayState) {
+		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(ms)) {
+			auto over = _animation->a_thumbOver.current();
+			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);
+		}
+
+		const auto icon = [&]() -> const style::icon* {
+			if (_data->waitingForAlbum()) {
+				return &(selected ? st::historyFileThumbWaitingSelected : st::historyFileThumbWaiting);
+			} else if (radial || _data->loading()) {
+				if (_data->uploading() || !_data->full->location().isNull()) {
+					return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel);
+				}
+				return nullptr;
+			}
+			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 HistoryPhoto::getStateGrouped(
+		const QRect &geometry,
+		QPoint point,
+		StateRequest request) const {
+	if (!geometry.contains(point)) {
+		return {};
+	}
+	return TextState(_parent, _data->uploading()
+		? _cancell
+		: _data->loaded()
+		? _openl
+		: _data->loading()
+		? (_data->full->location().isNull()
+			? ClickHandlerPtr()
+			: _cancell)
+		: _savel);
+}
+
+float64 HistoryPhoto::dataProgress() const {
+	return _data->progress();
+}
+
+bool HistoryPhoto::dataFinished() const {
+	return !_data->loading()
+		&& (!_data->uploading() || _data->waitingForAlbum());
+}
+
+bool HistoryPhoto::dataLoaded() const {
+	return _data->loaded();
+}
+
+bool HistoryPhoto::needInfoDisplay() const {
+	return (_parent->data()->id < 0 || _parent->isUnderCursor());
+}
+
+void HistoryPhoto::validateGroupedCache(
+		const QRect &geometry,
+		RectParts corners,
+		not_null<uint64*> cacheKey,
+		not_null<QPixmap*> cache) const {
+	using Option = Images::Option;
+	const auto loaded = _data->loaded();
+	const auto loadLevel = loaded ? 2 : _data->thumb->loaded() ? 1 : 0;
+	const auto width = geometry.width();
+	const auto height = geometry.height();
+	const auto options = Option::Smooth
+		| Option::RoundedLarge
+		| (loaded ? Option::None : 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 originalWidth = ConvertScale(_data->full->width());
+	const auto originalHeight = ConvertScale(_data->full->height());
+	const auto pixSize = Ui::GetImageScaleSizeForGeometry(
+		{ originalWidth, originalHeight },
+		{ width, height });
+	const auto pixWidth = pixSize.width() * cIntRetinaFactor();
+	const auto pixHeight = pixSize.height() * cIntRetinaFactor();
+	const auto &image = loaded ? _data->full : _data->thumb;
+
+	*cacheKey = key;
+	*cache = image->pixNoCache(_realParent->fullId(), pixWidth, pixHeight, options, width, height);
+}
+
+TextWithEntities HistoryPhoto::selectedText(TextSelection selection) const {
+	return _caption.originalTextWithEntities(selection, ExpandLinksAll);
+}
+
+bool HistoryPhoto::needsBubble() const {
+	if (!_caption.isEmpty()) {
+		return true;
+	}
+	const auto item = _parent->data();
+	if (item->toHistoryMessage()) {
+		return item->viaBot()
+			|| item->Has<HistoryMessageReply>()
+			|| _parent->displayForwardedFrom()
+			|| _parent->displayFromName();
+	}
+	return false;
+}
+
+bool HistoryPhoto::isReadyForOpen() const {
+	return _data->loaded();
+}
+
+void HistoryPhoto::parentTextUpdated() {
+	_caption = (_parent->media() == this)
+		? createCaption(_parent->data())
+		: Text();
+	Auth().data().requestViewResize(_parent);
+}
diff --git a/Telegram/SourceFiles/history/media/history_media_photo.h b/Telegram/SourceFiles/history/media/history_media_photo.h
new file mode 100644
index 000000000..019ce379e
--- /dev/null
+++ b/Telegram/SourceFiles/history/media/history_media_photo.h
@@ -0,0 +1,102 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+#include "history/media/history_media_file.h"
+
+class HistoryPhoto : public HistoryFileMedia {
+public:
+	HistoryPhoto(
+		not_null<Element*> parent,
+		not_null<HistoryItem*> realParent,
+		not_null<PhotoData*> photo);
+	HistoryPhoto(
+		not_null<Element*> parent,
+		not_null<PeerData*> chat,
+		not_null<PhotoData*> photo,
+		int width);
+
+	HistoryMediaType type() const override {
+		return MediaTypePhoto;
+	}
+
+	void draw(Painter &p, const QRect &clip, TextSelection selection, TimeMs ms) const override;
+	TextState textState(QPoint point, StateRequest request) const override;
+
+	[[nodiscard]] TextSelection adjustSelection(
+			TextSelection selection,
+			TextSelectType type) const override {
+		return _caption.adjustSelection(selection, type);
+	}
+	uint16 fullSelectionLength() const override {
+		return _caption.length();
+	}
+	bool hasTextForCopy() const override {
+		return !_caption.isEmpty();
+	}
+
+	TextWithEntities selectedText(TextSelection selection) const override;
+
+	PhotoData *getPhoto() const override {
+		return _data;
+	}
+
+	QSize sizeForGrouping() const override;
+	void drawGrouped(
+		Painter &p,
+		const QRect &clip,
+		TextSelection selection,
+		TimeMs 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;
+
+	TextWithEntities getCaption() const override {
+		return _caption.originalTextWithEntities();
+	}
+	bool needsBubble() const override;
+	bool customInfoLayout() const override {
+		return _caption.isEmpty();
+	}
+	bool skipBubbleTail() const override {
+		return isBubbleBottom() && _caption.isEmpty();
+	}
+	bool isReadyForOpen() const override;
+
+	void parentTextUpdated() override;
+
+protected:
+	float64 dataProgress() const override;
+	bool dataFinished() const override;
+	bool dataLoaded() const override;
+
+private:
+	void create(FullMsgId contextId, PeerData *chat = nullptr);
+
+	QSize countOptimalSize() override;
+	QSize countCurrentSize(int newWidth) override;
+
+	bool needInfoDisplay() const;
+	void validateGroupedCache(
+		const QRect &geometry,
+		RectParts corners,
+		not_null<uint64*> cacheKey,
+		not_null<QPixmap*> cache) const;
+
+	not_null<PhotoData*> _data;
+	int _serviceWidth = 0;
+	int _pixw = 1;
+	int _pixh = 1;
+	Text _caption;
+
+};
diff --git a/Telegram/SourceFiles/history/media/history_media_sticker.cpp b/Telegram/SourceFiles/history/media/history_media_sticker.cpp
new file mode 100644
index 000000000..f671db755
--- /dev/null
+++ b/Telegram/SourceFiles/history/media/history_media_sticker.cpp
@@ -0,0 +1,273 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "history/media/history_media_sticker.h"
+
+#include "layout.h"
+#include "boxes/sticker_set_box.h"
+#include "history/history_item_components.h"
+#include "history/history_item.h"
+#include "history/view/history_view_element.h"
+#include "history/view/history_view_cursor_state.h"
+#include "ui/image/image.h"
+#include "ui/emoji_config.h"
+#include "data/data_document.h"
+#include "styles/style_history.h"
+
+namespace {
+
+using TextState = HistoryView::TextState;
+
+} // namespace
+
+HistorySticker::HistorySticker(
+	not_null<Element*> parent,
+	not_null<DocumentData*> document)
+: HistoryMedia(parent)
+, _data(document)
+, _emoji(_data->sticker()->alt) {
+	_data->thumb->load(parent->data()->fullId());
+	if (auto emoji = Ui::Emoji::Find(_emoji)) {
+		_emoji = emoji->text();
+	}
+}
+
+QSize HistorySticker::countOptimalSize() {
+	auto sticker = _data->sticker();
+
+	if (!_packLink && sticker && sticker->set.type() != mtpc_inputStickerSetEmpty) {
+		_packLink = std::make_shared<LambdaClickHandler>([document = _data] {
+			StickerSetBox::Show(document);
+		});
+	}
+	_pixw = _data->dimensions.width();
+	_pixh = _data->dimensions.height();
+	if (_pixw > st::maxStickerSize) {
+		_pixh = (st::maxStickerSize * _pixh) / _pixw;
+		_pixw = st::maxStickerSize;
+	}
+	if (_pixh > st::maxStickerSize) {
+		_pixw = (st::maxStickerSize * _pixw) / _pixh;
+		_pixh = st::maxStickerSize;
+	}
+	if (_pixw < 1) _pixw = 1;
+	if (_pixh < 1) _pixh = 1;
+	auto maxWidth = qMax(_pixw, st::minPhotoSize);
+	auto minHeight = qMax(_pixh, st::minPhotoSize);
+	accumulate_max(
+		maxWidth,
+		_parent->infoWidth() + 2 * st::msgDateImgPadding.x());
+	if (_parent->media() == this) {
+		maxWidth += additionalWidth();
+	}
+	return { maxWidth, minHeight };
+}
+
+QSize HistorySticker::countCurrentSize(int newWidth) {
+	const auto item = _parent->data();
+	accumulate_min(newWidth, maxWidth());
+	if (_parent->media() == this) {
+		auto via = item->Get<HistoryMessageVia>();
+		auto reply = item->Get<HistoryMessageReply>();
+		if (via || reply) {
+			int usew = maxWidth() - additionalWidth(via, reply);
+			int availw = newWidth - usew - st::msgReplyPadding.left() - st::msgReplyPadding.left() - st::msgReplyPadding.left();
+			if (via) {
+				via->resize(availw);
+			}
+			if (reply) {
+				reply->resize(availw);
+			}
+		}
+	}
+	return { newWidth, minHeight() };
+}
+
+void HistorySticker::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const {
+	auto sticker = _data->sticker();
+	if (!sticker) return;
+
+	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
+
+	_data->checkSticker();
+	bool loaded = _data->loaded();
+	bool selected = (selection == FullSelection);
+
+	auto outbg = _parent->hasOutLayout();
+	auto inWebPage = (_parent->media() != this);
+
+	const auto item = _parent->data();
+	int usew = maxWidth(), usex = 0;
+	auto via = inWebPage ? nullptr : item->Get<HistoryMessageVia>();
+	auto reply = inWebPage ? nullptr : item->Get<HistoryMessageReply>();
+	if (via || reply) {
+		usew -= additionalWidth(via, reply);
+		if (outbg) {
+			usex = width() - usew;
+		}
+	}
+	if (rtl()) usex = width() - usex - usew;
+
+	const auto &pixmap = [&]() -> const QPixmap & {
+		const auto o = item->fullId();
+		const auto w = _pixw;
+		const auto h = _pixh;
+		const auto &c = st::msgStickerOverlay;
+		if (const auto image = _data->getStickerImage()) {
+			return selected
+				? image->pixColored(o, c, w, h)
+				: image->pix(o, w, h);
+		}
+		return selected
+			? _data->thumb->pixBlurredColored(o, c, w, h)
+			: _data->thumb->pixBlurred(o, w, h);
+	}();
+	p.drawPixmap(
+		QPoint{ usex + (usew - _pixw) / 2, (minHeight() - _pixh) / 2 },
+		pixmap);
+
+	if (!inWebPage) {
+		auto fullRight = usex + usew;
+		auto fullBottom = height();
+		if (needInfoDisplay()) {
+			_parent->drawInfo(p, fullRight, fullBottom, usex * 2 + usew, selected, InfoDisplayType::Background);
+		}
+		if (via || reply) {
+			int rectw = width() - usew - st::msgReplyPadding.left();
+			int recth = st::msgReplyPadding.top() + st::msgReplyPadding.bottom();
+			if (via) {
+				recth += st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0);
+			}
+			if (reply) {
+				recth += st::msgReplyBarSize.height();
+			}
+			int rectx = outbg ? 0 : (usew + st::msgReplyPadding.left());
+			int recty = st::msgDateImgDelta;
+			if (rtl()) rectx = width() - rectx - rectw;
+
+			App::roundRect(p, rectx, recty, rectw, recth, selected ? st::msgServiceBgSelected : st::msgServiceBg, selected ? StickerSelectedCorners : StickerCorners);
+			p.setPen(st::msgServiceFg);
+			rectx += st::msgReplyPadding.left();
+			rectw -= st::msgReplyPadding.left() + st::msgReplyPadding.right();
+			if (via) {
+				p.setFont(st::msgDateFont);
+				p.drawTextLeft(rectx, recty + st::msgReplyPadding.top(), 2 * rectx + rectw, via->text);
+				int skip = st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0);
+				recty += skip;
+			}
+			if (reply) {
+				HistoryMessageReply::PaintFlags flags = 0;
+				if (selected) {
+					flags |= HistoryMessageReply::PaintFlag::Selected;
+				}
+				reply->paint(p, _parent, rectx, recty, rectw, flags);
+			}
+		}
+		if (_parent->displayRightAction()) {
+			auto fastShareLeft = (fullRight + st::historyFastShareLeft);
+			auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize);
+			_parent->drawRightAction(p, fastShareLeft, fastShareTop, 2 * usex + usew);
+		}
+	}
+}
+
+TextState HistorySticker::textState(QPoint point, StateRequest request) const {
+	auto result = TextState(_parent);
+	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) {
+		return result;
+	}
+
+	auto outbg = _parent->hasOutLayout();
+	auto inWebPage = (_parent->media() != this);
+
+	const auto item = _parent->data();
+	int usew = maxWidth(), usex = 0;
+	auto via = inWebPage ? nullptr : item->Get<HistoryMessageVia>();
+	auto reply = inWebPage ? nullptr : item->Get<HistoryMessageReply>();
+	if (via || reply) {
+		usew -= additionalWidth(via, reply);
+		if (outbg) {
+			usex = width() - usew;
+		}
+	}
+	if (rtl()) usex = width() - usex - usew;
+
+	if (via || reply) {
+		int rectw = width() - usew - st::msgReplyPadding.left();
+		int recth = st::msgReplyPadding.top() + st::msgReplyPadding.bottom();
+		if (via) {
+			recth += st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0);
+		}
+		if (reply) {
+			recth += st::msgReplyBarSize.height();
+		}
+		int rectx = outbg ? 0 : (usew + st::msgReplyPadding.left());
+		int recty = st::msgDateImgDelta;
+		if (rtl()) rectx = width() - rectx - rectw;
+
+		if (via) {
+			int viah = st::msgReplyPadding.top() + st::msgServiceNameFont->height + (reply ? 0 : st::msgReplyPadding.bottom());
+			if (QRect(rectx, recty, rectw, viah).contains(point)) {
+				result.link = via->link;
+				return result;
+			}
+			int skip = st::msgServiceNameFont->height + (reply ? 2 * st::msgReplyPadding.top() : 0);
+			recty += skip;
+			recth -= skip;
+		}
+		if (reply) {
+			if (QRect(rectx, recty, rectw, recth).contains(point)) {
+				result.link = reply->replyToLink();
+				return result;
+			}
+		}
+	}
+	if (_parent->media() == this) {
+		auto fullRight = usex + usew;
+		auto fullBottom = height();
+		if (_parent->pointInTime(fullRight, fullBottom, point, InfoDisplayType::Image)) {
+			result.cursor = CursorState::Date;
+		}
+		if (_parent->displayRightAction()) {
+			auto fastShareLeft = (fullRight + st::historyFastShareLeft);
+			auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize);
+			if (QRect(fastShareLeft, fastShareTop, st::historyFastShareSize, st::historyFastShareSize).contains(point)) {
+				result.link = _parent->rightActionLink();
+			}
+		}
+	}
+
+	auto pixLeft = usex + (usew - _pixw) / 2;
+	auto pixTop = (minHeight() - _pixh) / 2;
+	if (QRect(pixLeft, pixTop, _pixw, _pixh).contains(point)) {
+		result.link = _packLink;
+		return result;
+	}
+	return result;
+}
+
+bool HistorySticker::needInfoDisplay() const {
+	return (_parent->data()->id < 0 || _parent->isUnderCursor());
+}
+
+int HistorySticker::additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply *reply) const {
+	int result = 0;
+	if (via) {
+		accumulate_max(result, st::msgReplyPadding.left() + st::msgReplyPadding.left() + via->maxWidth + st::msgReplyPadding.left());
+	}
+	if (reply) {
+		accumulate_max(result, st::msgReplyPadding.left() + reply->replyToWidth());
+	}
+	return result;
+}
+
+int HistorySticker::additionalWidth() const {
+	const auto item = _parent->data();
+	return additionalWidth(
+		item->Get<HistoryMessageVia>(),
+		item->Get<HistoryMessageReply>());
+}
diff --git a/Telegram/SourceFiles/history/media/history_media_sticker.h b/Telegram/SourceFiles/history/media/history_media_sticker.h
new file mode 100644
index 000000000..8d84df6a1
--- /dev/null
+++ b/Telegram/SourceFiles/history/media/history_media_sticker.h
@@ -0,0 +1,67 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+#include "history/media/history_media.h"
+
+struct HistoryMessageVia;
+struct HistoryMessageReply;
+struct HistoryMessageForwarded;
+
+class HistorySticker : public HistoryMedia {
+public:
+	HistorySticker(
+		not_null<Element*> parent,
+		not_null<DocumentData*> document);
+
+	HistoryMediaType type() const override {
+		return MediaTypeSticker;
+	}
+
+	void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override;
+	TextState textState(QPoint point, StateRequest request) const override;
+
+	bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override {
+		return true;
+	}
+	bool dragItem() const override {
+		return true;
+	}
+	bool dragItemByHandler(const ClickHandlerPtr &p) const override {
+		return true;
+	}
+
+	DocumentData *getDocument() const override {
+		return _data;
+	}
+
+	bool needsBubble() const override {
+		return false;
+	}
+	bool customInfoLayout() const override {
+		return true;
+	}
+	QString emoji() const {
+		return _emoji;
+	}
+
+private:
+	QSize countOptimalSize() override;
+	QSize countCurrentSize(int newWidth) override;
+
+	bool needInfoDisplay() const;
+	int additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply *reply) const;
+	int additionalWidth() const;
+
+	int _pixw = 1;
+	int _pixh = 1;
+	ClickHandlerPtr _packLink;
+	not_null<DocumentData*> _data;
+	QString _emoji;
+
+};
diff --git a/Telegram/SourceFiles/history/media/history_media_video.cpp b/Telegram/SourceFiles/history/media/history_media_video.cpp
new file mode 100644
index 000000000..d648f743f
--- /dev/null
+++ b/Telegram/SourceFiles/history/media/history_media_video.cpp
@@ -0,0 +1,525 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "history/media/history_media_video.h"
+
+#include "history/media/history_media_common.h"
+#include "layout.h"
+#include "auth_session.h"
+#include "history/history_item_components.h"
+#include "history/history_item.h"
+#include "history/view/history_view_element.h"
+#include "history/view/history_view_cursor_state.h"
+#include "ui/image/image.h"
+#include "ui/grouped_layout.h"
+#include "data/data_session.h"
+#include "data/data_document.h"
+#include "styles/style_history.h"
+
+namespace {
+
+using TextState = HistoryView::TextState;
+
+} // namespace
+
+HistoryVideo::HistoryVideo(
+	not_null<Element*> parent,
+	not_null<HistoryItem*> realParent,
+	not_null<DocumentData*> document)
+: HistoryFileMedia(parent, realParent)
+, _data(document)
+, _thumbw(1)
+, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) {
+	_caption = createCaption(realParent);
+
+	setDocumentLinks(_data, realParent);
+
+	setStatusSize(FileStatusSizeReady);
+
+	_data->thumb->load(realParent->fullId());
+}
+
+QSize HistoryVideo::countOptimalSize() {
+	if (_parent->media() != this) {
+		_caption = Text();
+	} else if (_caption.hasSkipBlock()) {
+		_caption.updateSkipBlock(
+			_parent->skipBlockWidth(),
+			_parent->skipBlockHeight());
+	}
+
+	auto tw = ConvertScale(_data->thumb->width());
+	auto th = ConvertScale(_data->thumb->height());
+	if (!tw || !th) {
+		tw = th = 1;
+	}
+	if (tw * st::msgVideoSize.height() > th * st::msgVideoSize.width()) {
+		th = qRound((st::msgVideoSize.width() / float64(tw)) * th);
+		tw = st::msgVideoSize.width();
+	} else {
+		tw = qRound((st::msgVideoSize.height() / float64(th)) * tw);
+		th = st::msgVideoSize.height();
+	}
+
+	_thumbw = qMax(tw, 1);
+	_thumbh = qMax(th, 1);
+
+	auto minWidth = qMax(st::minPhotoSize, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x()));
+	minWidth = qMax(minWidth, documentMaxStatusWidth(_data) + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x()));
+	auto maxWidth = qMax(_thumbw, minWidth);
+	auto minHeight = qMax(th, st::minPhotoSize);
+	if (_parent->hasBubble() && !_caption.isEmpty()) {
+		const auto captionw = maxWidth
+			- st::msgPadding.left()
+			- st::msgPadding.right();
+		minHeight += st::mediaCaptionSkip + _caption.countHeight(captionw);
+		if (isBubbleBottom()) {
+			minHeight += st::msgPadding.bottom();
+		}
+	}
+	return { maxWidth, minHeight };
+}
+
+QSize HistoryVideo::countCurrentSize(int newWidth) {
+	int tw = ConvertScale(_data->thumb->width()), th = ConvertScale(_data->thumb->height());
+	if (!tw || !th) {
+		tw = th = 1;
+	}
+	if (tw * st::msgVideoSize.height() > th * st::msgVideoSize.width()) {
+		th = qRound((st::msgVideoSize.width() / float64(tw)) * th);
+		tw = st::msgVideoSize.width();
+	} else {
+		tw = qRound((st::msgVideoSize.height() / float64(th)) * tw);
+		th = st::msgVideoSize.height();
+	}
+
+	if (newWidth < tw) {
+		th = qRound((newWidth / float64(tw)) * th);
+		tw = newWidth;
+	}
+
+	_thumbw = qMax(tw, 1);
+	_thumbh = qMax(th, 1);
+	auto minWidth = qMax(st::minPhotoSize, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x()));
+	minWidth = qMax(minWidth, documentMaxStatusWidth(_data) + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x()));
+	newWidth = qMax(_thumbw, minWidth);
+	auto newHeight = qMax(th, st::minPhotoSize);
+	if (_parent->hasBubble() && !_caption.isEmpty()) {
+		const auto captionw = newWidth
+			- st::msgPadding.left()
+			- st::msgPadding.right();
+		newHeight += st::mediaCaptionSkip + _caption.countHeight(captionw);
+		if (isBubbleBottom()) {
+			newHeight += st::msgPadding.bottom();
+		}
+	}
+	return { newWidth, newHeight };
+}
+
+void HistoryVideo::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const {
+	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
+
+	_data->automaticLoad(_realParent->fullId(), _parent->data());
+	bool loaded = _data->loaded(), displayLoading = _data->displayLoading();
+	bool selected = (selection == FullSelection);
+
+	auto paintx = 0, painty = 0, paintw = width(), painth = height();
+	bool bubble = _parent->hasBubble();
+
+	int captionw = paintw - st::msgPadding.left() - st::msgPadding.right();
+
+	if (displayLoading) {
+		ensureAnimation();
+		if (!_animation->radial.animating()) {
+			_animation->radial.start(_data->progress());
+		}
+	}
+	updateStatusText();
+	bool radial = isRadialAnimation(ms);
+
+	if (bubble) {
+		if (!_caption.isEmpty()) {
+			painth -= st::mediaCaptionSkip + _caption.countHeight(captionw);
+			if (isBubbleBottom()) {
+				painth -= st::msgPadding.bottom();
+			}
+		}
+	} else {
+		App::roundShadow(p, 0, 0, paintw, painth, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners);
+	}
+
+	auto inWebPage = (_parent->media() != this);
+	auto roundRadius = inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large;
+	auto roundCorners = inWebPage ? RectPart::AllCorners : ((isBubbleTop() ? (RectPart::TopLeft | RectPart::TopRight) : RectPart::None)
+		| ((isBubbleBottom() && _caption.isEmpty()) ? (RectPart::BottomLeft | RectPart::BottomRight) : RectPart::None));
+	QRect rthumb(rtlrect(paintx, painty, paintw, painth, width()));
+
+	const auto good = _data->goodThumbnail();
+	if (good && good->loaded()) {
+		p.drawPixmap(rthumb.topLeft(), good->pixSingle({}, _thumbw, _thumbh, paintw, painth, roundRadius, roundCorners));
+	} else {
+		if (good) {
+			good->load({});
+		}
+		p.drawPixmap(rthumb.topLeft(), _data->thumb->pixBlurredSingle(_realParent->fullId(), _thumbw, _thumbh, paintw, painth, roundRadius, roundCorners));
+	}
+	if (selected) {
+		App::complexOverlayRect(p, rthumb, roundRadius, roundCorners);
+	}
+
+	QRect inner(rthumb.x() + (rthumb.width() - st::msgFileSize) / 2, rthumb.y() + (rthumb.height() - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize);
+	p.setPen(Qt::NoPen);
+	if (selected) {
+		p.setBrush(st::msgDateImgBgSelected);
+	} else if (isThumbAnimation(ms)) {
+		auto over = _animation->a_thumbOver.current();
+		p.setBrush(anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, over));
+	} else {
+		bool over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel);
+		p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg);
+	}
+
+	{
+		PainterHighQualityEnabler hq(p);
+		p.drawEllipse(inner);
+	}
+
+	if (!selected && _animation) {
+		p.setOpacity(1);
+	}
+
+	auto icon = ([this, radial, selected, loaded]() -> const style::icon * {
+		if (loaded && !radial) {
+			return &(selected ? st::historyFileThumbPlaySelected : st::historyFileThumbPlay);
+		} else if (radial || _data->loading()) {
+			if (_parent->data()->id > 0 || _data->uploading()) {
+				return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel);
+			}
+			return nullptr;
+		}
+		return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload);
+	})();
+	if (icon) {
+		icon->paintInCenter(p, inner);
+	}
+	if (radial) {
+		QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine)));
+		_animation->radial.draw(p, rinner, st::msgFileRadialLine, selected ? st::historyFileThumbRadialFgSelected : st::historyFileThumbRadialFg);
+	}
+
+	auto statusX = paintx + st::msgDateImgDelta + st::msgDateImgPadding.x(), statusY = painty + st::msgDateImgDelta + st::msgDateImgPadding.y();
+	auto statusW = st::normalFont->width(_statusText) + 2 * st::msgDateImgPadding.x();
+	auto statusH = st::normalFont->height + 2 * st::msgDateImgPadding.y();
+	App::roundRect(p, rtlrect(statusX - st::msgDateImgPadding.x(), statusY - st::msgDateImgPadding.y(), statusW, statusH, width()), selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners);
+	p.setFont(st::normalFont);
+	p.setPen(st::msgDateImgFg);
+	p.drawTextLeft(statusX, statusY, width(), _statusText, statusW - 2 * st::msgDateImgPadding.x());
+
+	// date
+	if (!_caption.isEmpty()) {
+		auto outbg = _parent->hasOutLayout();
+		p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg));
+		_caption.draw(p, st::msgPadding.left(), painty + painth + st::mediaCaptionSkip, captionw, style::al_left, 0, -1, selection);
+	} else if (_parent->media() == this) {
+		auto fullRight = paintx + paintw, fullBottom = painty + painth;
+		_parent->drawInfo(p, fullRight, fullBottom, 2 * paintx + paintw, selected, InfoDisplayType::Image);
+		if (!bubble && _parent->displayRightAction()) {
+			auto fastShareLeft = (fullRight + st::historyFastShareLeft);
+			auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize);
+			_parent->drawRightAction(p, fastShareLeft, fastShareTop, 2 * paintx + paintw);
+		}
+	}
+}
+
+TextState HistoryVideo::textState(QPoint point, StateRequest request) const {
+	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) {
+		return {};
+	}
+
+	auto result = TextState(_parent);
+	bool loaded = _data->loaded();
+
+	auto paintx = 0, painty = 0, paintw = width(), painth = height();
+	bool bubble = _parent->hasBubble();
+
+	if (bubble && !_caption.isEmpty()) {
+		const auto captionw = paintw
+			- st::msgPadding.left()
+			- st::msgPadding.right();
+		painth -= _caption.countHeight(captionw);
+		if (isBubbleBottom()) {
+			painth -= st::msgPadding.bottom();
+		}
+		if (QRect(st::msgPadding.left(), painth, captionw, height() - painth).contains(point)) {
+			result = TextState(_parent, _caption.getState(
+				point - QPoint(st::msgPadding.left(), painth),
+				captionw,
+				request.forText()));
+		}
+		painth -= st::mediaCaptionSkip;
+	}
+	if (QRect(paintx, painty, paintw, painth).contains(point)) {
+		if (_data->uploading()) {
+			result.link = _cancell;
+		} else {
+			result.link = loaded ? _openl : (_data->loading() ? _cancell : _savel);
+		}
+	}
+	if (_caption.isEmpty() && _parent->media() == this) {
+		auto fullRight = paintx + paintw;
+		auto fullBottom = painty + painth;
+		if (_parent->pointInTime(fullRight, fullBottom, point, InfoDisplayType::Image)) {
+			result.cursor = CursorState::Date;
+		}
+		if (!bubble && _parent->displayRightAction()) {
+			auto fastShareLeft = (fullRight + st::historyFastShareLeft);
+			auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize);
+			if (QRect(fastShareLeft, fastShareTop, st::historyFastShareSize, st::historyFastShareSize).contains(point)) {
+				result.link = _parent->rightActionLink();
+			}
+		}
+	}
+	return result;
+}
+
+QSize HistoryVideo::sizeForGrouping() const {
+	const auto width = _data->dimensions.isEmpty()
+		? _data->thumb->width()
+		: _data->dimensions.width();
+	const auto height = _data->dimensions.isEmpty()
+		? _data->thumb->height()
+		: _data->dimensions.height();
+	return { std::max(width, 1), std::max(height, 1) };
+}
+
+void HistoryVideo::drawGrouped(
+		Painter &p,
+		const QRect &clip,
+		TextSelection selection,
+		TimeMs 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(ms);
+
+	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(ms)) {
+		auto over = _animation->a_thumbOver.current();
+		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 (loaded && !radial) {
+			return &(selected ? st::historyFileThumbPlaySelected : st::historyFileThumbPlay);
+		} else if (radial || _data->loading()) {
+			if (_parent->data()->id > 0 || _data->uploading()) {
+				return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel);
+			}
+			return nullptr;
+		}
+		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.) {
+			LOG(("INTERPOLATING: %1").arg(radialOpacity));
+			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 HistoryVideo::getStateGrouped(
+		const QRect &geometry,
+		QPoint point,
+		StateRequest request) const {
+	if (!geometry.contains(point)) {
+		return {};
+	}
+	return TextState(_parent, _data->uploading()
+		? _cancell
+		: _data->loaded()
+		? _openl
+		: _data->loading()
+		? _cancell
+		: _savel);
+}
+
+bool HistoryVideo::uploading() const {
+	return _data->uploading();
+}
+
+float64 HistoryVideo::dataProgress() const {
+	return _data->progress();
+}
+
+bool HistoryVideo::dataFinished() const {
+	return !_data->loading()
+		&& (!_data->uploading() || _data->waitingForAlbum());
+}
+
+bool HistoryVideo::dataLoaded() const {
+	return _data->loaded();
+}
+
+void HistoryVideo::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 image = useGood ? good : _data->thumb.get();
+	if (good && !useGood) {
+		good->load({});
+	}
+
+	const auto loaded = useGood ? true : _data->thumb->loaded();
+	const auto loadLevel = loaded ? 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 originalWidth = ConvertScale(_data->thumb->width());
+	const auto originalHeight = ConvertScale(_data->thumb->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->pixNoCache(_realParent->fullId(), pixWidth, pixHeight, options, width, height);
+}
+
+void HistoryVideo::setStatusSize(int newSize) const {
+	HistoryFileMedia::setStatusSize(newSize, _data->size, _data->duration(), 0);
+}
+
+TextWithEntities HistoryVideo::selectedText(TextSelection selection) const {
+	return _caption.originalTextWithEntities(selection, ExpandLinksAll);
+}
+
+bool HistoryVideo::needsBubble() const {
+	if (!_caption.isEmpty()) {
+		return true;
+	}
+	const auto item = _parent->data();
+	return item->viaBot()
+		|| item->Has<HistoryMessageReply>()
+		|| _parent->displayForwardedFrom()
+		|| _parent->displayFromName();
+	return false;
+}
+
+void HistoryVideo::parentTextUpdated() {
+	_caption = (_parent->media() == this)
+		? createCaption(_parent->data())
+		: Text();
+	Auth().data().requestViewResize(_parent);
+}
+
+void HistoryVideo::updateStatusText() const {
+	auto showPause = false;
+	auto statusSize = 0;
+	auto realDuration = 0;
+	if (_data->status == FileDownloadFailed || _data->status == FileUploadFailed) {
+		statusSize = FileStatusSizeFailed;
+	} else if (_data->uploading()) {
+		statusSize = _data->uploadingData->offset;
+	} else if (_data->loading()) {
+		statusSize = _data->loadOffset();
+	} else if (_data->loaded()) {
+		statusSize = FileStatusSizeLoaded;
+	} else {
+		statusSize = FileStatusSizeReady;
+	}
+	if (statusSize != _statusSize) {
+		setStatusSize(statusSize);
+	}
+}
diff --git a/Telegram/SourceFiles/history/media/history_media_video.h b/Telegram/SourceFiles/history/media/history_media_video.h
new file mode 100644
index 000000000..34e463aa3
--- /dev/null
+++ b/Telegram/SourceFiles/history/media/history_media_video.h
@@ -0,0 +1,96 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+#include "history/media/history_media_file.h"
+
+class HistoryVideo : public HistoryFileMedia {
+public:
+	HistoryVideo(
+		not_null<Element*> parent,
+		not_null<HistoryItem*> realParent,
+		not_null<DocumentData*> document);
+
+	HistoryMediaType type() const override {
+		return MediaTypeVideo;
+	}
+
+	void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override;
+	TextState textState(QPoint point, StateRequest request) const override;
+
+	[[nodiscard]] TextSelection adjustSelection(
+			TextSelection selection,
+			TextSelectType type) const override {
+		return _caption.adjustSelection(selection, type);
+	}
+	uint16 fullSelectionLength() const override {
+		return _caption.length();
+	}
+	bool hasTextForCopy() const override {
+		return !_caption.isEmpty();
+	}
+
+	TextWithEntities selectedText(TextSelection selection) const override;
+
+	DocumentData *getDocument() const override {
+		return _data;
+	}
+
+	QSize sizeForGrouping() const override;
+	void drawGrouped(
+		Painter &p,
+		const QRect &clip,
+		TextSelection selection,
+		TimeMs 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;
+
+	bool uploading() const override;
+
+	TextWithEntities getCaption() const override {
+		return _caption.originalTextWithEntities();
+	}
+	bool needsBubble() const override;
+	bool customInfoLayout() const override {
+		return _caption.isEmpty();
+	}
+	bool skipBubbleTail() const override {
+		return isBubbleBottom() && _caption.isEmpty();
+	}
+
+	void parentTextUpdated() override;
+
+protected:
+	float64 dataProgress() const override;
+	bool dataFinished() const override;
+	bool dataLoaded() const override;
+
+private:
+	QSize countOptimalSize() override;
+	QSize countCurrentSize(int newWidth) override;
+
+	void validateGroupedCache(
+		const QRect &geometry,
+		RectParts corners,
+		not_null<uint64*> cacheKey,
+		not_null<QPixmap*> cache) const;
+	void setStatusSize(int newSize) const;
+	void updateStatusText() const;
+
+	not_null<DocumentData*> _data;
+	int _thumbw = 1;
+	int _thumbh = 1;
+	Text _caption;
+
+};
diff --git a/Telegram/SourceFiles/history/media/history_media_web_page.cpp b/Telegram/SourceFiles/history/media/history_media_web_page.cpp
new file mode 100644
index 000000000..6c0f44ba9
--- /dev/null
+++ b/Telegram/SourceFiles/history/media/history_media_web_page.cpp
@@ -0,0 +1,696 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "history/media/history_media_web_page.h"
+
+#include "layout.h"
+#include "auth_session.h"
+#include "core/click_handler_types.h"
+#include "history/history_item_components.h"
+#include "history/history_item.h"
+#include "history/view/history_view_element.h"
+#include "history/view/history_view_cursor_state.h"
+#include "history/media/history_media_common.h"
+#include "ui/image/image.h"
+#include "ui/text_options.h"
+#include "data/data_session.h"
+#include "data/data_media_types.h"
+#include "data/data_web_page.h"
+#include "data/data_photo.h"
+#include "styles/style_history.h"
+
+namespace {
+
+using TextState = HistoryView::TextState;
+
+} // namespace
+
+namespace {
+
+constexpr auto kMaxOriginalEntryLines = 8192;
+
+int articleThumbWidth(PhotoData *thumb, int height) {
+	auto w = thumb->medium->width();
+	auto h = thumb->medium->height();
+	return qMax(qMin(height * w / h, height), 1);
+}
+
+int articleThumbHeight(PhotoData *thumb, int width) {
+	return qMax(thumb->medium->height() * width / thumb->medium->width(), 1);
+}
+
+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
+
+HistoryWebPage::HistoryWebPage(
+	not_null<Element*> parent,
+	not_null<WebPageData*> data)
+: HistoryMedia(parent)
+, _data(data)
+, _title(st::msgMinWidth - st::webPageLeft)
+, _description(st::msgMinWidth - st::webPageLeft) {
+	Auth().data().registerWebPageView(_data, _parent);
+}
+
+QSize HistoryWebPage::countOptimalSize() {
+	if (_data->pendingTill) {
+		return { 0, 0 };
+	}
+	const auto versionChanged = (_dataVersion != _data->version);
+	if (versionChanged) {
+		_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;
+	}
+	auto lineHeight = unitedLineHeight();
+
+	if (!_openl && !_data->url.isEmpty()) {
+		const auto previewOfHiddenUrl = [&] {
+			const auto simplify = [](const QString &url) {
+				auto result = url.toLower();
+				if (result.endsWith('/')) {
+					result.chop(1);
+				}
+				const auto prefixes = { qstr("http://"), qstr("https://") };
+				for (const auto &prefix : prefixes) {
+					if (result.startsWith(prefix)) {
+						result = result.mid(prefix.size());
+						break;
+					}
+				}
+				return result;
+			};
+			const auto simplified = simplify(_data->url);
+			const auto full = _parent->data()->originalText();
+			for (const auto &entity : full.entities) {
+				if (entity.type() != EntityInTextUrl) {
+					continue;
+				}
+				const auto link = full.text.mid(
+					entity.offset(),
+					entity.length());
+				if (simplify(link) == simplified) {
+					return false;
+				}
+			}
+			return true;
+		}();
+		_openl = previewOfHiddenUrl
+			? std::make_shared<HiddenUrlClickHandler>(_data->url)
+			: std::make_shared<UrlClickHandler>(_data->url, true);
+	}
+
+	// init layout
+	auto title = TextUtilities::SingleLine(_data->title.isEmpty()
+		? _data->author
+		: _data->title);
+	if (!_collage.empty()) {
+		_asArticle = false;
+	} else if (!_data->document
+		&& _data->photo
+		&& _data->type != WebPageType::Photo
+		&& _data->type != WebPageType::Video) {
+		if (_data->type == WebPageType::Profile) {
+			_asArticle = true;
+		} else if (_data->siteName == qstr("Twitter")
+			|| _data->siteName == qstr("Facebook")
+			|| _data->type == WebPageType::ArticleWithIV) {
+			_asArticle = false;
+		} else {
+			_asArticle = true;
+		}
+		if (_asArticle
+			&& _data->description.text.isEmpty()
+			&& title.isEmpty()
+			&& _data->siteName.isEmpty()) {
+			_asArticle = false;
+		}
+	} else {
+		_asArticle = false;
+	}
+
+	// init attach
+	if (!_attach && !_asArticle) {
+		_attach = CreateAttach(
+			_parent,
+			_data->document,
+			_data->photo,
+			_collage);
+	}
+
+	auto textFloatsAroundInfo = !_asArticle && !_attach && isBubbleBottom();
+
+	// init strings
+	if (_description.isEmpty() && !_data->description.text.isEmpty()) {
+		auto text = _data->description;
+
+		if (textFloatsAroundInfo) {
+			text.text += _parent->skipBlock();
+		}
+		if (isLogEntryOriginal()) {
+			// Fix layout for small bubbles (narrow media caption edit log entries).
+			_description = Text(st::minPhotoSize
+				- st::msgPadding.left()
+				- st::msgPadding.right()
+				- st::webPageLeft);
+		}
+		_description.setMarkedText(
+			st::webPageDescriptionStyle,
+			text,
+			Ui::WebpageTextDescriptionOptions(_data->siteName));
+	}
+	if (_title.isEmpty() && !title.isEmpty()) {
+		if (textFloatsAroundInfo && _description.isEmpty()) {
+			title += _parent->skipBlock();
+		}
+		_title.setText(
+			st::webPageTitleStyle,
+			title,
+			Ui::WebpageTextTitleOptions());
+	}
+	if (!_siteNameWidth && !_data->siteName.isEmpty()) {
+		_siteNameWidth = st::webPageTitleFont->width(_data->siteName);
+	}
+
+	// init dimensions
+	auto l = st::msgPadding.left() + st::webPageLeft, r = st::msgPadding.right();
+	auto skipBlockWidth = _parent->skipBlockWidth();
+	auto maxWidth = skipBlockWidth;
+	auto minHeight = 0;
+
+	auto siteNameHeight = _data->siteName.isEmpty() ? 0 : lineHeight;
+	auto titleMinHeight = _title.isEmpty() ? 0 : lineHeight;
+	auto descMaxLines = isLogEntryOriginal() ? kMaxOriginalEntryLines : (3 + (siteNameHeight ? 0 : 1) + (titleMinHeight ? 0 : 1));
+	auto descriptionMinHeight = _description.isEmpty() ? 0 : qMin(_description.minHeight(), descMaxLines * lineHeight);
+	auto articleMinHeight = siteNameHeight + titleMinHeight + descriptionMinHeight;
+	auto articlePhotoMaxWidth = 0;
+	if (_asArticle) {
+		articlePhotoMaxWidth = st::webPagePhotoDelta + qMax(articleThumbWidth(_data->photo, articleMinHeight), lineHeight);
+	}
+
+	if (_siteNameWidth) {
+		if (_title.isEmpty() && _description.isEmpty()) {
+			accumulate_max(maxWidth, _siteNameWidth + _parent->skipBlockWidth());
+		} else {
+			accumulate_max(maxWidth, _siteNameWidth + articlePhotoMaxWidth);
+		}
+		minHeight += lineHeight;
+	}
+	if (!_title.isEmpty()) {
+		accumulate_max(maxWidth, _title.maxWidth() + articlePhotoMaxWidth);
+		minHeight += titleMinHeight;
+	}
+	if (!_description.isEmpty()) {
+		accumulate_max(maxWidth, _description.maxWidth() + articlePhotoMaxWidth);
+		minHeight += descriptionMinHeight;
+	}
+	if (_attach) {
+		auto attachAtTop = !_siteNameWidth && _title.isEmpty() && _description.isEmpty();
+		if (!attachAtTop) minHeight += st::mediaInBubbleSkip;
+
+		_attach->initDimensions();
+		auto bubble = _attach->bubbleMargins();
+		auto maxMediaWidth = _attach->maxWidth() - bubble.left() - bubble.right();
+		if (isBubbleBottom() && _attach->customInfoLayout()) {
+			maxMediaWidth += skipBlockWidth;
+		}
+		accumulate_max(maxWidth, maxMediaWidth);
+		minHeight += _attach->minHeight() - bubble.top() - bubble.bottom();
+		if (!_attach->additionalInfoString().isEmpty()) {
+			minHeight += bottomInfoPadding();
+		}
+	}
+	if (_data->type == WebPageType::Video && _data->duration) {
+		_duration = formatDurationText(_data->duration);
+		_durationWidth = st::msgDateFont->width(_duration);
+	}
+	maxWidth += st::msgPadding.left() + st::webPageLeft + st::msgPadding.right();
+	auto padding = inBubblePadding();
+	minHeight += padding.top() + padding.bottom();
+
+	if (_asArticle) {
+		minHeight = resizeGetHeight(maxWidth);
+	}
+	return { maxWidth, minHeight };
+}
+
+QSize HistoryWebPage::countCurrentSize(int newWidth) {
+	if (_data->pendingTill) {
+		return { newWidth, minHeight() };
+	}
+
+	auto innerWidth = newWidth - st::msgPadding.left() - st::webPageLeft - st::msgPadding.right();
+	auto newHeight = 0;
+
+	auto lineHeight = unitedLineHeight();
+	auto linesMax = isLogEntryOriginal() ? kMaxOriginalEntryLines : 5;
+	auto siteNameLines = _siteNameWidth ? 1 : 0;
+	auto siteNameHeight = _siteNameWidth ? lineHeight : 0;
+	if (_asArticle) {
+		_pixh = linesMax * lineHeight;
+		do {
+			_pixw = articleThumbWidth(_data->photo, _pixh);
+			auto wleft = innerWidth - st::webPagePhotoDelta - qMax(_pixw, lineHeight);
+
+			newHeight = siteNameHeight;
+
+			if (_title.isEmpty()) {
+				_titleLines = 0;
+			} else {
+				if (_title.countHeight(wleft) < 2 * st::webPageTitleFont->height) {
+					_titleLines = 1;
+				} else {
+					_titleLines = 2;
+				}
+				newHeight += _titleLines * lineHeight;
+			}
+
+			auto descriptionHeight = _description.countHeight(wleft);
+			if (descriptionHeight < (linesMax - siteNameLines - _titleLines) * st::webPageDescriptionFont->height) {
+				// We have height for all the lines.
+				_descriptionLines = -1;
+				newHeight += descriptionHeight;
+			} else {
+				_descriptionLines = (linesMax - siteNameLines - _titleLines);
+				newHeight += _descriptionLines * lineHeight;
+			}
+
+			if (newHeight >= _pixh) {
+				break;
+			}
+
+			_pixh -= lineHeight;
+		} while (_pixh > lineHeight);
+		newHeight += bottomInfoPadding();
+	} else {
+		newHeight = siteNameHeight;
+
+		if (_title.isEmpty()) {
+			_titleLines = 0;
+		} else {
+			if (_title.countHeight(innerWidth) < 2 * st::webPageTitleFont->height) {
+				_titleLines = 1;
+			} else {
+				_titleLines = 2;
+			}
+			newHeight += _titleLines * lineHeight;
+		}
+
+		if (_description.isEmpty()) {
+			_descriptionLines = 0;
+		} else {
+			auto descriptionHeight = _description.countHeight(innerWidth);
+			if (descriptionHeight < (linesMax - siteNameLines - _titleLines) * st::webPageDescriptionFont->height) {
+				// We have height for all the lines.
+				_descriptionLines = -1;
+				newHeight += descriptionHeight;
+			} else {
+				_descriptionLines = (linesMax - siteNameLines - _titleLines);
+				newHeight += _descriptionLines * lineHeight;
+			}
+		}
+
+		if (_attach) {
+			auto attachAtTop = !_siteNameWidth && !_titleLines && !_descriptionLines;
+			if (!attachAtTop) newHeight += st::mediaInBubbleSkip;
+
+			auto bubble = _attach->bubbleMargins();
+
+			_attach->resizeGetHeight(innerWidth + bubble.left() + bubble.right());
+			newHeight += _attach->height() - bubble.top() - bubble.bottom();
+			if (!_attach->additionalInfoString().isEmpty()) {
+				newHeight += bottomInfoPadding();
+			} else if (isBubbleBottom() && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > innerWidth + bubble.left() + bubble.right()) {
+				newHeight += bottomInfoPadding();
+			}
+		}
+	}
+	auto padding = inBubblePadding();
+	newHeight += padding.top() + padding.bottom();
+
+	return { newWidth, newHeight };
+}
+
+TextSelection HistoryWebPage::toDescriptionSelection(
+		TextSelection selection) const {
+	return HistoryView::UnshiftItemSelection(selection, _title);
+}
+
+TextSelection HistoryWebPage::fromDescriptionSelection(
+		TextSelection selection) const {
+	return HistoryView::ShiftItemSelection(selection, _title);
+}
+
+void HistoryWebPage::refreshParentId(not_null<HistoryItem*> realParent) {
+	if (_attach) {
+		_attach->refreshParentId(realParent);
+	}
+}
+
+void HistoryWebPage::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const {
+	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
+	auto paintx = 0, painty = 0, paintw = width(), painth = height();
+
+	auto outbg = _parent->hasOutLayout();
+	bool selected = (selection == FullSelection);
+
+	auto &barfg = selected ? (outbg ? st::msgOutReplyBarSelColor : st::msgInReplyBarSelColor) : (outbg ? st::msgOutReplyBarColor : st::msgInReplyBarColor);
+	auto &semibold = selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg);
+	auto &regular = selected ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) : (outbg ? st::msgOutDateFg : st::msgInDateFg);
+
+	QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins());
+	auto padding = inBubblePadding();
+	auto tshift = padding.top();
+	auto bshift = padding.bottom();
+	paintw -= padding.left() + padding.right();
+	auto attachAdditionalInfoText = _attach ? _attach->additionalInfoString() : QString();
+	if (_asArticle) {
+		bshift += bottomInfoPadding();
+	} else if (!attachAdditionalInfoText.isEmpty()) {
+		bshift += bottomInfoPadding();
+	} else if (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > paintw + bubble.left() + bubble.right()) {
+		bshift += bottomInfoPadding();
+	}
+
+	QRect bar(rtlrect(st::msgPadding.left(), tshift, st::webPageBar, height() - tshift - bshift, width()));
+	p.fillRect(bar, barfg);
+
+	auto lineHeight = unitedLineHeight();
+	if (_asArticle) {
+		const auto contextId = _parent->data()->fullId();
+		_data->photo->medium->load(contextId, false, false);
+		bool full = _data->photo->medium->loaded();
+		QPixmap pix;
+		auto pw = qMax(_pixw, lineHeight);
+		auto ph = _pixh;
+		auto pixw = _pixw, pixh = articleThumbHeight(_data->photo, _pixw);
+		auto maxw = ConvertScale(_data->photo->medium->width()), maxh = ConvertScale(_data->photo->medium->height());
+		if (pixw * ph != pixh * pw) {
+			float64 coef = (pixw * ph > pixh * pw) ? qMin(ph / float64(pixh), maxh / float64(pixh)) : qMin(pw / float64(pixw), maxw / float64(pixw));
+			pixh = qRound(pixh * coef);
+			pixw = qRound(pixw * coef);
+		}
+		if (full) {
+			pix = _data->photo->medium->pixSingle(contextId, pixw, pixh, pw, ph, ImageRoundRadius::Small);
+		} else {
+			pix = _data->photo->thumb->pixBlurredSingle(contextId, pixw, pixh, pw, ph, ImageRoundRadius::Small);
+		}
+		p.drawPixmapLeft(padding.left() + paintw - pw, tshift, width(), pix);
+		if (selected) {
+			App::roundRect(p, rtlrect(padding.left() + paintw - pw, tshift, pw, _pixh, width()), p.textPalette().selectOverlay, SelectedOverlaySmallCorners);
+		}
+		paintw -= pw + st::webPagePhotoDelta;
+	}
+	if (_siteNameWidth) {
+		p.setFont(st::webPageTitleFont);
+		p.setPen(semibold);
+		p.drawTextLeft(padding.left(), tshift, width(), (paintw >= _siteNameWidth) ? _data->siteName : st::webPageTitleFont->elided(_data->siteName, paintw));
+		tshift += lineHeight;
+	}
+	if (_titleLines) {
+		p.setPen(outbg ? st::webPageTitleOutFg : st::webPageTitleInFg);
+		auto endskip = 0;
+		if (_title.hasSkipBlock()) {
+			endskip = _parent->skipBlockWidth();
+		}
+		_title.drawLeftElided(p, padding.left(), tshift, paintw, width(), _titleLines, style::al_left, 0, -1, endskip, false, selection);
+		tshift += _titleLines * lineHeight;
+	}
+	if (_descriptionLines) {
+		p.setPen(outbg ? st::webPageDescriptionOutFg : st::webPageDescriptionInFg);
+		auto endskip = 0;
+		if (_description.hasSkipBlock()) {
+			endskip = _parent->skipBlockWidth();
+		}
+		if (_descriptionLines > 0) {
+			_description.drawLeftElided(p, padding.left(), tshift, paintw, width(), _descriptionLines, style::al_left, 0, -1, endskip, false, toDescriptionSelection(selection));
+			tshift += _descriptionLines * lineHeight;
+		} else {
+			_description.drawLeft(p, padding.left(), tshift, paintw, width(), style::al_left, 0, -1, toDescriptionSelection(selection));
+			tshift += _description.countHeight(paintw);
+		}
+	}
+	if (_attach) {
+		auto attachAtTop = !_siteNameWidth && !_titleLines && !_descriptionLines;
+		if (!attachAtTop) tshift += st::mediaInBubbleSkip;
+
+		auto attachLeft = padding.left() - bubble.left();
+		auto attachTop = tshift - bubble.top();
+		if (rtl()) attachLeft = width() - attachLeft - _attach->width();
+
+		p.translate(attachLeft, attachTop);
+
+		auto attachSelection = selected ? FullSelection : TextSelection { 0, 0 };
+		_attach->draw(p, r.translated(-attachLeft, -attachTop), attachSelection, ms);
+		auto pixwidth = _attach->width();
+		auto pixheight = _attach->height();
+
+		if (_data->type == WebPageType::Video
+			&& _attach->type() == MediaTypePhoto) {
+			if (_attach->isReadyForOpen()) {
+				if (_data->siteName == qstr("YouTube")) {
+					st::youtubeIcon.paint(p, (pixwidth - st::youtubeIcon.width()) / 2, (pixheight - st::youtubeIcon.height()) / 2, width());
+				} else {
+					st::videoIcon.paint(p, (pixwidth - st::videoIcon.width()) / 2, (pixheight - st::videoIcon.height()) / 2, width());
+				}
+			}
+			if (_durationWidth) {
+				auto dateX = pixwidth - _durationWidth - st::msgDateImgDelta - 2 * st::msgDateImgPadding.x();
+				auto dateY = pixheight - st::msgDateFont->height - 2 * st::msgDateImgPadding.y() - st::msgDateImgDelta;
+				auto dateW = pixwidth - dateX - st::msgDateImgDelta;
+				auto dateH = pixheight - dateY - st::msgDateImgDelta;
+
+				App::roundRect(p, dateX, dateY, dateW, dateH, selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners);
+
+				p.setFont(st::msgDateFont);
+				p.setPen(st::msgDateImgFg);
+				p.drawTextLeft(dateX + st::msgDateImgPadding.x(), dateY + st::msgDateImgPadding.y(), pixwidth, _duration);
+			}
+		}
+
+		p.translate(-attachLeft, -attachTop);
+
+		if (!attachAdditionalInfoText.isEmpty()) {
+			p.setFont(st::msgDateFont);
+			p.setPen(selected ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) : (outbg ? st::msgOutDateFg : st::msgInDateFg));
+			p.drawTextLeft(st::msgPadding.left(), bar.y() + bar.height() + st::mediaInBubbleSkip, width(), attachAdditionalInfoText);
+		}
+	}
+}
+
+TextState HistoryWebPage::textState(QPoint point, StateRequest request) const {
+	auto result = TextState(_parent);
+
+	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) {
+		return result;
+	}
+	auto paintx = 0, painty = 0, paintw = width(), painth = height();
+
+	QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins());
+	auto padding = inBubblePadding();
+	auto tshift = padding.top();
+	auto bshift = padding.bottom();
+	if (_asArticle || (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > paintw + bubble.left() + bubble.right())) {
+		bshift += bottomInfoPadding();
+	}
+	paintw -= padding.left() + padding.right();
+
+	auto lineHeight = unitedLineHeight();
+	auto inThumb = false;
+	if (_asArticle) {
+		auto pw = qMax(_pixw, lineHeight);
+		if (rtlrect(padding.left() + paintw - pw, 0, pw, _pixh, width()).contains(point)) {
+			inThumb = true;
+		}
+		paintw -= pw + st::webPagePhotoDelta;
+	}
+	int symbolAdd = 0;
+	if (_siteNameWidth) {
+		tshift += lineHeight;
+	}
+	if (_titleLines) {
+		if (point.y() >= tshift && point.y() < tshift + _titleLines * lineHeight) {
+			Text::StateRequestElided titleRequest = request.forText();
+			titleRequest.lines = _titleLines;
+			result = TextState(_parent, _title.getStateElidedLeft(
+				point - QPoint(padding.left(), tshift),
+				paintw,
+				width(),
+				titleRequest));
+		} else if (point.y() >= tshift + _titleLines * lineHeight) {
+			symbolAdd += _title.length();
+		}
+		tshift += _titleLines * lineHeight;
+	}
+	if (_descriptionLines) {
+		auto descriptionHeight = (_descriptionLines > 0) ? _descriptionLines * lineHeight : _description.countHeight(paintw);
+		if (point.y() >= tshift && point.y() < tshift + descriptionHeight) {
+			if (_descriptionLines > 0) {
+				Text::StateRequestElided descriptionRequest = request.forText();
+				descriptionRequest.lines = _descriptionLines;
+				result = TextState(_parent, _description.getStateElidedLeft(
+					point - QPoint(padding.left(), tshift),
+					paintw,
+					width(),
+					descriptionRequest));
+			} else {
+				result = TextState(_parent, _description.getStateLeft(
+					point - QPoint(padding.left(), tshift),
+					paintw,
+					width(),
+					request.forText()));
+			}
+		} else if (point.y() >= tshift + descriptionHeight) {
+			symbolAdd += _description.length();
+		}
+		tshift += descriptionHeight;
+	}
+	if (inThumb) {
+		result.link = _openl;
+	} else if (_attach) {
+		auto attachAtTop = !_siteNameWidth && !_titleLines && !_descriptionLines;
+		if (!attachAtTop) tshift += st::mediaInBubbleSkip;
+
+		if (QRect(padding.left(), tshift, paintw, height() - tshift - bshift).contains(point)) {
+			auto attachLeft = padding.left() - bubble.left();
+			auto attachTop = tshift - bubble.top();
+			if (rtl()) attachLeft = width() - attachLeft - _attach->width();
+			result = _attach->textState(point - QPoint(attachLeft, attachTop), request);
+
+			if (result.link && !_data->document && _data->photo && _collage.empty() && _attach->isReadyForOpen()) {
+				if (_data->type == WebPageType::Profile
+					|| _data->type == WebPageType::Video) {
+					result.link = _openl;
+				} else if (_data->type == WebPageType::Photo
+					|| _data->siteName == qstr("Twitter")
+					|| _data->siteName == qstr("Facebook")) {
+					// leave photo link
+				} else {
+					result.link = _openl;
+				}
+			}
+		}
+	}
+
+	result.symbol += symbolAdd;
+	return result;
+}
+
+TextSelection HistoryWebPage::adjustSelection(TextSelection selection, TextSelectType type) const {
+	if (!_descriptionLines || selection.to <= _title.length()) {
+		return _title.adjustSelection(selection, type);
+	}
+	auto descriptionSelection = _description.adjustSelection(toDescriptionSelection(selection), type);
+	if (selection.from >= _title.length()) {
+		return fromDescriptionSelection(descriptionSelection);
+	}
+	auto titleSelection = _title.adjustSelection(selection, type);
+	return { titleSelection.from, fromDescriptionSelection(descriptionSelection).to };
+}
+
+void HistoryWebPage::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
+	if (_attach) {
+		_attach->clickHandlerActiveChanged(p, active);
+	}
+}
+
+void HistoryWebPage::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) {
+	if (_attach) {
+		_attach->clickHandlerPressedChanged(p, pressed);
+	}
+}
+
+void HistoryWebPage::playAnimation(bool autoplay) {
+	if (_attach) {
+		if (autoplay) {
+			_attach->autoplayAnimation();
+		} else {
+			_attach->playAnimation();
+		}
+	}
+}
+
+bool HistoryWebPage::isDisplayed() const {
+	const auto item = _parent->data();
+	return !_data->pendingTill
+		&& !item->Has<HistoryMessageLogEntryOriginal>();
+}
+
+TextWithEntities HistoryWebPage::selectedText(TextSelection selection) const {
+	auto titleResult = _title.originalTextWithEntities(
+		selection,
+		ExpandLinksAll);
+	auto descriptionResult = _description.originalTextWithEntities(
+		toDescriptionSelection(selection),
+		ExpandLinksAll);
+	if (titleResult.text.isEmpty()) {
+		return descriptionResult;
+	} else if (descriptionResult.text.isEmpty()) {
+		return titleResult;
+	}
+
+	titleResult.text += '\n';
+	TextUtilities::Append(titleResult, std::move(descriptionResult));
+	return titleResult;
+}
+
+QMargins HistoryWebPage::inBubblePadding() const {
+	auto lshift = st::msgPadding.left() + st::webPageLeft;
+	auto rshift = st::msgPadding.right();
+	auto bshift = isBubbleBottom() ? st::msgPadding.left() : st::mediaInBubbleSkip;
+	auto tshift = isBubbleTop() ? st::msgPadding.left() : st::mediaInBubbleSkip;
+	return QMargins(lshift, tshift, rshift, bshift);
+}
+
+bool HistoryWebPage::isLogEntryOriginal() const {
+	return _parent->data()->isLogEntry() && _parent->media() != this;
+}
+
+int HistoryWebPage::bottomInfoPadding() const {
+	if (!isBubbleBottom()) return 0;
+
+	auto result = st::msgDateFont->height;
+
+	// We use padding greater than st::msgPadding.bottom() in the
+	// bottom of the bubble so that the left line looks pretty.
+	// but if we have bottom skip because of the info display
+	// we don't need that additional padding so we replace it
+	// back with st::msgPadding.bottom() instead of left().
+	result += st::msgPadding.bottom() - st::msgPadding.left();
+	return result;
+}
+
+HistoryWebPage::~HistoryWebPage() {
+	Auth().data().unregisterWebPageView(_data, _parent);
+}
diff --git a/Telegram/SourceFiles/history/media/history_media_web_page.h b/Telegram/SourceFiles/history/media/history_media_web_page.h
new file mode 100644
index 000000000..8c91d034d
--- /dev/null
+++ b/Telegram/SourceFiles/history/media/history_media_web_page.h
@@ -0,0 +1,118 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+#include "history/media/history_media.h"
+
+namespace Data {
+class Media;
+} // namespace Data
+
+class HistoryWebPage : public HistoryMedia {
+public:
+	HistoryWebPage(
+		not_null<Element*> parent,
+		not_null<WebPageData*> data);
+
+	HistoryMediaType type() const override {
+		return MediaTypeWebPage;
+	}
+
+	void refreshParentId(not_null<HistoryItem*> realParent) override;
+
+	void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override;
+	TextState textState(QPoint point, StateRequest request) const override;
+
+	bool hideMessageText() const override {
+		return false;
+	}
+
+	[[nodiscard]] TextSelection adjustSelection(
+		TextSelection selection,
+		TextSelectType type) const override;
+	uint16 fullSelectionLength() const override {
+		return _title.length() + _description.length();
+	}
+	bool hasTextForCopy() const override {
+		return false; // we do not add _title and _description in FullSelection text copy.
+	}
+
+	bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override {
+		return _attach && _attach->toggleSelectionByHandlerClick(p);
+	}
+	bool dragItemByHandler(const ClickHandlerPtr &p) const override {
+		return _attach && _attach->dragItemByHandler(p);
+	}
+
+	TextWithEntities selectedText(TextSelection selection) const override;
+
+	void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
+	void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
+
+	bool isDisplayed() const override;
+	PhotoData *getPhoto() const override {
+		return _attach ? _attach->getPhoto() : nullptr;
+	}
+	DocumentData *getDocument() const override {
+		return _attach ? _attach->getDocument() : nullptr;
+	}
+	void stopAnimation() override {
+		if (_attach) _attach->stopAnimation();
+	}
+
+	not_null<WebPageData*> webpage() {
+		return _data;
+	}
+
+	bool needsBubble() const override {
+		return true;
+	}
+	bool customInfoLayout() const override {
+		return false;
+	}
+	bool allowsFastShare() const override {
+		return true;
+	}
+
+	HistoryMedia *attach() const {
+		return _attach.get();
+	}
+
+	~HistoryWebPage();
+
+private:
+	void playAnimation(bool autoplay) override;
+	QSize countOptimalSize() override;
+	QSize countCurrentSize(int newWidth) override;
+
+	TextSelection toDescriptionSelection(TextSelection selection) const;
+	TextSelection fromDescriptionSelection(TextSelection selection) const;
+	QMargins inBubblePadding() const;
+	int bottomInfoPadding() const;
+	bool isLogEntryOriginal() const;
+
+	not_null<WebPageData*> _data;
+	std::vector<std::unique_ptr<Data::Media>> _collage;
+	ClickHandlerPtr _openl;
+	std::unique_ptr<HistoryMedia> _attach;
+
+	bool _asArticle = false;
+	int _dataVersion = -1;
+	int _titleLines = 0;
+	int _descriptionLines = 0;
+
+	Text _title, _description;
+	int _siteNameWidth = 0;
+
+	QString _duration;
+	int _durationWidth = 0;
+
+	int _pixw = 0;
+	int _pixh = 0;
+
+};
diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp
index e8e0e6817..351d80fd1 100644
--- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp
@@ -13,7 +13,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "history/history_item.h"
 #include "history/history_message.h"
 #include "history/history_item_text.h"
-#include "history/history_media_types.h"
+#include "history/media/history_media.h"
+#include "history/media/history_media_web_page.h"
 #include "ui/widgets/popup_menu.h"
 #include "ui/image/image.h"
 #include "chat_helpers/message_field.h"
diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp
index e764dc8af..00798a575 100644
--- a/Telegram/SourceFiles/history/view/history_view_element.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_element.cpp
@@ -10,8 +10,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "history/view/history_view_service_message.h"
 #include "history/history_item_components.h"
 #include "history/history_item.h"
-#include "history/history_media.h"
-#include "history/history_media_grouped.h"
+#include "history/media/history_media.h"
+#include "history/media/history_media_grouped.h"
 #include "history/history.h"
 #include "data/data_session.h"
 #include "data/data_groups.h"
diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
index 73a9d3c5c..ef8460c78 100644
--- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
@@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "history/view/history_view_list_widget.h"
 
-#include "history/history_media_types.h"
+#include "history/media/history_media.h"
 #include "history/history_message.h"
 #include "history/history_item_components.h"
 #include "history/history_item_text.h"
@@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_session.h"
 #include "data/data_feed.h"
 #include "data/data_media_types.h"
+#include "data/data_document.h"
 #include "styles/style_history.h"
 
 namespace HistoryView {
diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp
index 0f12ce8b5..aa2108005 100644
--- a/Telegram/SourceFiles/history/view/history_view_message.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_message.cpp
@@ -10,8 +10,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "history/view/history_view_cursor_state.h"
 #include "history/history_item_components.h"
 #include "history/history_message.h"
-#include "history/history_media_types.h"
-#include "history/history_media.h"
+#include "history/media/history_media.h"
+#include "history/media/history_media_web_page.h"
 #include "history/history.h"
 #include "data/data_session.h"
 #include "lang/lang_keys.h"
diff --git a/Telegram/SourceFiles/history/view/history_view_service_message.cpp b/Telegram/SourceFiles/history/view/history_view_service_message.cpp
index 397953068..4605cf3eb 100644
--- a/Telegram/SourceFiles/history/view/history_view_service_message.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_service_message.cpp
@@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 #include "history/history.h"
 #include "history/history_service.h"
-#include "history/history_media.h"
+#include "history/media/history_media.h"
 #include "history/history_item_components.h"
 #include "history/view/history_view_cursor_state.h"
 #include "data/data_abstract_structure.h"
diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp
index 6f8a2c29b..dc43080ff 100644
--- a/Telegram/SourceFiles/mainwidget.cpp
+++ b/Telegram/SourceFiles/mainwidget.cpp
@@ -43,7 +43,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "history/history.h"
 #include "history/history_widget.h"
 #include "history/history_message.h"
-#include "history/history_media.h"
+#include "history/media/history_media.h"
 #include "history/view/history_view_service_message.h"
 #include "history/view/history_view_element.h"
 #include "lang/lang_keys.h"
diff --git a/Telegram/SourceFiles/media/player/media_player_float.cpp b/Telegram/SourceFiles/media/player/media_player_float.cpp
index e8eb4375b..d232909c2 100644
--- a/Telegram/SourceFiles/media/player/media_player_float.cpp
+++ b/Telegram/SourceFiles/media/player/media_player_float.cpp
@@ -11,7 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_document.h"
 #include "data/data_session.h"
 #include "data/data_media_types.h"
-#include "history/history_media.h"
+#include "history/media/history_media.h"
 #include "history/history_item.h"
 #include "history/view/history_view_element.h"
 #include "media/media_clip_reader.h"
diff --git a/Telegram/SourceFiles/media/view/media_view_group_thumbs.cpp b/Telegram/SourceFiles/media/view/media_view_group_thumbs.cpp
index bccb729f4..93626d00e 100644
--- a/Telegram/SourceFiles/media/view/media_view_group_thumbs.cpp
+++ b/Telegram/SourceFiles/media/view/media_view_group_thumbs.cpp
@@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_session.h"
 #include "data/data_web_page.h"
 #include "history/history.h"
-#include "history/history_media.h"
+#include "history/media/history_media.h"
 #include "ui/image/image.h"
 #include "auth_session.h"
 #include "styles/style_mediaview.h"
diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp
index bd20258d4..0748b0d8b 100644
--- a/Telegram/SourceFiles/mediaview.cpp
+++ b/Telegram/SourceFiles/mediaview.cpp
@@ -23,7 +23,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "media/media_audio.h"
 #include "history/history.h"
 #include "history/history_message.h"
-#include "history/history_media_types.h"
 #include "data/data_media_types.h"
 #include "data/data_session.h"
 #include "window/themes/window_theme_preview.h"
diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt
index e09acab86..1f100825c 100644
--- a/Telegram/gyp/telegram_sources.txt
+++ b/Telegram/gyp/telegram_sources.txt
@@ -220,6 +220,36 @@
 <(src_loc)/history/admin_log/history_admin_log_section.h
 <(src_loc)/history/feed/history_feed_section.cpp
 <(src_loc)/history/feed/history_feed_section.h
+<(src_loc)/history/media/history_media.h
+<(src_loc)/history/media/history_media.cpp
+<(src_loc)/history/media/history_media_call.h
+<(src_loc)/history/media/history_media_call.cpp
+<(src_loc)/history/media/history_media_common.h
+<(src_loc)/history/media/history_media_common.cpp
+<(src_loc)/history/media/history_media_contact.h
+<(src_loc)/history/media/history_media_contact.cpp
+<(src_loc)/history/media/history_media_document.h
+<(src_loc)/history/media/history_media_document.cpp
+<(src_loc)/history/media/history_media_file.h
+<(src_loc)/history/media/history_media_file.cpp
+<(src_loc)/history/media/history_media_game.h
+<(src_loc)/history/media/history_media_game.cpp
+<(src_loc)/history/media/history_media_gif.h
+<(src_loc)/history/media/history_media_gif.cpp
+<(src_loc)/history/media/history_media_grouped.h
+<(src_loc)/history/media/history_media_grouped.cpp
+<(src_loc)/history/media/history_media_invoice.h
+<(src_loc)/history/media/history_media_invoice.cpp
+<(src_loc)/history/media/history_media_location.h
+<(src_loc)/history/media/history_media_location.cpp
+<(src_loc)/history/media/history_media_photo.h
+<(src_loc)/history/media/history_media_photo.cpp
+<(src_loc)/history/media/history_media_sticker.h
+<(src_loc)/history/media/history_media_sticker.cpp
+<(src_loc)/history/media/history_media_video.h
+<(src_loc)/history/media/history_media_video.cpp
+<(src_loc)/history/media/history_media_web_page.h
+<(src_loc)/history/media/history_media_web_page.cpp
 <(src_loc)/history/view/history_view_context_menu.cpp
 <(src_loc)/history/view/history_view_context_menu.h
 <(src_loc)/history/view/history_view_cursor_state.cpp
@@ -249,12 +279,6 @@
 <(src_loc)/history/history_inner_widget.h
 <(src_loc)/history/history_location_manager.cpp
 <(src_loc)/history/history_location_manager.h
-<(src_loc)/history/history_media.h
-<(src_loc)/history/history_media.cpp
-<(src_loc)/history/history_media_grouped.h
-<(src_loc)/history/history_media_grouped.cpp
-<(src_loc)/history/history_media_types.cpp
-<(src_loc)/history/history_media_types.h
 <(src_loc)/history/history_message.cpp
 <(src_loc)/history/history_message.h
 <(src_loc)/history/history_service.cpp