From 2ef8136308e31e07988ee4654b73ae2a5ab495dc Mon Sep 17 00:00:00 2001
From: Ilya Fedin <fedin-ilja2010@ya.ru>
Date: Sat, 6 Jan 2024 12:56:19 +0400
Subject: [PATCH 01/73] Update patches commit in Dockerfile

---
 Telegram/build/docker/centos_env/Dockerfile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile
index d2c6320a0..bad748eb1 100644
--- a/Telegram/build/docker/centos_env/Dockerfile
+++ b/Telegram/build/docker/centos_env/Dockerfile
@@ -54,7 +54,7 @@ FROM builder AS patches
 RUN git init patches \
 	&& cd patches \
 	&& git remote add origin {{ GIT }}/desktop-app/patches.git \
-	&& git fetch --depth=1 origin 1e6c830a66649a60a2a277015143260f5716517b \
+	&& git fetch --depth=1 origin aadb3de1f4b259d64c6a9133fbd2931508a00faa \
 	&& git reset --hard FETCH_HEAD \
 	&& rm -rf .git
 

From 30d5b7fd6651eb1425e77e83edf0b75f645068e2 Mon Sep 17 00:00:00 2001
From: Ilya Fedin <fedin-ilja2010@ya.ru>
Date: Mon, 8 Jan 2024 16:16:22 +0400
Subject: [PATCH 02/73] Update patches commit in Dockerfile

---
 Telegram/build/docker/centos_env/Dockerfile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile
index bad748eb1..c9031a76d 100644
--- a/Telegram/build/docker/centos_env/Dockerfile
+++ b/Telegram/build/docker/centos_env/Dockerfile
@@ -54,7 +54,7 @@ FROM builder AS patches
 RUN git init patches \
 	&& cd patches \
 	&& git remote add origin {{ GIT }}/desktop-app/patches.git \
-	&& git fetch --depth=1 origin aadb3de1f4b259d64c6a9133fbd2931508a00faa \
+	&& git fetch --depth=1 origin 4ff99fa2d8836f2cd9befe6cf38ce87dc20f276b \
 	&& git reset --hard FETCH_HEAD \
 	&& rm -rf .git
 

From 893e14cc395d6358be84889eefe00fa506e7ef26 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Mon, 8 Jan 2024 17:34:08 +0400
Subject: [PATCH 03/73] Fix payment field values formatting.

Regression was introduced in e6b9a07163.

Fixes #27318.
---
 Telegram/SourceFiles/payments/ui/payments_field.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/Telegram/SourceFiles/payments/ui/payments_field.cpp b/Telegram/SourceFiles/payments/ui/payments_field.cpp
index e182714e3..1314674ce 100644
--- a/Telegram/SourceFiles/payments/ui/payments_field.cpp
+++ b/Telegram/SourceFiles/payments/ui/payments_field.cpp
@@ -217,10 +217,10 @@ struct SimpleFieldState {
 		const FieldConfig &config,
 		const QString &parsed,
 		const QString &countryIso2) {
-	static const auto RegExp = QRegularExpression("[^0-9]\\.");
 	if (config.type == FieldType::Country) {
 		return countryIso2;
 	} else if (config.type == FieldType::Money) {
+		static const auto RegExp = QRegularExpression("[^0-9\\.]");
 		const auto rule = LookupCurrencyRule(config.currency);
 		const auto real = QString(parsed).replace(
 			QChar(rule.decimal),
@@ -236,6 +236,7 @@ struct SimpleFieldState {
 			int64(base::SafeRound(real * std::pow(10., rule.exponent))));
 	} else if (config.type == FieldType::CardNumber
 		|| config.type == FieldType::CardCVC) {
+		static const auto RegExp = QRegularExpression("[^0-9]");
 		return QString(parsed).replace(RegExp, QString());
 	}
 	return parsed;

From c257b75a667acb597e3c7d4e58d809c8fc1775ce Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Fri, 5 Jan 2024 12:05:16 +0400
Subject: [PATCH 04/73] Add poll creation to the attach menu.

---
 .../inline_bots/bot_attach_web_view.cpp       | 23 +++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp
index 1a2703675..da0b08d7e 100644
--- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp
+++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp
@@ -32,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/painter.h"
 #include "window/themes/window_theme.h"
 #include "window/window_controller.h"
+#include "window/window_peer_menu.h"
 #include "window/window_session_controller.h"
 #include "webview/webview_interface.h"
 #include "core/application.h"
@@ -1660,6 +1661,28 @@ std::unique_ptr<Ui::DropdownMenu> MakeAttachBotsMenu(
 			attach(false);
 		}, &st::menuIconFile);
 	}
+	if (peer->canCreatePolls()) {
+		++minimal;
+		raw->addAction(tr::lng_polls_create(tr::now), [=] {
+			const auto action = actionFactory();
+			const auto source = action.options.scheduled
+				? Api::SendType::Scheduled
+				: Api::SendType::Normal;
+			const auto sendMenuType = action.replyTo.topicRootId
+				? SendMenu::Type::SilentOnly
+				: SendMenu::Type::Scheduled;
+			const auto flag = PollData::Flags();
+			const auto replyTo = action.replyTo;
+			Window::PeerMenuCreatePoll(
+				controller,
+				peer,
+				replyTo,
+				flag,
+				flag,
+				source,
+				sendMenuType);
+		}, &st::menuIconCreatePoll);
+	}
 	for (const auto &bot : bots->attachBots()) {
 		if (!bot.inAttachMenu
 			|| !PeerMatchesTypes(peer, bot.user, bot.types)) {

From 28b43eff7c3937bc1d0ce10a974311b6a93f3961 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Fri, 5 Jan 2024 12:50:04 +0400
Subject: [PATCH 05/73] Make Ctrl+Shift+[1-6] jump through accounts.

---
 Telegram/SourceFiles/core/shortcuts.cpp       | 15 +++++++
 Telegram/SourceFiles/core/shortcuts.h         | 16 ++++++++
 .../window/window_session_controller.cpp      | 39 ++++++++++++++++---
 .../window/window_session_controller.h        |  2 +-
 4 files changed, 66 insertions(+), 6 deletions(-)

diff --git a/Telegram/SourceFiles/core/shortcuts.cpp b/Telegram/SourceFiles/core/shortcuts.cpp
index f673aba28..56158e2b5 100644
--- a/Telegram/SourceFiles/core/shortcuts.cpp
+++ b/Telegram/SourceFiles/core/shortcuts.cpp
@@ -80,6 +80,13 @@ const auto CommandByName = base::flat_map<QString, Command>{
 	{ u"next_folder"_q       , Command::FolderNext },
 	{ u"all_chats"_q         , Command::ShowAllChats },
 
+	{ u"account1"_q          , Command::ShowAccount1 },
+	{ u"account2"_q          , Command::ShowAccount2 },
+	{ u"account3"_q          , Command::ShowAccount3 },
+	{ u"account4"_q          , Command::ShowAccount4 },
+	{ u"account5"_q          , Command::ShowAccount5 },
+	{ u"account6"_q          , Command::ShowAccount6 },
+
 	{ u"folder1"_q           , Command::ShowFolder1 },
 	{ u"folder2"_q           , Command::ShowFolder2 },
 	{ u"folder3"_q           , Command::ShowFolder3 },
@@ -392,6 +399,14 @@ void Manager::fillDefaults() {
 		set(u"%1+%2"_q.arg(ctrl).arg(index), command);
 	}
 
+	auto &&accounts = ranges::views::zip(
+		kShowAccount,
+		ranges::views::ints(1, ranges::unreachable));
+
+	for (const auto [command, index] : accounts) {
+		set(u"%1+shift+%2"_q.arg(ctrl).arg(index), command);
+	}
+
 	set(u"%1+shift+down"_q.arg(ctrl), Command::FolderNext);
 	set(u"%1+shift+up"_q.arg(ctrl), Command::FolderPrevious);
 
diff --git a/Telegram/SourceFiles/core/shortcuts.h b/Telegram/SourceFiles/core/shortcuts.h
index d7b3dc3c5..b562517d5 100644
--- a/Telegram/SourceFiles/core/shortcuts.h
+++ b/Telegram/SourceFiles/core/shortcuts.h
@@ -38,6 +38,13 @@ enum class Command {
 	ChatPinned7,
 	ChatPinned8,
 
+	ShowAccount1,
+	ShowAccount2,
+	ShowAccount3,
+	ShowAccount4,
+	ShowAccount5,
+	ShowAccount6,
+
 	ShowAllChats,
 	ShowFolder1,
 	ShowFolder2,
@@ -79,6 +86,15 @@ enum class Command {
 	Command::ShowFolderLast,
 };
 
+[[maybe_unused]] constexpr auto kShowAccount = {
+	Command::ShowAccount1,
+	Command::ShowAccount2,
+	Command::ShowAccount3,
+	Command::ShowAccount4,
+	Command::ShowAccount5,
+	Command::ShowAccount6,
+};
+
 [[nodiscard]] FnMut<bool()> RequestHandler(Command command);
 
 class Request {
diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp
index 454e525f6..3f78e1850 100644
--- a/Telegram/SourceFiles/window/window_session_controller.cpp
+++ b/Telegram/SourceFiles/window/window_session_controller.cpp
@@ -1224,19 +1224,48 @@ void SessionController::showGiftPremiumsBox(const QString &ref) {
 
 void SessionController::init() {
 	if (session().supportMode()) {
-		initSupportMode();
+		session().supportHelper().registerWindow(this);
 	}
+	setupShortcuts();
 }
 
-void SessionController::initSupportMode() {
-	session().supportHelper().registerWindow(this);
-
+void SessionController::setupShortcuts() {
 	Shortcuts::Requests(
 	) | rpl::filter([=] {
-		return (Core::App().activeWindow() == &window());
+		return (Core::App().activeWindow() == &window())
+			&& !isLayerShown()
+			&& !window().locked();
 	}) | rpl::start_with_next([=](not_null<Shortcuts::Request*> request) {
 		using C = Shortcuts::Command;
 
+		const auto app = &Core::App();
+		const auto accountsCount = int(app->domain().accounts().size());
+		auto &&accounts = ranges::views::zip(
+			Shortcuts::kShowAccount,
+			ranges::views::ints(0, accountsCount));
+		for (const auto [command, index] : accounts) {
+			request->check(command) && request->handle([=] {
+				const auto list = app->domain().orderedAccounts();
+				if (index >= list.size()) {
+					return false;
+				}
+				const auto account = list[index];
+				if (account == &session().account()) {
+					return false;
+				}
+				const auto window = app->separateWindowForAccount(account);
+				if (window) {
+					window->activate();
+				} else {
+					app->domain().maybeActivate(account);
+				}
+				return true;
+			});
+		}
+
+		if (!session().supportMode()) {
+			return;
+		}
 		request->check(C::SupportHistoryBack) && request->handle([=] {
 			return chatEntryHistoryMove(-1);
 		});
diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h
index 03a60fcac..4ef7b1791 100644
--- a/Telegram/SourceFiles/window/window_session_controller.h
+++ b/Telegram/SourceFiles/window/window_session_controller.h
@@ -601,7 +601,7 @@ private:
 	struct CachedTheme;
 
 	void init();
-	void initSupportMode();
+	void setupShortcuts();
 	void refreshFiltersMenu();
 	void checkOpenedFilter();
 	void suggestArchiveAndMute();

From 0f207faa3eac0ce4d3eb628a4e243050df069e53 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Mon, 8 Jan 2024 17:47:03 +0400
Subject: [PATCH 06/73] Version 4.14.4.

- Switch between logged in accounts using Ctrl+Shift+[1-6] shortcuts.
- Add poll creation in groups to the attach menu, if exists.
- Another fix for payment card validation.
---
 Telegram/Resources/uwp/AppX/AppxManifest.xml | 2 +-
 Telegram/Resources/winrc/Telegram.rc         | 8 ++++----
 Telegram/Resources/winrc/Updater.rc          | 8 ++++----
 Telegram/SourceFiles/core/version.h          | 4 ++--
 Telegram/build/version                       | 8 ++++----
 changelog.txt                                | 6 ++++++
 6 files changed, 21 insertions(+), 15 deletions(-)

diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml
index bc517dc13..8030e5cba 100644
--- a/Telegram/Resources/uwp/AppX/AppxManifest.xml
+++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml
@@ -10,7 +10,7 @@
   <Identity Name="TelegramMessengerLLP.TelegramDesktop"
     ProcessorArchitecture="ARCHITECTURE"
     Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
-    Version="4.14.3.0" />
+    Version="4.14.4.0" />
   <Properties>
     <DisplayName>Telegram Desktop</DisplayName>
     <PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc
index ec675b7e3..158616895 100644
--- a/Telegram/Resources/winrc/Telegram.rc
+++ b/Telegram/Resources/winrc/Telegram.rc
@@ -44,8 +44,8 @@ IDI_ICON1               ICON                    "..\\art\\icon256.ico"
 //
 
 VS_VERSION_INFO VERSIONINFO
- FILEVERSION 4,14,3,0
- PRODUCTVERSION 4,14,3,0
+ FILEVERSION 4,14,4,0
+ PRODUCTVERSION 4,14,4,0
  FILEFLAGSMASK 0x3fL
 #ifdef _DEBUG
  FILEFLAGS 0x1L
@@ -62,10 +62,10 @@ BEGIN
         BEGIN
             VALUE "CompanyName", "Telegram FZ-LLC"
             VALUE "FileDescription", "Telegram Desktop"
-            VALUE "FileVersion", "4.14.3.0"
+            VALUE "FileVersion", "4.14.4.0"
             VALUE "LegalCopyright", "Copyright (C) 2014-2024"
             VALUE "ProductName", "Telegram Desktop"
-            VALUE "ProductVersion", "4.14.3.0"
+            VALUE "ProductVersion", "4.14.4.0"
         END
     END
     BLOCK "VarFileInfo"
diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc
index 26208ac82..bfc6ab89c 100644
--- a/Telegram/Resources/winrc/Updater.rc
+++ b/Telegram/Resources/winrc/Updater.rc
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
 //
 
 VS_VERSION_INFO VERSIONINFO
- FILEVERSION 4,14,3,0
- PRODUCTVERSION 4,14,3,0
+ FILEVERSION 4,14,4,0
+ PRODUCTVERSION 4,14,4,0
  FILEFLAGSMASK 0x3fL
 #ifdef _DEBUG
  FILEFLAGS 0x1L
@@ -53,10 +53,10 @@ BEGIN
         BEGIN
             VALUE "CompanyName", "Telegram FZ-LLC"
             VALUE "FileDescription", "Telegram Desktop Updater"
-            VALUE "FileVersion", "4.14.3.0"
+            VALUE "FileVersion", "4.14.4.0"
             VALUE "LegalCopyright", "Copyright (C) 2014-2024"
             VALUE "ProductName", "Telegram Desktop"
-            VALUE "ProductVersion", "4.14.3.0"
+            VALUE "ProductVersion", "4.14.4.0"
         END
     END
     BLOCK "VarFileInfo"
diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h
index 939e36683..d6e18ed78 100644
--- a/Telegram/SourceFiles/core/version.h
+++ b/Telegram/SourceFiles/core/version.h
@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
 constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
 constexpr auto AppName = "Telegram Desktop"_cs;
 constexpr auto AppFile = "Telegram"_cs;
-constexpr auto AppVersion = 4014003;
-constexpr auto AppVersionStr = "4.14.3";
+constexpr auto AppVersion = 4014004;
+constexpr auto AppVersionStr = "4.14.4";
 constexpr auto AppBetaVersion = false;
 constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
diff --git a/Telegram/build/version b/Telegram/build/version
index beba60eed..7f191e54d 100644
--- a/Telegram/build/version
+++ b/Telegram/build/version
@@ -1,7 +1,7 @@
-AppVersion         4014003
+AppVersion         4014004
 AppVersionStrMajor 4.14
-AppVersionStrSmall 4.14.3
-AppVersionStr      4.14.3
+AppVersionStrSmall 4.14.4
+AppVersionStr      4.14.4
 BetaChannel        0
 AlphaVersion       0
-AppVersionOriginal 4.14.3
+AppVersionOriginal 4.14.4
diff --git a/changelog.txt b/changelog.txt
index da9a23547..06857dfac 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,3 +1,9 @@
+4.14.4 (08.01.24)
+
+- Switch between logged in accounts using Ctrl+Shift+[1-6] shortcuts.
+- Add poll creation in groups to the attach menu, if exists.
+- Another fix for payment card validation.
+
 4.14.3 (04.01.24)
 
 - Allow sending single-time voice messages.

From 6b44143f5b93f85fb2c479fe970f7efef5ef9c8a Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Mon, 8 Jan 2024 12:03:49 -0800
Subject: [PATCH 07/73] Version 4.14.4: Fix build with GCC.

---
 Telegram/SourceFiles/api/api_sending.cpp | 1 -
 1 file changed, 1 deletion(-)

diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp
index d971a03f4..0e5d7c585 100644
--- a/Telegram/SourceFiles/api/api_sending.cpp
+++ b/Telegram/SourceFiles/api/api_sending.cpp
@@ -402,7 +402,6 @@ void SendConfirmedFile(
 		flags |= MessageFlag::HasReplyInfo;
 	}
 	const auto anonymousPost = peer->amAnonymous();
-	const auto silentPost = ShouldSendSilent(peer, file->to.options);
 	FillMessagePostFlags(action, peer, flags);
 	if (file->to.options.scheduled) {
 		flags |= MessageFlag::IsOrWasScheduled;

From 7779d021b4d4e04e80d954f1f19416951354135a Mon Sep 17 00:00:00 2001
From: Klemens Nanni <kn@openbsd.org>
Date: Mon, 8 Jan 2024 22:49:46 +0100
Subject: [PATCH 08/73] update cmake/ submodule to include cppgir BSD fixes

---
 cmake | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/cmake b/cmake
index 9620c4674..e541d6577 160000
--- a/cmake
+++ b/cmake
@@ -1 +1 @@
-Subproject commit 9620c467404f15a01bb5271af02b2676c2aaf306
+Subproject commit e541d6577d542c0e42788341c2ebf0b54e354461

From 26fa3db66d458f8ae113a8634526f432e2b177bd Mon Sep 17 00:00:00 2001
From: Ilya Fedin <fedin-ilja2010@ya.ru>
Date: Tue, 9 Jan 2024 16:24:55 +0400
Subject: [PATCH 09/73] Replace deprecated medium display length with a number

---
 lib/xdg/org.telegram.desktop.metainfo.xml | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/lib/xdg/org.telegram.desktop.metainfo.xml b/lib/xdg/org.telegram.desktop.metainfo.xml
index d787eada9..810a52b0b 100644
--- a/lib/xdg/org.telegram.desktop.metainfo.xml
+++ b/lib/xdg/org.telegram.desktop.metainfo.xml
@@ -45,12 +45,14 @@
         <keyword>im</keyword>
     </keywords>
     <requires>
-        <display_length compare="ge">medium</display_length>
+        <!-- windowMinHeight from Telegram/SourceFiles/window/window.style -->
+        <display_length compare="ge">480</display_length>
         <internet>always</internet>
+        <!-- on-screen keyboards are too big for current minimal display height -->
+        <control>keyboard</control>
     </requires>
     <supports>
         <control>pointing</control>
-        <control>keyboard</control>
         <control>touch</control>
     </supports>
     <content_rating type="oars-1.1">

From 8138a26c2d761efa93069d8405649db1e85787a4 Mon Sep 17 00:00:00 2001
From: Ilya Fedin <fedin-ilja2010@ya.ru>
Date: Tue, 9 Jan 2024 16:25:29 +0400
Subject: [PATCH 10/73] Add information about provided mime-type and D-Bus
 service

---
 lib/xdg/org.telegram.desktop.metainfo.xml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/lib/xdg/org.telegram.desktop.metainfo.xml b/lib/xdg/org.telegram.desktop.metainfo.xml
index 810a52b0b..874bbc2be 100644
--- a/lib/xdg/org.telegram.desktop.metainfo.xml
+++ b/lib/xdg/org.telegram.desktop.metainfo.xml
@@ -88,5 +88,7 @@
     <launchable type="desktop-id">org.telegram.desktop.desktop</launchable>
     <provides>
         <binary>telegram-desktop</binary>
+        <dbus type="session">org.telegram.desktop</dbus>
+        <mediatype>x-scheme-handler/tg</mediatype>
     </provides>
 </component>

From aa121aa1de79402c930e3ea2ad79508e35d64e6c Mon Sep 17 00:00:00 2001
From: Ilya Fedin <fedin-ilja2010@ya.ru>
Date: Tue, 9 Jan 2024 23:22:46 +0400
Subject: [PATCH 11/73] Get rid of architecture whitelist from snapcraft.yaml

So the Canonical builders build on new architectures as soon as they appear
---
 snap/snapcraft.yaml | 6 ------
 1 file changed, 6 deletions(-)

diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml
index 16e8914b6..41dc00828 100644
--- a/snap/snapcraft.yaml
+++ b/snap/snapcraft.yaml
@@ -7,12 +7,6 @@ grade: stable
 confinement: strict
 compression: lzo
 
-architectures:
-  - build-on: amd64
-  - build-on: arm64
-  - build-on: armhf
-  - build-on: ppc64el
-
 apps:
   telegram-desktop:
     command: usr/bin/telegram-desktop

From d803b3ae7d386f926742aad76856432056f0c624 Mon Sep 17 00:00:00 2001
From: Ilya Fedin <fedin-ilja2010@ya.ru>
Date: Wed, 10 Jan 2024 07:01:22 +0400
Subject: [PATCH 12/73] Try to add mesa vulkan drivers to snap

---
 snap/snapcraft.yaml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml
index 41dc00828..4b28983d8 100644
--- a/snap/snapcraft.yaml
+++ b/snap/snapcraft.yaml
@@ -491,6 +491,7 @@ parts:
       - libxkbcommon0
       - libxkbcommon-x11-0
       - zlib1g
+      - mesa-vulkan-drivers
     override-pull: |
       QT=6.6.1
 

From 308fdcf9cf8d11042f778bfb478a4c298116ff74 Mon Sep 17 00:00:00 2001
From: Ilya Fedin <fedin-ilja2010@ya.ru>
Date: Wed, 10 Jan 2024 09:59:34 +0400
Subject: [PATCH 13/73] Update submodules

---
 Telegram/build/docker/centos_env/Dockerfile | 2 +-
 Telegram/lib_ui                             | 2 +-
 Telegram/lib_webview                        | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile
index c9031a76d..ad9a28438 100644
--- a/Telegram/build/docker/centos_env/Dockerfile
+++ b/Telegram/build/docker/centos_env/Dockerfile
@@ -54,7 +54,7 @@ FROM builder AS patches
 RUN git init patches \
 	&& cd patches \
 	&& git remote add origin {{ GIT }}/desktop-app/patches.git \
-	&& git fetch --depth=1 origin 4ff99fa2d8836f2cd9befe6cf38ce87dc20f276b \
+	&& git fetch --depth=1 origin a63f0887630c3e2130ee60ef322dd51ae3983c88 \
 	&& git reset --hard FETCH_HEAD \
 	&& rm -rf .git
 
diff --git a/Telegram/lib_ui b/Telegram/lib_ui
index 30c5dfe6f..00f5bdacc 160000
--- a/Telegram/lib_ui
+++ b/Telegram/lib_ui
@@ -1 +1 @@
-Subproject commit 30c5dfe6f65babf234c889959061c97c4a2f391d
+Subproject commit 00f5bdaccdff4f53e8ba347f09f2de161b7ca7da
diff --git a/Telegram/lib_webview b/Telegram/lib_webview
index 63e4ba48f..a1c84d636 160000
--- a/Telegram/lib_webview
+++ b/Telegram/lib_webview
@@ -1 +1 @@
-Subproject commit 63e4ba48fd8540fa3c2949d123160a2ce3411d70
+Subproject commit a1c84d636f7975781dece08939bbc24da8298c3c

From 1e98e19aaf8f11449164b24e6b437f7798cb1a44 Mon Sep 17 00:00:00 2001
From: Ilya Fedin <fedin-ilja2010@ya.ru>
Date: Wed, 10 Jan 2024 10:38:59 +0400
Subject: [PATCH 14/73] Add installed packages to cache key for macOS packaged
 action

---
 .github/workflows/mac_packaged.yml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.github/workflows/mac_packaged.yml b/.github/workflows/mac_packaged.yml
index 631ff2a70..51ba9917a 100644
--- a/.github/workflows/mac_packaged.yml
+++ b/.github/workflows/mac_packaged.yml
@@ -73,6 +73,7 @@ jobs:
           sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
 
           xcodebuild -version > CACHE_KEY.txt
+          brew list --versions >> CACHE_KEY.txt
           echo $MANUAL_CACHING >> CACHE_KEY.txt
           echo "$GITHUB_WORKSPACE" >> CACHE_KEY.txt
           if [ "$AUTO_CACHING" = "1" ]; then

From 333ef9b48ab0c3a009c0039775eb69ad189f62ad Mon Sep 17 00:00:00 2001
From: Ilya Fedin <fedin-ilja2010@ya.ru>
Date: Fri, 12 Jan 2024 07:15:32 +0400
Subject: [PATCH 15/73] Hide connection widget when the window is not exposed

---
 .../view/history_view_top_bar_widget.cpp      | 14 ++++++++++-
 .../window/window_connecting_widget.cpp       | 23 ++++++++++++++-----
 .../window/window_connecting_widget.h         |  2 ++
 3 files changed, 32 insertions(+), 7 deletions(-)

diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp
index 774081836..bb3407afd 100644
--- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp
@@ -55,6 +55,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_send_action.h"
 #include "chat_helpers/emoji_interactions.h"
 #include "base/unixtime.h"
+#include "base/event_filter.h"
 #include "support/support_helper.h"
 #include "apiwrap.h"
 #include "api/api_chat_participants.h"
@@ -230,6 +231,16 @@ TopBarWidget::TopBarWidget(
 		updateConnectingState();
 	}, lifetime());
 
+	base::install_event_filter(
+		this,
+		window()->windowHandle(),
+		[=](not_null<QEvent*> e) {
+			if (e->type() == QEvent::Expose) {
+				updateConnectingState();
+			}
+			return base::EventFilterResult::Continue;
+		});
+
 	setCursor(style::cur_pointer);
 }
 
@@ -241,7 +252,8 @@ Main::Session &TopBarWidget::session() const {
 
 void TopBarWidget::updateConnectingState() {
 	const auto state = _controller->session().mtp().dcstate();
-	if (state == MTP::ConnectedState) {
+	const auto exposed = window()->windowHandle()->isExposed();
+	if (state == MTP::ConnectedState || !exposed) {
 		if (_connecting) {
 			_connecting = nullptr;
 			update();
diff --git a/Telegram/SourceFiles/window/window_connecting_widget.cpp b/Telegram/SourceFiles/window/window_connecting_widget.cpp
index 3d3a5d2a3..d045b153c 100644
--- a/Telegram/SourceFiles/window/window_connecting_widget.cpp
+++ b/Telegram/SourceFiles/window/window_connecting_widget.cpp
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "window/window_connecting_widget.h"
 
+#include "base/event_filter.h"
 #include "ui/widgets/buttons.h"
 #include "ui/effects/radial_animation.h"
 #include "ui/painter.h"
@@ -207,6 +208,14 @@ ConnectionState::ConnectionState(
 	rpl::producer<bool> shown)
 : _account(account)
 , _parent(parent)
+, _exposeFilter(base::install_event_filter(
+	parent->window()->windowHandle(),
+	[=](not_null<QEvent*> e) {
+		if (e->type() == QEvent::Expose) {
+			refreshState();
+		}
+		return base::EventFilterResult::Continue;
+	}))
 , _refreshTimer([=] { refreshState(); })
 , _currentLayout(computeLayout(_state)) {
 	rpl::combine(
@@ -290,6 +299,7 @@ void ConnectionState::setBottomSkip(int skip) {
 void ConnectionState::refreshState() {
 	using Checker = Core::UpdateChecker;
 	const auto state = [&]() -> State {
+		const auto exposed = _parent->window()->windowHandle()->isExposed();
 		const auto under = _widget && _widget->isOver();
 		const auto ready = (Checker().state() == Checker::State::Ready);
 		const auto state = _account->mtp().dcstate();
@@ -297,18 +307,18 @@ void ConnectionState::refreshState() {
 		if (state == MTP::ConnectingState
 			|| state == MTP::DisconnectedState
 			|| (state < 0 && state > -600)) {
-			return { State::Type::Connecting, proxy, under, ready };
+			return { State::Type::Connecting, proxy, exposed, under, ready };
 		} else if (state < 0
 			&& state >= -kMinimalWaitingStateDuration
 			&& _state.type != State::Type::Waiting) {
-			return { State::Type::Connecting, proxy, under, ready };
+			return { State::Type::Connecting, proxy, exposed, under, ready };
 		} else if (state < 0) {
 			const auto wait = ((-state) / 1000) + 1;
-			return { State::Type::Waiting, proxy, under, ready, wait };
+			return { State::Type::Waiting, proxy, exposed, under, ready, wait };
 		}
-		return { State::Type::Connected, proxy, under, ready };
+		return { State::Type::Connected, proxy, exposed, under, ready };
 	}();
-	if (state.waitTillRetry > 0) {
+	if (state.exposed && state.waitTillRetry > 0) {
 		_refreshTimer.callOnce(kRefreshTimeout);
 	}
 	if (state == _state) {
@@ -421,7 +431,8 @@ auto ConnectionState::computeLayout(const State &state) const -> Layout {
 	auto result = Layout();
 	result.proxyEnabled = state.useProxy;
 	result.progressShown = (state.type != State::Type::Connected);
-	result.visible = !state.updateReady
+	result.visible = state.exposed
+		&& !state.updateReady
 		&& (state.useProxy
 			|| state.type == State::Type::Connecting
 			|| state.type == State::Type::Waiting);
diff --git a/Telegram/SourceFiles/window/window_connecting_widget.h b/Telegram/SourceFiles/window/window_connecting_widget.h
index 5c391a9a6..7813ce5ad 100644
--- a/Telegram/SourceFiles/window/window_connecting_widget.h
+++ b/Telegram/SourceFiles/window/window_connecting_widget.h
@@ -46,6 +46,7 @@ private:
 		};
 		Type type = Type::Connected;
 		bool useProxy = false;
+		bool exposed = false;
 		bool underCursor = false;
 		bool updateReady = false;
 		int waitTillRetry = 0;
@@ -79,6 +80,7 @@ private:
 
 	const not_null<Main::Account*> _account;
 	not_null<Ui::RpWidget*> _parent;
+	base::unique_qptr<QObject> _exposeFilter;
 	rpl::variable<int> _bottomSkip;
 	base::unique_qptr<Widget> _widget;
 	bool _forceHidden = false;

From eca8c28dea1c59dc6ea5f48bd83fb3f06c4b2b8d Mon Sep 17 00:00:00 2001
From: Ilya Fedin <fedin-ilja2010@ya.ru>
Date: Sat, 13 Jan 2024 00:50:29 +0400
Subject: [PATCH 16/73] Add missing QWindow includes

---
 .../SourceFiles/history/view/history_view_top_bar_widget.cpp    | 2 ++
 Telegram/SourceFiles/window/window_connecting_widget.cpp        | 2 ++
 2 files changed, 4 insertions(+)

diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp
index bb3407afd..891f45927 100644
--- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp
@@ -65,6 +65,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "styles/style_info.h"
 #include "styles/style_menu_icons.h"
 
+#include <QtGui/QWindow>
+
 namespace HistoryView {
 namespace {
 
diff --git a/Telegram/SourceFiles/window/window_connecting_widget.cpp b/Telegram/SourceFiles/window/window_connecting_widget.cpp
index d045b153c..7a757df68 100644
--- a/Telegram/SourceFiles/window/window_connecting_widget.cpp
+++ b/Telegram/SourceFiles/window/window_connecting_widget.cpp
@@ -23,6 +23,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "lang/lang_keys.h"
 #include "styles/style_window.h"
 
+#include <QtGui/QWindow>
+
 namespace Window {
 namespace {
 

From dd1cca1a0ae880939242762de06e3d53d82440cb Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 11 Jan 2024 19:58:13 +0000
Subject: [PATCH 17/73] Bump jinja2 from 3.1.2 to 3.1.3 in
 /Telegram/build/docker/centos_env

Bumps [jinja2](https://github.com/pallets/jinja) from 3.1.2 to 3.1.3.
- [Release notes](https://github.com/pallets/jinja/releases)
- [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst)
- [Commits](https://github.com/pallets/jinja/compare/3.1.2...3.1.3)

---
updated-dependencies:
- dependency-name: jinja2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
---
 Telegram/build/docker/centos_env/poetry.lock  | 28 +++++++++----------
 .../build/docker/centos_env/pyproject.toml    |  2 +-
 2 files changed, 14 insertions(+), 16 deletions(-)

diff --git a/Telegram/build/docker/centos_env/poetry.lock b/Telegram/build/docker/centos_env/poetry.lock
index 3b86e84d5..08526811d 100644
--- a/Telegram/build/docker/centos_env/poetry.lock
+++ b/Telegram/build/docker/centos_env/poetry.lock
@@ -1,10 +1,15 @@
+# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
+
 [[package]]
 name = "jinja2"
-version = "3.1.2"
+version = "3.1.3"
 description = "A very fast and expressive template engine."
-category = "main"
 optional = false
 python-versions = ">=3.7"
+files = [
+    {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"},
+    {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"},
+]
 
 [package.dependencies]
 MarkupSafe = ">=2.0"
@@ -16,21 +21,9 @@ i18n = ["Babel (>=2.7)"]
 name = "markupsafe"
 version = "2.1.1"
 description = "Safely add untrusted strings to HTML/XML markup."
-category = "main"
 optional = false
 python-versions = ">=3.7"
-
-[metadata]
-lock-version = "1.1"
-python-versions = "^3.7"
-content-hash = "fdf86553de6f950425c8ca77fe37127c9242c83445ce44b951ee4032ef72ea2f"
-
-[metadata.files]
-jinja2 = [
-    {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
-    {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
-]
-markupsafe = [
+files = [
     {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"},
     {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"},
     {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"},
@@ -72,3 +65,8 @@ markupsafe = [
     {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"},
     {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"},
 ]
+
+[metadata]
+lock-version = "2.0"
+python-versions = "^3.7"
+content-hash = "bac9a23a86839c72127e75f2cf181e331f59d96f723403d529e7ea19774ff9c4"
diff --git a/Telegram/build/docker/centos_env/pyproject.toml b/Telegram/build/docker/centos_env/pyproject.toml
index b6507ba3f..bba5c212f 100644
--- a/Telegram/build/docker/centos_env/pyproject.toml
+++ b/Telegram/build/docker/centos_env/pyproject.toml
@@ -6,7 +6,7 @@ authors = []
 
 [tool.poetry.dependencies]
 python = "^3.7"
-Jinja2 = "^3.1.2"
+Jinja2 = "^3.1.3"
 
 [tool.poetry.dev-dependencies]
 

From 0faf801de7853ff7a3270e6e4bf3ce23c255d6b7 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Wed, 3 Jan 2024 14:46:34 +0400
Subject: [PATCH 18/73] Update API scheme to layer 171.

---
 Telegram/SourceFiles/api/api_messages_search.cpp  |  1 +
 .../SourceFiles/calls/calls_box_controller.cpp    |  1 +
 .../SourceFiles/data/data_search_controller.cpp   |  1 +
 Telegram/SourceFiles/dialogs/dialogs_widget.cpp   |  3 +++
 Telegram/SourceFiles/export/export_api_wrap.cpp   |  1 +
 Telegram/SourceFiles/mtproto/scheme/api.tl        | 15 ++++++++++++---
 6 files changed, 19 insertions(+), 3 deletions(-)

diff --git a/Telegram/SourceFiles/api/api_messages_search.cpp b/Telegram/SourceFiles/api/api_messages_search.cpp
index 15119b906..cc97e22c0 100644
--- a/Telegram/SourceFiles/api/api_messages_search.cpp
+++ b/Telegram/SourceFiles/api/api_messages_search.cpp
@@ -91,6 +91,7 @@ void MessagesSearch::searchRequest() {
 				? _from->input
 				: MTP_inputPeerEmpty()),
 			MTPInputPeer(), // saved_peer_id
+			MTPVector<MTPReaction>(), // saved_reaction
 			MTPint(), // top_msg_id
 			MTP_inputMessagesFilterEmpty(),
 			MTP_int(0), // min_date
diff --git a/Telegram/SourceFiles/calls/calls_box_controller.cpp b/Telegram/SourceFiles/calls/calls_box_controller.cpp
index 9eb53580f..11c6347d5 100644
--- a/Telegram/SourceFiles/calls/calls_box_controller.cpp
+++ b/Telegram/SourceFiles/calls/calls_box_controller.cpp
@@ -521,6 +521,7 @@ void BoxController::loadMoreRows() {
 		MTP_string(), // q
 		MTP_inputPeerEmpty(),
 		MTPInputPeer(), // saved_peer_id
+		MTPVector<MTPReaction>(), // saved_reaction
 		MTPint(), // top_msg_id
 		MTP_inputMessagesFilterPhoneCalls(MTP_flags(0)),
 		MTP_int(0), // min_date
diff --git a/Telegram/SourceFiles/data/data_search_controller.cpp b/Telegram/SourceFiles/data/data_search_controller.cpp
index 688ce4ae9..43c5efb66 100644
--- a/Telegram/SourceFiles/data/data_search_controller.cpp
+++ b/Telegram/SourceFiles/data/data_search_controller.cpp
@@ -98,6 +98,7 @@ std::optional<SearchRequest> PrepareSearchRequest(
 		MTP_string(query),
 		MTP_inputPeerEmpty(),
 		MTPInputPeer(), // saved_peer_id
+		MTPVector<MTPReaction>(), // saved_reaction
 		MTP_int(topicRootId),
 		filter,
 		MTP_int(0), // min_date
diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
index 4aa36ec38..f6ace227a 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
@@ -1764,6 +1764,7 @@ bool Widget::searchMessages(bool searchCache) {
 						? _searchQueryFrom->input
 						: MTP_inputPeerEmpty()),
 					MTPInputPeer(), // saved_peer_id
+					MTPVector<MTPReaction>(), // saved_reaction
 					MTP_int(topic ? topic->rootId() : 0),
 					MTP_inputMessagesFilterEmpty(),
 					MTP_int(0), // min_date
@@ -2006,6 +2007,7 @@ void Widget::searchMore() {
 						? _searchQueryFrom->input
 						: MTP_inputPeerEmpty()),
 					MTPInputPeer(), // saved_peer_id
+					MTPVector<MTPReaction>(), // saved_reaction
 					MTP_int(topic ? topic->rootId() : 0),
 					MTP_inputMessagesFilterEmpty(),
 					MTP_int(0), // min_date
@@ -2079,6 +2081,7 @@ void Widget::searchMore() {
 					? _searchQueryFrom->input
 					: MTP_inputPeerEmpty()),
 				MTPInputPeer(), // saved_peer_id
+				MTPVector<MTPReaction>(), // saved_reaction
 				MTPint(), // top_msg_id
 				MTP_inputMessagesFilterEmpty(),
 				MTP_int(0), // min_date
diff --git a/Telegram/SourceFiles/export/export_api_wrap.cpp b/Telegram/SourceFiles/export/export_api_wrap.cpp
index 61f29b16c..25fe29a71 100644
--- a/Telegram/SourceFiles/export/export_api_wrap.cpp
+++ b/Telegram/SourceFiles/export/export_api_wrap.cpp
@@ -1625,6 +1625,7 @@ void ApiWrap::requestChatMessages(
 			MTP_string(), // query
 			MTP_inputPeerSelf(),
 			MTPInputPeer(), // saved_peer_id
+			MTPVector<MTPReaction>(), // saved_reaction
 			MTPint(), // top_msg_id
 			MTP_inputMessagesFilterEmpty(),
 			MTP_int(0), // min_date
diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl
index 87ce36bbc..fee427f9f 100644
--- a/Telegram/SourceFiles/mtproto/scheme/api.tl
+++ b/Telegram/SourceFiles/mtproto/scheme/api.tl
@@ -400,6 +400,7 @@ updateBotMessageReaction#ac21d3ce peer:Peer msg_id:int date:int actor:Peer old_r
 updateBotMessageReactions#9cb7759 peer:Peer msg_id:int date:int reactions:Vector<ReactionCount> qts:int = Update;
 updateSavedDialogPinned#aeaf9e74 flags:# pinned:flags.0?true peer:DialogPeer = Update;
 updatePinnedSavedDialogs#686c85a6 flags:# order:flags.0?Vector<DialogPeer> = Update;
+updateSavedReactionTags#39c67432 = Update;
 
 updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State;
 
@@ -1366,7 +1367,7 @@ auth.loggedOut#c3a2835f flags:# future_auth_token:flags.0?bytes = auth.LoggedOut
 
 reactionCount#a3d1cb80 flags:# chosen_order:flags.0?int reaction:Reaction count:int = ReactionCount;
 
-messageReactions#4f2b9479 flags:# min:flags.0?true can_see_list:flags.2?true results:Vector<ReactionCount> recent_reactions:flags.1?Vector<MessagePeerReaction> = MessageReactions;
+messageReactions#4f2b9479 flags:# min:flags.0?true can_see_list:flags.2?true reactions_as_tags:flags.3?true results:Vector<ReactionCount> recent_reactions:flags.1?Vector<MessagePeerReaction> = MessageReactions;
 
 messages.messageReactionsList#31bd492d flags:# count:int reactions:Vector<MessagePeerReaction> chats:Vector<Chat> users:Vector<User> next_offset:flags.0?string = messages.MessageReactionsList;
 
@@ -1643,6 +1644,11 @@ messages.savedDialogs#f83ae221 dialogs:Vector<SavedDialog> messages:Vector<Messa
 messages.savedDialogsSlice#44ba9dd9 count:int dialogs:Vector<SavedDialog> messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.SavedDialogs;
 messages.savedDialogsNotModified#c01f6fe8 count:int = messages.SavedDialogs;
 
+savedReactionTag#cb6ff828 flags:# reaction:Reaction title:flags.0?string count:int = SavedReactionTag;
+
+messages.savedReactionTagsNotModified#889b59ef = messages.SavedReactionTags;
+messages.savedReactionTags#3259950a tags:Vector<SavedReactionTag> hash:long = messages.SavedReactionTags;
+
 ---functions---
 
 invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
@@ -1802,7 +1808,7 @@ contacts.setBlocked#94c65c76 flags:# my_stories_from:flags.0?true id:Vector<Inpu
 messages.getMessages#63c66506 id:Vector<InputMessage> = messages.Messages;
 messages.getDialogs#a0f4cb4f flags:# exclude_pinned:flags.0?true folder_id:flags.1?int offset_date:int offset_id:int offset_peer:InputPeer limit:int hash:long = messages.Dialogs;
 messages.getHistory#4423e6c5 peer:InputPeer offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages;
-messages.search#a7b4e929 flags:# peer:InputPeer q:string from_id:flags.0?InputPeer saved_peer_id:flags.2?InputPeer top_msg_id:flags.1?int filter:MessagesFilter min_date:int max_date:int offset_id:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages;
+messages.search#29ee847a flags:# peer:InputPeer q:string from_id:flags.0?InputPeer saved_peer_id:flags.2?InputPeer saved_reaction:flags.3?Vector<Reaction> top_msg_id:flags.1?int filter:MessagesFilter min_date:int max_date:int offset_id:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages;
 messages.readHistory#e306d3a peer:InputPeer max_id:int = messages.AffectedMessages;
 messages.deleteHistory#b08f922a flags:# just_clear:flags.0?true revoke:flags.1?true peer:InputPeer max_id:int min_date:flags.2?int max_date:flags.3?int = messages.AffectedHistory;
 messages.deleteMessages#e58e95d2 flags:# revoke:flags.0?true id:Vector<int> = messages.AffectedMessages;
@@ -1993,6 +1999,9 @@ messages.deleteSavedHistory#6e98102b flags:# peer:InputPeer max_id:int min_date:
 messages.getPinnedSavedDialogs#d63d94e0 = messages.SavedDialogs;
 messages.toggleSavedDialogPin#ac81bbde flags:# pinned:flags.0?true peer:InputDialogPeer = Bool;
 messages.reorderPinnedSavedDialogs#8b716587 flags:# force:flags.0?true order:Vector<InputDialogPeer> = Bool;
+messages.getSavedReactionTags#761ddacf hash:long = messages.SavedReactionTags;
+messages.updateSavedReactionTag#60297dec flags:# reaction:Reaction title:flags.0?string = Bool;
+messages.getDefaultTagReactions#bdf93428 hash:long = messages.Reactions;
 
 updates.getState#edd4882a = updates.State;
 updates.getDifference#19c2f763 flags:# pts:int pts_limit:flags.1?int pts_total_limit:flags.0?int date:int qts:int qts_limit:flags.2?int = updates.Difference;
@@ -2235,4 +2244,4 @@ premium.applyBoost#6b7da746 flags:# slots:flags.0?Vector<int> peer:InputPeer = p
 premium.getBoostsStatus#42f1f61 peer:InputPeer = premium.BoostsStatus;
 premium.getUserBoosts#39854d1f peer:InputPeer user_id:InputUser = premium.BoostsList;
 
-// LAYER 170
+// LAYER 171

From 9b43d204e2c8fe2e471edb73ba5a9986eb4bd174 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Wed, 3 Jan 2024 14:46:50 +0400
Subject: [PATCH 19/73] Track and render reactions as tags in Saved Messages.

---
 .../SourceFiles/data/data_saved_messages.cpp  |   4 +-
 Telegram/SourceFiles/data/data_types.h        |   2 +
 Telegram/SourceFiles/history/history_item.cpp |  57 +++---
 Telegram/SourceFiles/history/history_item.h   |   1 +
 .../history/view/history_view_message.cpp     |   6 +-
 .../view/reactions/history_view_reactions.cpp | 166 ++++++++++++++++--
 .../view/reactions/history_view_reactions.h   |  11 ++
 Telegram/SourceFiles/ui/chat/chat.style       |   6 +
 Telegram/SourceFiles/ui/empty_userpic.cpp     |   2 +-
 9 files changed, 216 insertions(+), 39 deletions(-)

diff --git a/Telegram/SourceFiles/data/data_saved_messages.cpp b/Telegram/SourceFiles/data/data_saved_messages.cpp
index 66bc87609..d81f97fe5 100644
--- a/Telegram/SourceFiles/data/data_saved_messages.cpp
+++ b/Telegram/SourceFiles/data/data_saved_messages.cpp
@@ -20,6 +20,8 @@ namespace {
 
 constexpr auto kPerPage = 50;
 constexpr auto kFirstPerPage = 10;
+constexpr auto kListPerPage = 100;
+constexpr auto kListFirstPerPage = 20;
 
 } // namespace
 
@@ -82,7 +84,7 @@ void SavedMessages::sendLoadMore() {
 			MTP_int(_offsetDate),
 			MTP_int(_offsetId),
 			_offsetPeer ? _offsetPeer->input : MTP_inputPeerEmpty(),
-			MTP_int(kPerPage),
+			MTP_int(_offsetId ? kListPerPage : kListFirstPerPage),
 			MTP_long(0)) // hash
 	).done([=](const MTPmessages_SavedDialogs &result) {
 		apply(result, false);
diff --git a/Telegram/SourceFiles/data/data_types.h b/Telegram/SourceFiles/data/data_types.h
index 31c976859..46f75419a 100644
--- a/Telegram/SourceFiles/data/data_types.h
+++ b/Telegram/SourceFiles/data/data_types.h
@@ -313,6 +313,8 @@ enum class MessageFlag : uint64 {
 	ShowSimilarChannels   = (1ULL << 41),
 
 	Sponsored             = (1ULL << 42),
+
+	ReactionsAreTags      = (1ULL << 43),
 };
 inline constexpr bool is_flag_type(MessageFlag) { return true; }
 using MessageFlags = base::flags<MessageFlag>;
diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp
index dcdd8b3d0..35a2dc7db 100644
--- a/Telegram/SourceFiles/history/history_item.cpp
+++ b/Telegram/SourceFiles/history/history_item.cpp
@@ -2429,6 +2429,10 @@ const std::vector<Data::MessageReaction> &HistoryItem::reactions() const {
 	return _reactions ? _reactions->list() : kEmpty;
 }
 
+bool HistoryItem::reactionsAreTags() const {
+	return _flags & MessageFlag::ReactionsAreTags;
+}
+
 auto HistoryItem::recentReactions() const
 -> const base::flat_map<
 		Data::ReactionId,
@@ -3556,31 +3560,40 @@ bool HistoryItem::changeReactions(const MTPMessageReactions *reactions) {
 	}
 	if (!reactions) {
 		_flags &= ~MessageFlag::CanViewReactions;
+		if (_history->peer->isSelf()) {
+			_flags |= MessageFlag::ReactionsAreTags;
+		}
 		return (base::take(_reactions) != nullptr);
 	}
-	return reactions->match([&](const MTPDmessageReactions &data) {
-		if (data.is_can_see_list()) {
-			_flags |= MessageFlag::CanViewReactions;
-		} else {
-			_flags &= ~MessageFlag::CanViewReactions;
+	const auto &data = reactions->data();
+	const auto empty = data.vresults().v.isEmpty();
+	if (data.is_reactions_as_tags()
+		|| (empty && _history->peer->isSelf())) {
+		_flags |= MessageFlag::ReactionsAreTags;
+	} else {
+		_flags &= ~MessageFlag::ReactionsAreTags;
+	}
+	if (data.is_can_see_list()) {
+		_flags |= MessageFlag::CanViewReactions;
+	} else {
+		_flags &= ~MessageFlag::CanViewReactions;
+	}
+	if (empty) {
+		return (base::take(_reactions) != nullptr);
+	} else if (!_reactions) {
+		_reactions = std::make_unique<Data::MessageReactions>(this);
+	}
+	const auto min = data.is_min();
+	const auto &list = data.vresults().v;
+	const auto &recent = data.vrecent_reactions().value_or_empty();
+	if (min && hasUnreadReaction()) {
+		// We can't update reactions from min if we have unread.
+		if (_reactions->checkIfChanged(list, recent, min)) {
+			updateReactionsUnknown();
 		}
-		if (data.vresults().v.isEmpty()) {
-			return (base::take(_reactions) != nullptr);
-		} else if (!_reactions) {
-			_reactions = std::make_unique<Data::MessageReactions>(this);
-		}
-		const auto min = data.is_min();
-		const auto &list = data.vresults().v;
-		const auto &recent = data.vrecent_reactions().value_or_empty();
-		if (min && hasUnreadReaction()) {
-			// We can't update reactions from min if we have unread.
-			if (_reactions->checkIfChanged(list, recent, min)) {
-				updateReactionsUnknown();
-			}
-			return false;
-		}
-		return _reactions->change(list, recent, min);
-	});
+		return false;
+	}
+	return _reactions->change(list, recent, min);
 }
 
 void HistoryItem::applyTTL(const MTPDmessage &data) {
diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h
index 412b9e057..75705eab8 100644
--- a/Telegram/SourceFiles/history/history_item.h
+++ b/Telegram/SourceFiles/history/history_item.h
@@ -454,6 +454,7 @@ public:
 		not_null<UserData*> from) const;
 	[[nodiscard]] crl::time lastReactionsRefreshTime() const;
 
+	[[nodiscard]] bool reactionsAreTags() const;
 	[[nodiscard]] bool hasDirectLink() const;
 	[[nodiscard]] bool changesWallPaper() const;
 
diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp
index 4edffbe37..f319dfc9a 100644
--- a/Telegram/SourceFiles/history/view/history_view_message.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_message.cpp
@@ -2917,8 +2917,12 @@ bool Message::isSignedAuthorElided() const {
 bool Message::embedReactionsInBottomInfo() const {
 	const auto item = data();
 	const auto user = item->history()->peer->asUser();
-	if (!user || user->isPremium() || user->session().premium()) {
+	if (!user
+		|| user->isPremium()
+		|| user->isSelf()
+		|| user->session().premium()) {
 		// Only in messages of a non premium user with a non premium user.
+		// In saved messages we use reactions for tags, we don't embed them.
 		return false;
 	}
 	auto seenMy = false;
diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp
index 911472560..a7e83c029 100644
--- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp
+++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp
@@ -55,6 +55,7 @@ struct InlineList::Button {
 	int count = 0;
 	int countTextWidth = 0;
 	bool chosen = false;
+	bool tag = false;
 };
 
 InlineList::InlineList(
@@ -118,6 +119,7 @@ void InlineList::layoutButtons() {
 	) | ranges::views::transform([](const MessageReaction &reaction) {
 		return not_null{ &reaction };
 	}) | ranges::to_vector;
+	const auto tags = _data.flags & Data::Flag::Tags;
 	const auto &list = _owner->list(::Data::Reactions::Type::All);
 	ranges::sort(sorted, [&](
 			not_null<const MessageReaction*> a,
@@ -142,8 +144,10 @@ void InlineList::layoutButtons() {
 		buttons.push_back((i != end(_buttons))
 			? std::move(*i)
 			: prepareButtonWithId(id));
-		const auto j = _data.recent.find(id);
-		if (j != end(_data.recent) && !j->second.empty()) {
+		if (tags) {
+			setButtonTag(buttons.back());
+		} else if (const auto j = _data.recent.find(id)
+			; j != end(_data.recent) && !j->second.empty()) {
 			setButtonUserpics(buttons.back(), j->second);
 		} else {
 			setButtonCount(buttons.back(), reaction->count);
@@ -168,12 +172,22 @@ InlineList::Button InlineList::prepareButtonWithId(const ReactionId &id) {
 	return result;
 }
 
+void InlineList::setButtonTag(Button &button) {
+	if (button.tag) {
+		return;
+	}
+	button.userpics = nullptr;
+	button.count = 0;
+	button.tag = true;
+}
+
 void InlineList::setButtonCount(Button &button, int count) {
-	if (button.count == count && !button.userpics) {
+	if (!button.tag && button.count == count && !button.userpics) {
 		return;
 	}
 	button.userpics = nullptr;
 	button.count = count;
+	button.tag = false;
 	button.countText = Lang::FormatCountToShort(count).string;
 	button.countTextWidth = st::semiboldFont->width(button.countText);
 }
@@ -181,6 +195,7 @@ void InlineList::setButtonCount(Button &button, int count) {
 void InlineList::setButtonUserpics(
 		Button &button,
 		const std::vector<not_null<PeerData*>> &peers) {
+	button.tag = false;
 	if (!button.userpics) {
 		button.userpics = std::make_unique<Userpics>();
 	}
@@ -228,6 +243,10 @@ QSize InlineList::countOptimalSize() {
 	const auto between = st::reactionInlineBetween;
 	const auto padding = st::reactionInlinePadding;
 	const auto size = st::reactionInlineSize;
+	const auto widthBaseTag = padding.left()
+		+ size
+		+ st::reactionInlineTagSkip
+		+ padding.right();
 	const auto widthBaseCount = padding.left()
 		+ size
 		+ st::reactionInlineSkip
@@ -245,7 +264,9 @@ QSize InlineList::countOptimalSize() {
 	};
 	const auto height = padding.top() + size + padding.bottom();
 	for (auto &button : _buttons) {
-		const auto width = button.userpics
+		const auto width = button.tag
+			? widthBaseTag
+			: button.userpics
 			? (widthBaseUserpics + userpicsWidth(button))
 			: (widthBaseCount + button.countTextWidth);
 		button.geometry.setSize({ width, height });
@@ -336,7 +357,8 @@ void InlineList::paint(
 	const auto padding = st::reactionInlinePadding;
 	const auto size = st::reactionInlineSize;
 	const auto skip = (size - st::reactionInlineImage) / 2;
-	const auto inbubble = (_data.flags & InlineListData::Flag::InBubble);
+	const auto tags = (_data.flags & Data::Flag::Tags);
+	const auto inbubble = (_data.flags & Data::Flag::InBubble);
 	const auto flipped = (_data.flags & Data::Flag::Flipped);
 	p.setFont(st::semiboldFont);
 	for (const auto &button : _buttons) {
@@ -366,21 +388,26 @@ void InlineList::paint(
 		if (bubbleProgress > 0.) {
 			auto hq = PainterHighQualityEnabler(p);
 			p.setPen(Qt::NoPen);
+			auto opacity = 1.;
+			auto color = QColor();
 			if (inbubble) {
 				if (!chosen) {
-					p.setOpacity(bubbleProgress * (context.outbg
+					opacity = bubbleProgress * (context.outbg
 						? kOutNonChosenOpacity
-						: kInNonChosenOpacity));
+						: kInNonChosenOpacity);
 				} else if (!bubbleReady) {
-					p.setOpacity(bubbleProgress);
+					opacity = bubbleProgress;
 				}
-				p.setBrush(stm->msgFileBg);
+				color = stm->msgFileBg->c;
 			} else {
 				if (!bubbleReady) {
-					p.setOpacity(bubbleProgress);
+					opacity = bubbleProgress;
 				}
-				p.setBrush(chosen ? st->msgServiceFg() : st->msgServiceBg());
+				color = (chosen
+					? st->msgServiceFg()
+					: st->msgServiceBg())->c;
 			}
+
 			const auto radius = geometry.height() / 2.;
 			const auto fill = geometry.marginsAdded({
 				flipped ? bubbleSkip : 0,
@@ -388,7 +415,7 @@ void InlineList::paint(
 				flipped ? 0 : bubbleSkip,
 				0,
 			});
-			p.drawRoundedRect(fill, radius, radius);
+			paintSingleBg(p, fill, color, opacity);
 			if (inbubble && !chosen) {
 				p.setOpacity(bubbleProgress);
 			}
@@ -434,7 +461,8 @@ void InlineList::paint(
 				.target = image,
 			});
 		}
-		if (bubbleProgress == 0.) {
+		if (tags || bubbleProgress == 0.) {
+			p.setOpacity(1.);
 			continue;
 		}
 		resolveUserpicsImage(button);
@@ -479,6 +507,115 @@ void InlineList::paint(
 	}
 }
 
+void InlineList::validateTagBg(const QColor &color) const {
+	if (!_tagBg.isNull() && _tagBgColor == color) {
+		return;
+	}
+	_tagBgColor = color;
+
+	const auto padding = st::reactionInlinePadding;
+	const auto size = st::reactionInlineSize;
+	const auto width = padding.left()
+		+ size
+		+ st::reactionInlineTagSkip
+		+ padding.right();
+	const auto height = padding.top() + size + padding.bottom();
+	const auto ratio = style::DevicePixelRatio();
+
+	auto mask = QImage(
+		QSize(width, height) * ratio,
+		QImage::Format_ARGB32_Premultiplied);
+	mask.setDevicePixelRatio(ratio);
+
+	mask.fill(Qt::transparent);
+	auto p = QPainter(&mask);
+
+	auto path = QPainterPath();
+	const auto arrow = st::reactionInlineTagArrow;
+	const auto rradius = st::reactionInlineTagRightRadius * 1.;
+	const auto radius = st::reactionInlineTagLeftRadius - rradius;
+	const auto fg = QColor(255, 255, 255);
+	auto pen = QPen(fg);
+	pen.setWidthF(rradius * 2.);
+	pen.setJoinStyle(Qt::RoundJoin);
+	const auto rect = QRectF(0, 0, width, height).marginsRemoved(
+		{ rradius, rradius, rradius, rradius });
+
+	const auto right = rect.x() + rect.width();
+	const auto bottom = rect.y() + rect.height();
+	path.moveTo(rect.x() + radius, rect.y());
+	path.lineTo(right - arrow, rect.y());
+	path.lineTo(right, rect.y() + rect.height() / 2);
+	path.lineTo(right - arrow, bottom);
+	path.lineTo(rect.x() + radius, bottom);
+	path.arcTo(QRectF(rect.x(), bottom - radius * 2, radius * 2, radius * 2), 270, -90);
+	path.lineTo(rect.x(), rect.y() + radius);
+	path.arcTo(QRectF(rect.x(), rect.y(), radius * 2, radius * 2), 180, -90);
+	path.closeSubpath();
+
+	const auto dsize = st::reactionInlineTagDot;
+	const auto dot = QRectF(
+		right - st::reactionInlineTagDotSkip - dsize,
+		rect.y() + (rect.height() - dsize) / 2.,
+		dsize,
+		dsize);
+
+	auto hq = PainterHighQualityEnabler(p);
+	p.setCompositionMode(QPainter::CompositionMode_Source);
+	p.setPen(pen);
+	p.setBrush(fg);
+	p.drawPath(path);
+
+	p.setPen(Qt::NoPen);
+	p.setBrush(QColor(255, 255, 255, 255 * 0.6));
+	p.drawEllipse(dot);
+
+	p.end();
+
+	_tagBg = style::colorizeImage(mask, color);
+}
+
+void InlineList::paintSingleBg(
+		Painter &p,
+		const QRect &fill,
+		const QColor &color,
+		float64 opacity) const {
+	p.setOpacity(opacity);
+	if (!(_data.flags & Data::Flag::Tags)) {
+		const auto radius = fill.height() / 2.;
+		p.setBrush(color);
+		p.drawRoundedRect(fill, radius, radius);
+		return;
+	}
+	validateTagBg(color);
+	const auto ratio = style::DevicePixelRatio();
+	const auto left = st::reactionInlineTagLeftRadius;
+	const auto right = (_tagBg.width() / ratio) - left;
+	Assert(right > 0);
+	const auto useLeft = std::min(fill.width(), left);
+	p.drawImage(
+		QRect(fill.x(), fill.y(), useLeft, fill.height()),
+		_tagBg,
+		QRect(0, 0, useLeft * ratio, _tagBg.height()));
+	const auto middle = fill.width() - left - right;
+	if (middle > 0) {
+		p.fillRect(fill.x() + left, fill.y(), middle, fill.height(), color);
+	}
+	if (const auto useRight = fill.width() - left; useRight > 0) {
+		p.drawImage(
+			QRect(
+				fill.x() + fill.width() - useRight,
+				fill.y(),
+				useRight,
+				fill.height()),
+			_tagBg,
+			QRect(_tagBg.width() - useRight * ratio,
+				0,
+				useRight * ratio,
+				_tagBg.height()));
+	}
+}
+
 bool InlineList::getState(
 		QPoint point,
 		not_null<TextState*> outResult) const {
@@ -654,7 +791,8 @@ InlineListData InlineListDataFromMessage(not_null<Message*> message) {
 		}
 	}
 	result.flags = (message->hasOutLayout() ? Flag::OutLayout : Flag())
-		| (message->embedReactionsInBubble() ? Flag::InBubble : Flag());
+		| (message->embedReactionsInBubble() ? Flag::InBubble : Flag())
+		| (item->reactionsAreTags() ? Flag::Tags : Flag());
 	return result;
 }
 
diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions.h b/Telegram/SourceFiles/history/view/reactions/history_view_reactions.h
index 398ec0cd8..cc4fa80d8 100644
--- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions.h
+++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions.h
@@ -41,6 +41,7 @@ struct InlineListData {
 		InBubble  = 0x01,
 		OutLayout = 0x02,
 		Flipped   = 0x04,
+		Tags      = 0x08,
 	};
 	friend inline constexpr bool is_flag_type(Flag) { return true; };
 	using Flags = base::flags<Flag>;
@@ -103,6 +104,7 @@ private:
 	void layout();
 	void layoutButtons();
 
+	void setButtonTag(Button &button);
 	void setButtonCount(Button &button, int count);
 	void setButtonUserpics(
 		Button &button,
@@ -115,6 +117,13 @@ private:
 		QPoint innerTopLeft,
 		const PaintContext &context,
 		const QColor &textColor) const;
+	void paintSingleBg(
+		Painter &p,
+		const QRect &fill,
+		const QColor &color,
+		float64 opacity) const;
+
+	void validateTagBg(const QColor &color) const;
 
 	QSize countOptimalSize() override;
 
@@ -124,6 +133,8 @@ private:
 	Data _data;
 	std::vector<Button> _buttons;
 	QSize _skipBlock;
+	mutable QImage _tagBg;
+	mutable QColor _tagBgColor;
 	mutable QImage _customCache;
 	mutable int _customSkip = 0;
 	bool _hasCustomEmoji = false;
diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style
index 7507e0e6b..bc54f7366 100644
--- a/Telegram/SourceFiles/ui/chat/chat.style
+++ b/Telegram/SourceFiles/ui/chat/chat.style
@@ -851,6 +851,12 @@ reactionInlinePadding: margins(5px, 2px, 7px, 2px);
 reactionInlineSize: 18px;
 reactionInlineImage: 32px;
 reactionInlineSkip: 3px;
+reactionInlineTagSkip: 6px;
+reactionInlineTagLeftRadius: 6px;
+reactionInlineTagRightRadius: 3px;
+reactionInlineTagArrow: 5px;
+reactionInlineTagDot: 5px;
+reactionInlineTagDotSkip: 2px;
 reactionInlineBetween: 4px;
 reactionInlineInBubbleLeft: -3px;
 reactionInlineUserpicsPadding: margins(1px, 1px, 1px, 1px);
diff --git a/Telegram/SourceFiles/ui/empty_userpic.cpp b/Telegram/SourceFiles/ui/empty_userpic.cpp
index d2b11df2c..1eebf7a11 100644
--- a/Telegram/SourceFiles/ui/empty_userpic.cpp
+++ b/Telegram/SourceFiles/ui/empty_userpic.cpp
@@ -440,7 +440,7 @@ void EmptyUserpic::PaintHiddenAuthor(
 		{ 0., st::premiumButtonBg2->c },
 		{ 1., st::premiumButtonBg3->c },
 	});
-	const auto &fg = st::historyPeerUserpicFg;
+	const auto &fg = st::premiumButtonFg;
 	PaintHiddenAuthor(p, x, y, outerWidth, size, QBrush(bg), fg);
 }
 

From 9aacff8b540843cb6765013c59f16c103e0f51c6 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Thu, 4 Jan 2024 11:47:59 +0400
Subject: [PATCH 20/73] Request correct saved/default reaction tags.

---
 Telegram/SourceFiles/api/api_updates.cpp      |   4 +
 .../data/data_message_reactions.cpp           | 177 +++++++++++++++++-
 .../SourceFiles/data/data_message_reactions.h |  36 ++++
 .../history_view_reactions_button.cpp         |   4 +-
 .../SourceFiles/window/section_widget.cpp     |  20 +-
 5 files changed, 227 insertions(+), 14 deletions(-)

diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp
index c9714d67e..dde1a68cd 100644
--- a/Telegram/SourceFiles/api/api_updates.cpp
+++ b/Telegram/SourceFiles/api/api_updates.cpp
@@ -2508,6 +2508,10 @@ void Updates::feedUpdate(const MTPUpdate &update) {
 		session().data().reactions().refreshRecentDelayed();
 	} break;
 
+	case mtpc_updateSavedReactionTags: {
+		session().data().reactions().refreshMyTagsDelayed();
+	} break;
+
 	////// Cloud saved GIFs
 	case mtpc_updateSavedGifs: {
 		session().data().stickers().setLastSavedGifsUpdate(0);
diff --git a/Telegram/SourceFiles/data/data_message_reactions.cpp b/Telegram/SourceFiles/data/data_message_reactions.cpp
index 9dc677fbf..b6eadde69 100644
--- a/Telegram/SourceFiles/data/data_message_reactions.cpp
+++ b/Telegram/SourceFiles/data/data_message_reactions.cpp
@@ -38,6 +38,7 @@ constexpr auto kPollEach = 20 * crl::time(1000);
 constexpr auto kSizeForDownscale = 64;
 constexpr auto kRecentRequestTimeout = 10 * crl::time(1000);
 constexpr auto kRecentReactionsLimit = 40;
+constexpr auto kMyTagsRequestTimeout = crl::time(1000);
 constexpr auto kTopRequestDelay = 60 * crl::time(1000);
 constexpr auto kTopReactionsLimit = 14;
 
@@ -64,6 +65,27 @@ constexpr auto kTopReactionsLimit = 14;
 	return result;
 }
 
+[[nodiscard]] std::vector<MyTagInfo> ListFromMTP(
+		const MTPDmessages_savedReactionTags &data) {
+	const auto &list = data.vtags().v;
+	auto result = std::vector<MyTagInfo>();
+	result.reserve(list.size());
+	for (const auto &reaction : list) {
+		const auto &data = reaction.data();
+		const auto id = ReactionFromMTP(data.vreaction());
+		if (id.empty()) {
+			LOG(("API Error: reactionEmpty in messages.reactions."));
+		} else {
+			result.push_back({
+				.id = id,
+				.title = qs(data.vtitle().value_or_empty()),
+				.count = data.vcount().v,
+			});
+		}
+	}
+	return result;
+}
+
 [[nodiscard]] Reaction CustomReaction(not_null<DocumentData*> document) {
 	return Reaction{
 		.id = { { document->id } },
@@ -121,6 +143,8 @@ PossibleItemReactionsRef LookupPossibleReactions(
 	const auto &full = reactions->list(Reactions::Type::Active);
 	const auto &top = reactions->list(Reactions::Type::Top);
 	const auto &recent = reactions->list(Reactions::Type::Recent);
+	const auto &myTags = reactions->list(Reactions::Type::MyTags);
+	const auto &tags = reactions->list(Reactions::Type::Tags);
 	const auto &all = item->reactions();
 	const auto limit = UniqueReactionsLimit(peer);
 	const auto premiumPossible = session->premiumPossible();
@@ -143,7 +167,19 @@ PossibleItemReactionsRef LookupPossibleReactions(
 		}
 	};
 	reactions->clearTemporary();
-	if (limited) {
+	if (item->reactionsAreTags()) {
+		auto &&all = ranges::views::concat(myTags, tags);
+		result.recent.reserve(myTags.size() + tags.size());
+		for (const auto &reaction : all) {
+			if (premiumPossible
+				|| ranges::contains(tags, reaction.id, &Reaction::id)) {
+				if (added.emplace(reaction.id).second) {
+					result.recent.push_back(&reaction);
+				}
+			}
+		}
+		result.customAllowed = premiumPossible;
+	} else if (limited) {
 		result.recent.reserve(all.size());
 		add([&](const Reaction &reaction) {
 			return ranges::contains(all, reaction.id, &MessageReaction::id);
@@ -193,12 +229,14 @@ PossibleItemReactionsRef LookupPossibleReactions(
 		result.customAllowed = (allowed.type == AllowedReactionsType::All)
 			&& premiumPossible;
 	}
-	const auto i = ranges::find(
-		result.recent,
-		reactions->favoriteId(),
-		&Reaction::id);
-	if (i != end(result.recent) && i != begin(result.recent)) {
-		std::rotate(begin(result.recent), i, i + 1);
+	if (!item->reactionsAreTags()) {
+		const auto i = ranges::find(
+			result.recent,
+			reactions->favoriteId(),
+			&Reaction::id);
+		if (i != end(result.recent) && i != begin(result.recent)) {
+			std::rotate(begin(result.recent), i, i + 1);
+		}
 	}
 	return result;
 }
@@ -280,16 +318,42 @@ void Reactions::refreshDefault() {
 	requestDefault();
 }
 
+void Reactions::refreshMyTags() {
+	requestMyTags();
+}
+
+void Reactions::refreshMyTagsDelayed() {
+	if (_myTagsRequestId || _myTagsRequestScheduled) {
+		return;
+	}
+	_myTagsRequestScheduled = true;
+	base::call_delayed(kMyTagsRequestTimeout, &_owner->session(), [=] {
+		if (_myTagsRequestScheduled) {
+			requestMyTags();
+		}
+	});
+}
+
+void Reactions::refreshTags() {
+	requestTags();
+}
+
 const std::vector<Reaction> &Reactions::list(Type type) const {
 	switch (type) {
 	case Type::Active: return _active;
 	case Type::Recent: return _recent;
 	case Type::Top: return _top;
 	case Type::All: return _available;
+	case Type::MyTags: return _myTags;
+	case Type::Tags: return _tags;
 	}
 	Unexpected("Type in Reactions::list.");
 }
 
+const std::vector<MyTagInfo> &Reactions::myTagsInfo() const {
+	return _myTagsInfo;
+}
+
 ReactionId Reactions::favoriteId() const {
 	return _favoriteId;
 }
@@ -375,6 +439,14 @@ rpl::producer<> Reactions::favoriteUpdates() const {
 	return _favoriteUpdated.events();
 }
 
+rpl::producer<> Reactions::myTagsUpdates() const {
+	return _myTagsUpdated.events();
+}
+
+rpl::producer<> Reactions::tagsUpdates() const {
+	return _tagsUpdated.events();
+}
+
 void Reactions::preloadImageFor(const ReactionId &id) {
 	if (_images.contains(id) || id.emoji().isEmpty()) {
 		return;
@@ -617,6 +689,46 @@ void Reactions::requestGeneric() {
 	}).send();
 }
 
+void Reactions::requestMyTags() {
+	if (_myTagsRequestId) {
+		return;
+	}
+	auto &api = _owner->session().api();
+	_myTagsRequestScheduled = false;
+	_myTagsRequestId = api.request(MTPmessages_GetSavedReactionTags(
+		MTP_long(_myTagsHash)
+	)).done([=](const MTPmessages_SavedReactionTags &result) {
+		_myTagsRequestId = 0;
+		result.match([&](const MTPDmessages_savedReactionTags &data) {
+			updateMyTags(data);
+		}, [](const MTPDmessages_savedReactionTagsNotModified&) {
+		});
+	}).fail([=] {
+		_myTagsRequestId = 0;
+		_myTagsHash = 0;
+	}).send();
+}
+
+void Reactions::requestTags() {
+	if (_tagsRequestId) {
+		return;
+	}
+	auto &api = _owner->session().api();
+	_tagsRequestId = api.request(MTPmessages_GetDefaultTagReactions(
+		MTP_long(_tagsHash)
+	)).done([=](const MTPmessages_Reactions &result) {
+		_tagsRequestId = 0;
+		result.match([&](const MTPDmessages_reactions &data) {
+			updateTags(data);
+		}, [](const MTPDmessages_reactionsNotModified&) {
+		});
+	}).fail([=] {
+		_tagsRequestId = 0;
+		_tagsHash = 0;
+	}).send();
+
+}
+
 void Reactions::updateTop(const MTPDmessages_reactions &data) {
 	_topHash = data.vhash().v;
 	_topIds = ListFromMTP(data);
@@ -685,6 +797,23 @@ void Reactions::updateGeneric(const MTPDmessages_stickerSet &data) {
 	}
 }
 
+void Reactions::updateMyTags(const MTPDmessages_savedReactionTags &data) {
+	_myTagsHash = data.vhash().v;
+	_myTagsInfo = ListFromMTP(data);
+	_myTagsIds = _myTagsInfo | ranges::views::transform(
+		&MyTagInfo::id
+	) | ranges::to_vector;
+	_myTags = resolveByIds(_myTagsIds, _unresolvedMyTags);
+	_myTagsUpdated.fire({});
+}
+
+void Reactions::updateTags(const MTPDmessages_reactions &data) {
+	_tagsHash = data.vhash().v;
+	_tagsIds = ListFromMTP(data);
+	_tags = resolveByIds(_tagsIds, _unresolvedTags);
+	_tagsUpdated.fire({});
+}
+
 void Reactions::recentUpdated() {
 	_topRefreshTimer.callOnce(kTopRequestDelay);
 	_recentUpdated.fire({});
@@ -696,9 +825,25 @@ void Reactions::defaultUpdated() {
 	if (_genericAnimations.empty()) {
 		requestGeneric();
 	}
+	refreshMyTags();
+	refreshTags();
 	_defaultUpdated.fire({});
 }
 
+void Reactions::myTagsUpdated() {
+	if (_genericAnimations.empty()) {
+		requestGeneric();
+	}
+	_myTagsUpdated.fire({});
+}
+
+void Reactions::tagsUpdated() {
+	if (_genericAnimations.empty()) {
+		requestGeneric();
+	}
+	_tagsUpdated.fire({});
+}
+
 not_null<CustomEmojiManager::Listener*> Reactions::resolveListener() {
 	return static_cast<CustomEmojiManager::Listener*>(this);
 }
@@ -710,6 +855,10 @@ void Reactions::customEmojiResolveDone(not_null<DocumentData*> document) {
 	const auto top = (i != end(_unresolvedTop));
 	const auto j = _unresolvedRecent.find(id);
 	const auto recent = (j != end(_unresolvedRecent));
+	const auto k = _unresolvedMyTags.find(id);
+	const auto myTag = (k != end(_unresolvedMyTags));
+	const auto l = _unresolvedTags.find(id);
+	const auto tag = (l != end(_unresolvedTags));
 	if (favorite) {
 		_unresolvedFavoriteId = ReactionId();
 		_favorite = resolveById(_favoriteId);
@@ -722,6 +871,14 @@ void Reactions::customEmojiResolveDone(not_null<DocumentData*> document) {
 		_unresolvedRecent.erase(j);
 		_recent = resolveByIds(_recentIds, _unresolvedRecent);
 	}
+	if (myTag) {
+		_unresolvedMyTags.erase(k);
+		_myTags = resolveByIds(_myTagsIds, _unresolvedMyTags);
+	}
+	if (tag) {
+		_unresolvedTags.erase(l);
+		_tags = resolveByIds(_tagsIds, _unresolvedTags);
+	}
 	if (favorite) {
 		_favoriteUpdated.fire({});
 	}
@@ -731,6 +888,12 @@ void Reactions::customEmojiResolveDone(not_null<DocumentData*> document) {
 	if (recent) {
 		_recentUpdated.fire({});
 	}
+	if (myTag) {
+		_myTagsUpdated.fire({});
+	}
+	if (tag) {
+		_tagsUpdated.fire({});
+	}
 }
 
 std::optional<Reaction> Reactions::resolveById(const ReactionId &id) {
diff --git a/Telegram/SourceFiles/data/data_message_reactions.h b/Telegram/SourceFiles/data/data_message_reactions.h
index 61b29107d..e43538a11 100644
--- a/Telegram/SourceFiles/data/data_message_reactions.h
+++ b/Telegram/SourceFiles/data/data_message_reactions.h
@@ -56,6 +56,12 @@ struct PossibleItemReactions {
 [[nodiscard]] PossibleItemReactionsRef LookupPossibleReactions(
 	not_null<HistoryItem*> item);
 
+struct MyTagInfo {
+	ReactionId id;
+	QString title;
+	int count = 0;
+};
+
 class Reactions final : private CustomEmojiManager::Listener {
 public:
 	explicit Reactions(not_null<Session*> owner);
@@ -70,14 +76,20 @@ public:
 	void refreshRecent();
 	void refreshRecentDelayed();
 	void refreshDefault();
+	void refreshMyTags();
+	void refreshMyTagsDelayed();
+	void refreshTags();
 
 	enum class Type {
 		Active,
 		Recent,
 		Top,
 		All,
+		MyTags,
+		Tags,
 	};
 	[[nodiscard]] const std::vector<Reaction> &list(Type type) const;
+	[[nodiscard]] const std::vector<MyTagInfo> &myTagsInfo() const;
 	[[nodiscard]] ReactionId favoriteId() const;
 	[[nodiscard]] const Reaction *favorite() const;
 	void setFavorite(const ReactionId &id);
@@ -88,6 +100,8 @@ public:
 	[[nodiscard]] rpl::producer<> recentUpdates() const;
 	[[nodiscard]] rpl::producer<> defaultUpdates() const;
 	[[nodiscard]] rpl::producer<> favoriteUpdates() const;
+	[[nodiscard]] rpl::producer<> myTagsUpdates() const;
+	[[nodiscard]] rpl::producer<> tagsUpdates() const;
 
 	enum class ImageSize {
 		BottomInfo,
@@ -130,14 +144,20 @@ private:
 	void requestRecent();
 	void requestDefault();
 	void requestGeneric();
+	void requestMyTags();
+	void requestTags();
 
 	void updateTop(const MTPDmessages_reactions &data);
 	void updateRecent(const MTPDmessages_reactions &data);
 	void updateDefault(const MTPDmessages_availableReactions &data);
 	void updateGeneric(const MTPDmessages_stickerSet &data);
+	void updateMyTags(const MTPDmessages_savedReactionTags &data);
+	void updateTags(const MTPDmessages_reactions &data);
 
 	void recentUpdated();
 	void defaultUpdated();
+	void myTagsUpdated();
+	void tagsUpdated();
 
 	[[nodiscard]] std::optional<Reaction> resolveById(const ReactionId &id);
 	[[nodiscard]] std::vector<Reaction> resolveByIds(
@@ -167,6 +187,13 @@ private:
 	std::vector<Reaction> _recent;
 	std::vector<ReactionId> _recentIds;
 	base::flat_set<ReactionId> _unresolvedRecent;
+	std::vector<Reaction> _myTags;
+	std::vector<ReactionId> _myTagsIds;
+	std::vector<MyTagInfo> _myTagsInfo;
+	base::flat_set<ReactionId> _unresolvedMyTags;
+	std::vector<Reaction> _tags;
+	std::vector<ReactionId> _tagsIds;
+	base::flat_set<ReactionId> _unresolvedTags;
 	std::vector<Reaction> _top;
 	std::vector<ReactionId> _topIds;
 	base::flat_set<ReactionId> _unresolvedTop;
@@ -184,6 +211,8 @@ private:
 	rpl::event_stream<> _recentUpdated;
 	rpl::event_stream<> _defaultUpdated;
 	rpl::event_stream<> _favoriteUpdated;
+	rpl::event_stream<> _myTagsUpdated;
+	rpl::event_stream<> _tagsUpdated;
 
 	// We need &i->second stay valid while inserting new items.
 	// So we use std::map instead of base::flat_map here.
@@ -203,6 +232,13 @@ private:
 
 	mtpRequestId _genericRequestId = 0;
 
+	mtpRequestId _myTagsRequestId = 0;
+	bool _myTagsRequestScheduled = false;
+	uint64 _myTagsHash = 0;
+
+	mtpRequestId _tagsRequestId = 0;
+	uint64 _tagsHash = 0;
+
 	base::flat_map<ReactionId, ImageSet> _images;
 	rpl::lifetime _imagesLoadLifetime;
 	bool _waitingForList = false;
diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_button.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_button.cpp
index 91ad93484..2081a85e7 100644
--- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_button.cpp
+++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_button.cpp
@@ -895,7 +895,9 @@ void SetupManagerList(
 				reactions.topUpdates(),
 				reactions.recentUpdates(),
 				reactions.defaultUpdates(),
-				reactions.favoriteUpdates()
+				reactions.favoriteUpdates(),
+				reactions.myTagsUpdates(),
+				reactions.tagsUpdates()
 			) | rpl::start_with_next([=] {
 				if (!state->timer.isActive()) {
 					state->timer.callOnce(kRefreshListDelay);
diff --git a/Telegram/SourceFiles/window/section_widget.cpp b/Telegram/SourceFiles/window/section_widget.cpp
index 47e1196ea..8da39b10e 100644
--- a/Telegram/SourceFiles/window/section_widget.cpp
+++ b/Telegram/SourceFiles/window/section_widget.cpp
@@ -529,14 +529,22 @@ bool ShowReactPremiumError(
 		|| ranges::contains(item->chosenReactions(), id)
 		|| item->history()->peer->isBroadcast()) {
 		return false;
-	}
-	const auto &list = controller->session().data().reactions().list(
-		Data::Reactions::Type::Active);
-	const auto i = ranges::find(list, id, &Data::Reaction::id);
-	if (i == end(list) || !i->premium) {
-		if (!id.custom()) {
+	} else if (item->reactionsAreTags()) {
+		const auto &list = controller->session().data().reactions().list(
+			Data::Reactions::Type::Tags);
+		const auto i = ranges::find(list, id, &Data::Reaction::id);
+		if (i != end(list)) {
 			return false;
 		}
+	} else {
+		const auto &list = controller->session().data().reactions().list(
+			Data::Reactions::Type::Active);
+		const auto i = ranges::find(list, id, &Data::Reaction::id);
+		if (i == end(list) || !i->premium) {
+			if (!id.custom()) {
+				return false;
+			}
+		}
 	}
 	ShowPremiumPreviewBox(controller, PremiumPreview::InfiniteReactions);
 	return true;

From e667436a98cb9c766a3b24cf23d53af7c96095f0 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Thu, 4 Jan 2024 12:08:49 +0400
Subject: [PATCH 21/73] Track my tags usages.

---
 .../data/data_message_reactions.cpp           | 57 +++++++++++++++++++
 .../SourceFiles/data/data_message_reactions.h |  4 ++
 2 files changed, 61 insertions(+)

diff --git a/Telegram/SourceFiles/data/data_message_reactions.cpp b/Telegram/SourceFiles/data/data_message_reactions.cpp
index b6eadde69..9e1726b7a 100644
--- a/Telegram/SourceFiles/data/data_message_reactions.cpp
+++ b/Telegram/SourceFiles/data/data_message_reactions.cpp
@@ -378,6 +378,56 @@ void Reactions::setFavorite(const ReactionId &id) {
 	applyFavorite(id);
 }
 
+void Reactions::incrementMyTag(const ReactionId &id) {
+	auto i = ranges::find(_myTagsInfo, id, &MyTagInfo::id);
+	if (i == end(_myTagsInfo)) {
+		_myTagsInfo.push_back({ .id = id, .count = 0 });
+		i = end(_myTagsInfo) - 1;
+	}
+	++i->count;
+	while (i != begin(_myTagsInfo)) {
+		auto j = i - 1;
+		if (j->count >= i->count) {
+			break;
+		}
+		std::swap(*i, *j);
+		i = j;
+	}
+	scheduleMyTagsUpdate();
+}
+
+void Reactions::decrementMyTag(const ReactionId &id) {
+	auto i = ranges::find(_myTagsInfo, id, &MyTagInfo::id);
+	if (i->count <= 0) {
+		return;
+	}
+	--i->count;
+	while (i + 1 != end(_myTagsInfo)) {
+		auto j = i + 1;
+		if (j->count <= i->count) {
+			break;
+		}
+		std::swap(*i, *j);
+		i = j;
+	}
+	scheduleMyTagsUpdate();
+}
+
+void Reactions::scheduleMyTagsUpdate() {
+	_myTagsUpdateScheduled = true;
+	crl::on_main(&session(), [=] {
+		if (!_myTagsUpdateScheduled) {
+			return;
+		}
+		_myTagsUpdateScheduled = false;
+		_myTagsIds = _myTagsInfo | ranges::views::transform(
+			&MyTagInfo::id
+		) | ranges::to_vector;
+		_myTags = resolveByIds(_myTagsIds, _unresolvedMyTags);
+		_myTagsUpdated.fire({});
+	});
+}
+
 DocumentData *Reactions::chooseGenericAnimation(
 		not_null<DocumentData*> custom) const {
 	const auto sticker = custom->sticker();
@@ -1155,6 +1205,10 @@ void MessageReactions::add(const ReactionId &id, bool addToRecent) {
 		return;
 	}
 	auto my = 0;
+	const auto tags = _item->reactionsAreTags();
+	if (tags) {
+		history->owner().reactions().incrementMyTag(id);
+	}
 	_list.erase(ranges::remove_if(_list, [&](MessageReaction &one) {
 		const auto removing = one.my && (my == myLimit || ++my == myLimit);
 		if (!removing) {
@@ -1176,6 +1230,9 @@ void MessageReactions::add(const ReactionId &id, bool addToRecent) {
 				}
 			}
 		}
+		if (tags) {
+			history->owner().reactions().decrementMyTag(one.id);
+		}
 		return removed;
 	}), end(_list));
 	const auto peer = history->peer;
diff --git a/Telegram/SourceFiles/data/data_message_reactions.h b/Telegram/SourceFiles/data/data_message_reactions.h
index e43538a11..2743bffc8 100644
--- a/Telegram/SourceFiles/data/data_message_reactions.h
+++ b/Telegram/SourceFiles/data/data_message_reactions.h
@@ -93,6 +93,8 @@ public:
 	[[nodiscard]] ReactionId favoriteId() const;
 	[[nodiscard]] const Reaction *favorite() const;
 	void setFavorite(const ReactionId &id);
+	void incrementMyTag(const ReactionId &id);
+	void decrementMyTag(const ReactionId &id);
 	[[nodiscard]] DocumentData *chooseGenericAnimation(
 		not_null<DocumentData*> custom) const;
 
@@ -165,6 +167,7 @@ private:
 		base::flat_set<ReactionId> &unresolved);
 	void resolve(const ReactionId &id);
 	void applyFavorite(const ReactionId &id);
+	void scheduleMyTagsUpdate();
 
 	[[nodiscard]] std::optional<Reaction> parse(
 		const MTPAvailableReaction &entry);
@@ -234,6 +237,7 @@ private:
 
 	mtpRequestId _myTagsRequestId = 0;
 	bool _myTagsRequestScheduled = false;
+	bool _myTagsUpdateScheduled = false;
 	uint64 _myTagsHash = 0;
 
 	mtpRequestId _tagsRequestId = 0;

From 9c151ca151b20a7400e13c48064f853c111cd243 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Fri, 5 Jan 2024 11:25:45 +0400
Subject: [PATCH 22/73] Allow filtering Saved Messages search by tags.

---
 Telegram/CMakeLists.txt                       |   2 +
 Telegram/SourceFiles/dialogs/dialogs.style    |   3 +
 .../dialogs/dialogs_inner_widget.cpp          | 115 +++++++-
 .../dialogs/dialogs_inner_widget.h            |   8 +
 .../dialogs/dialogs_search_tags.cpp           | 246 ++++++++++++++++++
 .../SourceFiles/dialogs/dialogs_search_tags.h |  78 ++++++
 .../SourceFiles/dialogs/dialogs_widget.cpp    |  49 +++-
 Telegram/SourceFiles/dialogs/dialogs_widget.h |   4 +
 .../view/reactions/history_view_reactions.cpp |  46 ++--
 .../view/reactions/history_view_reactions.h   |   3 +
 10 files changed, 518 insertions(+), 36 deletions(-)
 create mode 100644 Telegram/SourceFiles/dialogs/dialogs_search_tags.cpp
 create mode 100644 Telegram/SourceFiles/dialogs/dialogs_search_tags.h

diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt
index 4f9db7939..59c701340 100644
--- a/Telegram/CMakeLists.txt
+++ b/Telegram/CMakeLists.txt
@@ -609,6 +609,8 @@ PRIVATE
     dialogs/dialogs_row.h
     dialogs/dialogs_search_from_controllers.cpp
     dialogs/dialogs_search_from_controllers.h
+    dialogs/dialogs_search_tags.cpp
+    dialogs/dialogs_search_tags.h
     dialogs/dialogs_widget.cpp
     dialogs/dialogs_widget.h
     dialogs/ui/dialogs_layout.cpp
diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style
index 055675b2d..c45ea636f 100644
--- a/Telegram/SourceFiles/dialogs/dialogs.style
+++ b/Telegram/SourceFiles/dialogs/dialogs.style
@@ -623,3 +623,6 @@ dialogsStoriesTooltipHide: IconButton(defaultIconButton) {
 searchedBarHeight: 32px;
 searchedBarFont: normalFont;
 searchedBarPosition: point(17px, 7px);
+
+dialogsSearchTagSkip: point(8px, 4px);
+dialogsSearchTagBottom: 10px;
diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
index afbf0ca77..a9f1d1b41 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
@@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "dialogs/dialogs_indexed_list.h"
 #include "dialogs/dialogs_widget.h"
 #include "dialogs/dialogs_search_from_controllers.h"
+#include "dialogs/dialogs_search_tags.h"
 #include "history/history.h"
 #include "history/history_item.h"
 #include "core/shortcuts.h"
@@ -40,6 +41,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_chat_filters.h"
 #include "data/data_cloud_file.h"
 #include "data/data_changes.h"
+#include "data/data_message_reactions.h"
 #include "data/data_saved_messages.h"
 #include "data/data_stories.h"
 #include "data/stickers/data_stickers.h"
@@ -477,18 +479,26 @@ int InnerWidget::peerSearchOffset() const {
 		+ st::searchedBarHeight;
 }
 
-int InnerWidget::searchedOffset() const {
-	auto result = peerSearchOffset();
+int InnerWidget::searchInChatOffset() const {
+	auto result = peerSearchOffset() - st::searchedBarHeight;
 	if (!_peerSearchResults.empty()) {
 		result += (_peerSearchResults.size() * st::dialogsRowHeight)
 			+ st::searchedBarHeight;
 	}
-	result += searchInChatSkip();
 	return result;
 }
 
+int InnerWidget::searchedOffset() const {
+	return searchInChatOffset()
+		+ searchInChatSkip()
+		+ st::searchedBarHeight;
+}
+
 int InnerWidget::searchInChatSkip() const {
 	auto result = 0;
+	if (_searchTags) {
+		result += _searchTags->height();
+	}
 	if (_searchInChat) {
 		result += st::searchedBarHeight + st::dialogsSearchInHeight;
 	}
@@ -1111,12 +1121,20 @@ void InnerWidget::paintSearchInChat(
 	auto height = searchInChatSkip();
 
 	auto top = 0;
+	if (_searchTags) {
+		const auto height = _searchTags->height();
+		p.fillRect(0, top, width(), height, currentBg());
+		const auto position = QPoint(_searchTagsLeft, 0);
+		_searchTags->paint(p, position, context.now, context.paused);
+		top += height;
+	}
 	p.setFont(st::searchedBarFont);
 	if (_searchInChat) {
-		top += st::searchedBarHeight;
-		p.fillRect(0, 0, width(), top, st::searchedBarBg);
+		const auto bar = st::searchedBarHeight;
+		p.fillRect(0, top, width(), top + bar, st::searchedBarBg);
 		p.setPen(st::searchedBarFg);
-		p.drawTextLeft(st::searchedBarPosition.x(), st::searchedBarPosition.y(), width(), tr::lng_dlg_search_in(tr::now));
+		p.drawTextLeft(st::searchedBarPosition.x(), top + st::searchedBarPosition.y(), width(), tr::lng_dlg_search_in(tr::now));
+		top += bar;
 	}
 	auto fullRect = QRect(0, top, width(), height - top);
 	p.fillRect(fullRect, currentBg());
@@ -1276,6 +1294,21 @@ void InnerWidget::selectByMouse(QPoint globalPosition) {
 	_lastMousePosition = globalPosition;
 	_lastRowLocalMouseX = local.x();
 
+	const auto tagBase = QPoint(_searchTagsLeft, searchInChatOffset());
+	const auto tagPoint = local - tagBase;
+	const auto inTags = _searchTags
+		&& QRect(
+			tagBase,
+			QSize(width() - 2 * _searchTagsLeft, _searchTags->height())
+		).contains(local);
+	const auto tagLink = inTags
+		? _searchTags->lookupHandler(tagPoint)
+		: nullptr;
+	ClickHandler::setActive(tagLink);
+	if (inTags) {
+		setCursor(tagLink ? style::cur_pointer : style::cur_default);
+	}
+
 	const auto w = width();
 	const auto mouseY = local.y();
 	clearIrrelevantState();
@@ -1370,7 +1403,7 @@ void InnerWidget::selectByMouse(QPoint globalPosition) {
 				updateSelectedRow();
 			}
 		}
-		if (wasSelected != isSelected()) {
+		if (!inTags && wasSelected != isSelected()) {
 			setCursor(wasSelected ? style::cur_default : style::cur_pointer);
 		}
 	}
@@ -1452,6 +1485,7 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) {
 			QSize(width(), _st->height),
 			row->repaint());
 	}
+	ClickHandler::pressed();
 	if (anim::Disabled()
 		&& (!_pressed || !_pressed->entry()->isPinnedDialog(_filterId))) {
 		mousePressReleased(e->globalPos(), e->button(), e->modifiers());
@@ -1743,6 +1777,9 @@ void InnerWidget::mousePressReleased(
 			chooseRow(modifiers, pressedTopicRootId);
 		}
 	}
+	if (auto activated = ClickHandler::unpressed()) {
+		ActivateClickHandler(window(), activated, { button });
+	}
 }
 
 void InnerWidget::setCollapsedPressed(int pressed) {
@@ -1825,9 +1862,10 @@ void InnerWidget::moveCancelSearchButtons() {
 		st::columnMinimalWidthLeft - _narrowWidth);
 	const auto left = widthForCancelButton - st::dialogsSearchInSkip - _cancelSearchInChat->width();
 	const auto top = (st::dialogsSearchInHeight - st::dialogsCancelSearchInPeer.height) / 2;
-	_cancelSearchInChat->moveToLeft(left, st::searchedBarHeight + top);
-	const auto skip = _searchInChat ? (st::searchedBarHeight + st::dialogsSearchInHeight + st::lineWidth) : 0;
-	_cancelSearchFromUser->moveToLeft(left, skip + top);
+	const auto skip = st::searchedBarHeight + (_searchTags ? _searchTags->height() : 0);
+	_cancelSearchInChat->moveToLeft(left, skip + top);
+	const auto next = _searchInChat ? (skip + st::dialogsSearchInHeight + st::lineWidth) : 0;
+	_cancelSearchFromUser->moveToLeft(left, next + top);
 }
 
 void InnerWidget::dialogRowReplaced(
@@ -2330,7 +2368,9 @@ void InnerWidget::applyFilterUpdate(QString newFilter, bool force) {
 	newFilter = words.isEmpty() ? QString() : words.join(' ');
 	if (newFilter != _filter || force) {
 		_filter = newFilter;
-		if (_filter.isEmpty() && !_searchFromPeer) {
+		if (_filter.isEmpty()
+			&& !_searchFromPeer
+			&& _searchTagsSelected.empty()) {
 			clearFilter();
 		} else {
 			setState(WidgetState::Filtered);
@@ -2350,7 +2390,9 @@ void InnerWidget::applyFilterUpdate(QString newFilter, bool force) {
 					top += i->row->height();
 				}
 			};
-			if (!_searchInChat && !_searchFromPeer && !words.isEmpty()) {
+			if (!_searchInChat
+				&& !_searchFromPeer
+				&& !words.isEmpty()) {
 				if (_savedSublists) {
 					const auto owner = &session().data();
 					append(owner->savedMessages().chatsList()->indexed());
@@ -2791,6 +2833,11 @@ void InnerWidget::refresh(bool toTop) {
 		return refreshWithCollapsedRows(toTop);
 	}
 	refreshEmptyLabel();
+	if (_searchTags) {
+		_searchTagsLeft = st::dialogsFilterSkip
+			+ st::dialogsFilterPadding.x();
+		_searchTags->resizeToWidth(width() - 2 * _searchTagsLeft);
+	}
 	auto h = 0;
 	if (_state == WidgetState::Default) {
 		if (_shownList->empty()) {
@@ -2926,6 +2973,43 @@ void InnerWidget::searchInChat(Key key, PeerData *from) {
 		} else if (const auto migrateFrom = peer->migrateFrom()) {
 			_searchInMigrated = peer->owner().history(migrateFrom);
 		}
+
+		if (peer->isSelf()) {
+			const auto reactions = &peer->owner().reactions();
+			const auto list = [=] {
+				return reactions->list(Data::Reactions::Type::MyTags);
+			};
+			_searchTags = std::make_unique<SearchTags>(
+				&peer->owner(),
+				rpl::single(
+					list()
+				) | rpl::then(
+					reactions->myTagsUpdates() | rpl::map(list)
+				));
+
+			_searchTags->selectedValue(
+			) | rpl::start_with_next([=](std::vector<Data::ReactionId> &&list) {
+				_searchTagsSelected = std::move(list);
+			}, _searchTags->lifetime());
+
+			_searchTags->repaintRequests() | rpl::start_with_next([=] {
+				const auto height = _searchTags->height();
+				update(0, searchInChatOffset(), width(), height);
+			}, _searchTags->lifetime());
+
+			_searchTags->heightValue() | rpl::filter(
+				rpl::mappers::_1 > 0
+			) | rpl::start_with_next([=] {
+				refresh();
+				moveCancelSearchButtons();
+			}, _searchTags->lifetime());
+		} else {
+			_searchTags = nullptr;
+			_searchTagsSelected.clear();
+		}
+	} else {
+		_searchTags = nullptr;
+		_searchTagsSelected.clear();
 	}
 	_searchInChat = key;
 	_searchFromPeer = from;
@@ -2957,6 +3041,13 @@ void InnerWidget::searchInChat(Key key, PeerData *from) {
 		_searchInChat || !_filter.isEmpty());
 }
 
+auto InnerWidget::searchTagsValue() const
+-> rpl::producer<std::vector<Data::ReactionId>> {
+	return _searchTags
+		? _searchTags->selectedValue()
+		: rpl::single(std::vector<Data::ReactionId>());
+}
+
 void InnerWidget::refreshSearchInChatLabel() {
 	const auto dialog = [&] {
 		if (const auto topic = _searchInChat.topic()) {
diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h
index 7915bd912..d511c65e8 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h
+++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h
@@ -43,6 +43,7 @@ namespace Data {
 class Thread;
 class Folder;
 class Forum;
+struct ReactionId;
 } // namespace Data
 
 namespace Dialogs::Ui {
@@ -57,6 +58,7 @@ namespace Dialogs {
 class Row;
 class FakeRow;
 class IndexedList;
+class SearchTags;
 
 struct ChosenRow {
 	Key key;
@@ -138,6 +140,8 @@ public:
 	[[nodiscard]] bool hasFilteredResults() const;
 
 	void searchInChat(Key key, PeerData *from);
+	[[nodiscard]] auto searchTagsValue() const
+		-> rpl::producer<std::vector<Data::ReactionId>>;
 
 	void applyFilterUpdate(QString newFilter, bool force = false);
 	void onHashtagFilterUpdate(QStringView newFilter);
@@ -325,6 +329,7 @@ private:
 	[[nodiscard]] int filteredIndex(int y) const;
 	[[nodiscard]] int filteredHeight(int till = -1) const;
 	[[nodiscard]] int peerSearchOffset() const;
+	[[nodiscard]] int searchInChatOffset() const;
 	[[nodiscard]] int searchedOffset() const;
 	[[nodiscard]] int searchInChatSkip() const;
 
@@ -482,6 +487,9 @@ private:
 	mutable Ui::PeerUserpicView _searchFromUserUserpic;
 	Ui::Text::String _searchInChatText;
 	Ui::Text::String _searchFromUserText;
+	std::unique_ptr<SearchTags> _searchTags;
+	std::vector<Data::ReactionId> _searchTagsSelected;
+	int _searchTagsLeft = 0;
 	RowDescriptor _menuRow;
 
 	base::flat_map<
diff --git a/Telegram/SourceFiles/dialogs/dialogs_search_tags.cpp b/Telegram/SourceFiles/dialogs/dialogs_search_tags.cpp
new file mode 100644
index 000000000..22a15ec55
--- /dev/null
+++ b/Telegram/SourceFiles/dialogs/dialogs_search_tags.cpp
@@ -0,0 +1,246 @@
+/*
+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 "dialogs/dialogs_search_tags.h"
+
+#include "data/stickers/data_custom_emoji.h"
+#include "data/data_document.h"
+#include "data/data_message_reactions.h"
+#include "data/data_session.h"
+#include "history/view/reactions/history_view_reactions.h"
+#include "ui/effects/animation_value.h"
+#include "ui/power_saving.h"
+#include "styles/style_chat.h"
+#include "styles/style_dialogs.h"
+
+namespace Dialogs {
+
+struct SearchTags::Tag {
+	Data::ReactionId id;
+	std::unique_ptr<Ui::Text::CustomEmoji> custom;
+	mutable QImage image;
+	QRect geometry;
+	ClickHandlerPtr link;
+	bool selected = false;
+};
+
+SearchTags::SearchTags(
+	not_null<Data::Session*> owner,
+	rpl::producer<std::vector<Data::Reaction>> tags)
+: _owner(owner) {
+	std::move(
+		tags
+	) | rpl::start_with_next([=](const std::vector<Data::Reaction> &list) {
+		fill(list);
+	}, _lifetime);
+
+	style::PaletteChanged(
+	) | rpl::start_with_next([=] {
+		_normalBg = _selectedBg = QImage();
+	}, _lifetime);
+}
+
+SearchTags::~SearchTags() = default;
+
+void SearchTags::fill(const std::vector<Data::Reaction> &list) {
+	const auto selected = collectSelected();
+	_tags.clear();
+	_tags.reserve(list.size());
+	const auto link = [&](Data::ReactionId id) {
+		return std::make_shared<LambdaClickHandler>(crl::guard(this, [=] {
+			const auto i = ranges::find(_tags, id, &Tag::id);
+			if (i != end(_tags)) {
+				i->selected = !i->selected;
+				_selectedChanges.fire({});
+			}
+		}));
+	};
+	for (const auto &reaction : list) {
+		const auto id = reaction.id;
+		const auto customId = id.custom();
+		_tags.push_back({
+			.id = id,
+			.custom = (customId
+				? _owner->customEmojiManager().create(
+					customId,
+					[=] { _repaintRequests.fire({}); })
+				: nullptr),
+			.link = link(id),
+			.selected = ranges::contains(selected, id),
+		});
+		if (!customId) {
+			_owner->reactions().preloadImageFor(id);
+		}
+	}
+	if (_width > 0) {
+		layout();
+	}
+}
+
+void SearchTags::layout() {
+	Expects(_width > 0);
+
+	const auto &bg = validateBg(false);
+	const auto skip = st::dialogsSearchTagSkip;
+	const auto size = bg.size() / bg.devicePixelRatio();
+	const auto xsingle = size.width() + skip.x();
+	const auto ysingle = size.height() + skip.y();
+	const auto columns = std::max((_width + skip.x()) / xsingle, 1);
+	const auto rows = (_tags.size() + columns - 1) / columns;
+	for (auto row = 0; row != rows; ++row) {
+		for (auto column = 0; column != columns; ++column) {
+			const auto index = row * columns + column;
+			if (index >= _tags.size()) {
+				break;
+			}
+			const auto x = column * xsingle;
+			const auto y = row * ysingle;
+			_tags[index].geometry = QRect(QPoint(x, y), size);
+		}
+	}
+	const auto bottom = st::dialogsSearchTagBottom;
+	_height = rows ? (rows * ysingle - skip.y() + bottom) : 0;
+}
+
+void SearchTags::resizeToWidth(int width) {
+	if (_width == width || width <= 0) {
+		return;
+	}
+	_width = width;
+	layout();
+}
+
+int SearchTags::height() const {
+	return _height.current();
+}
+
+rpl::producer<int> SearchTags::heightValue() const {
+	return _height.value();
+}
+
+rpl::producer<> SearchTags::repaintRequests() const {
+	return _repaintRequests.events();
+}
+
+ClickHandlerPtr SearchTags::lookupHandler(QPoint point) const {
+	for (const auto &tag : _tags) {
+		if (tag.geometry.contains(point.x(), point.y())) {
+			return tag.link;
+		}
+	}
+	return nullptr;
+}
+
+auto SearchTags::selectedValue() const
+-> rpl::producer<std::vector<Data::ReactionId>> {
+	return _selectedChanges.events() | rpl::map([=] {
+		return collectSelected();
+	});
+}
+
+void SearchTags::paintCustomFrame(
+		QPainter &p,
+		not_null<Ui::Text::CustomEmoji*> emoji,
+		QPoint innerTopLeft,
+		crl::time now,
+		bool paused,
+		const QColor &textColor) const {
+	if (_customCache.isNull()) {
+		using namespace Ui::Text;
+		const auto size = st::emojiSize;
+		const auto factor = style::DevicePixelRatio();
+		const auto adjusted = AdjustCustomEmojiSize(size);
+		_customCache = QImage(
+			QSize(adjusted, adjusted) * factor,
+			QImage::Format_ARGB32_Premultiplied);
+		_customCache.setDevicePixelRatio(factor);
+		_customSkip = (size - adjusted) / 2;
+	}
+	_customCache.fill(Qt::transparent);
+	auto q = QPainter(&_customCache);
+	emoji->paint(q, {
+		.textColor = textColor,
+		.now = now,
+		.paused = paused || On(PowerSaving::kEmojiChat),
+	});
+	q.end();
+	_customCache = Images::Round(
+		std::move(_customCache),
+		(Images::Option::RoundLarge
+			| Images::Option::RoundSkipTopRight
+			| Images::Option::RoundSkipBottomRight));
+
+	p.drawImage(
+		innerTopLeft + QPoint(_customSkip, _customSkip),
+		_customCache);
+}
+
+void SearchTags::paint(
+		QPainter &p,
+		QPoint position,
+		crl::time now,
+		bool paused) const {
+	const auto size = st::reactionInlineSize;
+	const auto skip = (size - st::reactionInlineImage) / 2;
+	const auto padding = st::reactionInlinePadding;
+	for (const auto &tag : _tags) {
+		const auto geometry = tag.geometry.translated(position);
+		p.drawImage(geometry.topLeft(), validateBg(tag.selected));
+		if (!tag.custom && tag.image.isNull()) {
+			tag.image = _owner->reactions().resolveImageFor(
+				tag.id,
+				::Data::Reactions::ImageSize::InlineList);
+		}
+		const auto inner = geometry.marginsRemoved(padding);
+		const auto image = QRect(
+			inner.topLeft() + QPoint(skip, skip),
+			QSize(st::reactionInlineImage, st::reactionInlineImage));
+		if (const auto custom = tag.custom.get()) {
+			const auto textFg = tag.selected
+				? st::dialogsNameFgActive->c
+				: st::dialogsNameFgOver->c;
+			paintCustomFrame(
+				p,
+				custom,
+				inner.topLeft(),
+				now,
+				paused,
+				textFg);
+		} else if (!tag.image.isNull()) {
+			p.drawImage(image.topLeft(), tag.image);
+		}
+	}
+}
+
+const QImage &SearchTags::validateBg(bool selected) const {
+	using namespace HistoryView::Reactions;
+	auto &image = selected ? _selectedBg : _normalBg;
+	if (image.isNull()) {
+		const auto tagBg = selected
+			? st::dialogsBgActive->c
+			: st::dialogsBgOver->c;
+		const auto dotBg = selected
+			? anim::with_alpha(tagBg, InlineList::TagDotAlpha())
+			: st::windowSubTextFg->c;
+		image = InlineList::PrepareTagBg(tagBg, dotBg);
+	}
+	return image;
+}
+
+std::vector<Data::ReactionId> SearchTags::collectSelected() const {
+	return _tags | ranges::views::filter(
+		&Tag::selected
+	) | ranges::views::transform(
+		&Tag::id
+	) | ranges::to_vector;
+}
+
+rpl::lifetime &SearchTags::lifetime() {
+	return _lifetime;
+}
+
+} // namespace Dialogs
diff --git a/Telegram/SourceFiles/dialogs/dialogs_search_tags.h b/Telegram/SourceFiles/dialogs/dialogs_search_tags.h
new file mode 100644
index 000000000..b1c193878
--- /dev/null
+++ b/Telegram/SourceFiles/dialogs/dialogs_search_tags.h
@@ -0,0 +1,78 @@
+/*
+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/weak_ptr.h"
+
+namespace Data {
+class Session;
+struct Reaction;
+struct ReactionId;
+} // namespace Data
+
+namespace Ui::Text {
+class CustomEmoji;
+} // namespace Ui::Text
+
+namespace Dialogs {
+
+class SearchTags final : public base::has_weak_ptr {
+public:
+	SearchTags(
+		not_null<Data::Session*> owner,
+		rpl::producer<std::vector<Data::Reaction>> tags);
+	~SearchTags();
+
+	void resizeToWidth(int width);
+	[[nodiscard]] int height() const;
+	[[nodiscard]] rpl::producer<int> heightValue() const;
+	[[nodiscard]] rpl::producer<> repaintRequests() const;
+
+	[[nodiscard]] ClickHandlerPtr lookupHandler(QPoint point) const;
+	[[nodiscard]] auto selectedValue() const
+		-> rpl::producer<std::vector<Data::ReactionId>>;
+
+	void paint(
+		QPainter &p,
+		QPoint position,
+		crl::time now,
+		bool paused) const;
+
+	[[nodiscard]] rpl::lifetime &lifetime();
+
+private:
+	struct Tag;
+
+	void fill(const std::vector<Data::Reaction> &list);
+	void paintCustomFrame(
+		QPainter &p,
+		not_null<Ui::Text::CustomEmoji*> emoji,
+		QPoint innerTopLeft,
+		crl::time now,
+		bool paused,
+		const QColor &textColor) const;
+	void layout();
+	[[nodiscard]] std::vector<Data::ReactionId> collectSelected() const;
+	[[nodiscard]] const QImage &validateBg(bool selected) const;
+
+	const not_null<Data::Session*> _owner;
+	std::vector<Tag> _tags;
+	rpl::event_stream<> _selectedChanges;
+	rpl::event_stream<> _repaintRequests;
+	mutable QImage _normalBg;
+	mutable QImage _selectedBg;
+	mutable QImage _customCache;
+	mutable int _customSkip = 0;
+	rpl::variable<int> _height;
+	int _width = 0;
+
+	rpl::lifetime _lifetime;
+
+};
+
+} // namespace Dialogs
diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
index f6ace227a..2f19148fa 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
@@ -1713,7 +1713,7 @@ void Widget::loadMoreBlockedByDate() {
 bool Widget::searchMessages(bool searchCache) {
 	auto result = false;
 	auto q = currentSearchQuery().trimmed();
-	if (q.isEmpty() && !_searchFromAuthor) {
+	if (q.isEmpty() && !_searchFromAuthor && _searchTags.empty()) {
 		cancelSearchRequest();
 		_api.request(base::take(_peerSearchRequest)).cancel();
 		_api.request(base::take(_topicSearchRequest)).cancel();
@@ -1730,6 +1730,7 @@ bool Widget::searchMessages(bool searchCache) {
 		if (i != _searchCache.end()) {
 			_searchQuery = q;
 			_searchQueryFrom = _searchFromAuthor;
+			_searchQueryTags = _searchTags;
 			_searchNextRate = 0;
 			_searchFull = _searchFullMigrated = false;
 			cancelSearchRequest();
@@ -1741,9 +1742,12 @@ bool Widget::searchMessages(bool searchCache) {
 				0);
 			result = true;
 		}
-	} else if (_searchQuery != q || _searchQueryFrom != _searchFromAuthor) {
+	} else if (_searchQuery != q
+		|| _searchQueryFrom != _searchFromAuthor
+		|| _searchQueryTags != _searchTags) {
 		_searchQuery = q;
 		_searchQueryFrom = _searchFromAuthor;
+		_searchQueryTags = _searchTags;
 		_searchNextRate = 0;
 		_searchFull = _searchFullMigrated = false;
 		cancelSearchRequest();
@@ -1757,14 +1761,20 @@ bool Widget::searchMessages(bool searchCache) {
 				using Flag = MTPmessages_Search::Flag;
 				_searchRequest = session().api().request(MTPmessages_Search(
 					MTP_flags((topic ? Flag::f_top_msg_id : Flag())
-						| (_searchQueryFrom ? Flag::f_from_id : Flag())),
+						| (_searchQueryFrom ? Flag::f_from_id : Flag())
+						| (_searchQueryTags.empty()
+							? Flag()
+							: Flag::f_saved_reaction)),
 					peer->input,
 					MTP_string(_searchQuery),
 					(_searchQueryFrom
 						? _searchQueryFrom->input
 						: MTP_inputPeerEmpty()),
 					MTPInputPeer(), // saved_peer_id
-					MTPVector<MTPReaction>(), // saved_reaction
+					MTP_vector_from_range(
+						_searchQueryTags | ranges::views::transform(
+							Data::ReactionToMTP
+						)),
 					MTP_int(topic ? topic->rootId() : 0),
 					MTP_inputMessagesFilterEmpty(),
 					MTP_int(0), // min_date
@@ -1868,6 +1878,7 @@ bool Widget::searchMessages(bool searchCache) {
 bool Widget::searchForPeersRequired(const QString &query) const {
 	return !_searchInChat
 		&& !_searchFromAuthor
+		&& _searchTags.empty()
 		&& !_openedForum
 		&& !query.isEmpty()
 		&& (query[0] != '#');
@@ -1876,6 +1887,7 @@ bool Widget::searchForPeersRequired(const QString &query) const {
 bool Widget::searchForTopicsRequired(const QString &query) const {
 	return !_searchInChat
 		&& !_searchFromAuthor
+		&& _searchTags.empty()
 		&& _openedForum
 		&& !query.isEmpty()
 		&& (query[0] != '#')
@@ -2000,14 +2012,20 @@ void Widget::searchMore() {
 				using Flag = MTPmessages_Search::Flag;
 				_searchRequest = session().api().request(MTPmessages_Search(
 					MTP_flags((topic ? Flag::f_top_msg_id : Flag())
-						| (_searchQueryFrom ? Flag::f_from_id : Flag())),
+						| (_searchQueryFrom ? Flag::f_from_id : Flag())
+						| (_searchQueryTags.empty()
+							? Flag()
+							: Flag::f_saved_reaction)),
 					peer->input,
 					MTP_string(_searchQuery),
 					(_searchQueryFrom
 						? _searchQueryFrom->input
 						: MTP_inputPeerEmpty()),
 					MTPInputPeer(), // saved_peer_id
-					MTPVector<MTPReaction>(), // saved_reaction
+					MTP_vector_from_range(
+						_searchQueryTags | ranges::views::transform(
+							Data::ReactionToMTP
+						)),
 					MTP_int(topic ? topic->rootId() : 0),
 					MTP_inputMessagesFilterEmpty(),
 					MTP_int(0), // min_date
@@ -2401,7 +2419,7 @@ void Widget::applyFilterUpdate(bool force) {
 	updateStoriesVisibility();
 	const auto filterText = currentSearchQuery();
 	_inner->applyFilterUpdate(filterText, force);
-	if (filterText.isEmpty() && !_searchFromAuthor) {
+	if (filterText.isEmpty() && !_searchFromAuthor && _searchTags.empty()) {
 		clearSearchCache();
 	}
 	_cancelSearch->toggle(!filterText.isEmpty(), anim::type::normal);
@@ -2417,7 +2435,9 @@ void Widget::applyFilterUpdate(bool force) {
 		_peerSearchQuery = QString();
 	}
 
-	if (_chooseFromUser->toggled() || _searchFromAuthor) {
+	if (_chooseFromUser->toggled()
+		|| _searchFromAuthor
+		|| !_searchTags.empty()) {
 		auto switchToChooseFrom = HistoryView::SwitchToChooseFromQuery();
 		if (_lastFilterText != switchToChooseFrom
 			&& switchToChooseFrom.startsWith(_lastFilterText)
@@ -2619,6 +2639,18 @@ bool Widget::setSearchInChat(Key chat, PeerData *from) {
 		controller()->closeFolder();
 	}
 	_inner->searchInChat(_searchInChat, _searchFromAuthor);
+	_searchTagsLifetime = _inner->searchTagsValue(
+	) | rpl::start_with_next([=](std::vector<Data::ReactionId> &&list) {
+		if (_searchTags != list) {
+			clearSearchCache();
+			_searchTags = std::move(list);
+			if (_searchTags.empty()) {
+				applyFilterUpdate(true);
+			} else {
+				searchMessages();
+			}
+		}
+	});
 	if (_subsectionTopBar) {
 		_subsectionTopBar->searchEnableJumpToDate(
 			_openedForum && _searchInChat);
@@ -2639,6 +2671,7 @@ void Widget::clearSearchCache() {
 	}
 	_searchQuery = QString();
 	_searchQueryFrom = nullptr;
+	_searchQueryTags.clear();
 	_topicSearchQuery = QString();
 	_topicSearchOffsetDate = 0;
 	_topicSearchOffsetId = _topicSearchOffsetTopicId = 0;
diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h
index 711509625..5ab53e2f9 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_widget.h
+++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h
@@ -22,6 +22,7 @@ class Error;
 namespace Data {
 class Forum;
 enum class StorySourcesList : uchar;
+struct ReactionId;
 } // namespace Data
 
 namespace Main {
@@ -285,6 +286,8 @@ private:
 	Dialogs::Key _searchInChat;
 	History *_searchInMigrated = nullptr;
 	PeerData *_searchFromAuthor = nullptr;
+	std::vector<Data::ReactionId> _searchTags;
+	rpl::lifetime _searchTagsLifetime;
 	QString _lastFilterText;
 
 	rpl::event_stream<rpl::producer<Stories::Content>> _storiesContents;
@@ -313,6 +316,7 @@ private:
 
 	QString _searchQuery;
 	PeerData *_searchQueryFrom = nullptr;
+	std::vector<Data::ReactionId> _searchQueryTags;
 	int32 _searchNextRate = 0;
 	bool _searchFull = false;
 	bool _searchFullMigrated = false;
diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp
index a7e83c029..292774899 100644
--- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp
+++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp
@@ -507,12 +507,11 @@ void InlineList::paint(
 	}
 }
 
-void InlineList::validateTagBg(const QColor &color) const {
-	if (!_tagBg.isNull() && _tagBgColor == color) {
-		return;
-	}
-	_tagBgColor = color;
+float64 InlineList::TagDotAlpha() {
+	return 0.6;
+}
 
+QImage InlineList::PrepareTagBg(QColor tagBg, QColor dotBg) {
 	const auto padding = st::reactionInlinePadding;
 	const auto size = st::reactionInlineSize;
 	const auto width = padding.left()
@@ -522,20 +521,19 @@ void InlineList::validateTagBg(const QColor &color) const {
 	const auto height = padding.top() + size + padding.bottom();
 	const auto ratio = style::DevicePixelRatio();
 
-	auto mask = QImage(
+	auto result = QImage(
 		QSize(width, height) * ratio,
 		QImage::Format_ARGB32_Premultiplied);
-	mask.setDevicePixelRatio(ratio);
+	result.setDevicePixelRatio(ratio);
 
-	mask.fill(Qt::transparent);
-	auto p = QPainter(&mask);
+	result.fill(Qt::transparent);
+	auto p = QPainter(&result);
 
 	auto path = QPainterPath();
 	const auto arrow = st::reactionInlineTagArrow;
 	const auto rradius = st::reactionInlineTagRightRadius * 1.;
 	const auto radius = st::reactionInlineTagLeftRadius - rradius;
-	const auto fg = QColor(255, 255, 255);
-	auto pen = QPen(fg);
+	auto pen = QPen(tagBg);
 	pen.setWidthF(rradius * 2.);
 	pen.setJoinStyle(Qt::RoundJoin);
 	const auto rect = QRectF(0, 0, width, height).marginsRemoved(
@@ -548,9 +546,15 @@ void InlineList::validateTagBg(const QColor &color) const {
 	path.lineTo(right, rect.y() + rect.height() / 2);
 	path.lineTo(right - arrow, bottom);
 	path.lineTo(rect.x() + radius, bottom);
-	path.arcTo(QRectF(rect.x(), bottom - radius * 2, radius * 2, radius * 2), 270, -90);
+	path.arcTo(
+		QRectF(rect.x(), bottom - radius * 2, radius * 2, radius * 2),
+		270,
+		-90);
 	path.lineTo(rect.x(), rect.y() + radius);
-	path.arcTo(QRectF(rect.x(), rect.y(), radius * 2, radius * 2), 180, -90);
+	path.arcTo(
+		QRectF(rect.x(), rect.y(), radius * 2, radius * 2),
+		180,
+		-90);
 	path.closeSubpath();
 
 	const auto dsize = st::reactionInlineTagDot;
@@ -563,16 +567,26 @@ void InlineList::validateTagBg(const QColor &color) const {
 	auto hq = PainterHighQualityEnabler(p);
 	p.setCompositionMode(QPainter::CompositionMode_Source);
 	p.setPen(pen);
-	p.setBrush(fg);
+	p.setBrush(tagBg);
 	p.drawPath(path);
 
 	p.setPen(Qt::NoPen);
-	p.setBrush(QColor(255, 255, 255, 255 * 0.6));
+	p.setBrush(dotBg);
 	p.drawEllipse(dot);
 
 	p.end();
 
-	_tagBg = style::colorizeImage(mask, color);
+	return result;
+}
+
+void InlineList::validateTagBg(const QColor &color) const {
+	if (!_tagBg.isNull() && _tagBgColor == color) {
+		return;
+	}
+	_tagBgColor = color;
+	auto dot = color;
+	dot.setAlphaF(dot.alphaF() * TagDotAlpha());
+	_tagBg = PrepareTagBg(color, anim::with_alpha(color, TagDotAlpha()));
 }
 
 void InlineList::paintSingleBg(
diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions.h b/Telegram/SourceFiles/history/view/reactions/history_view_reactions.h
index cc4fa80d8..619e1f053 100644
--- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions.h
+++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions.h
@@ -93,6 +93,9 @@ public:
 		ReactionId,
 		std::unique_ptr<Ui::ReactionFlyAnimation>> animations);
 
+	[[nodiscard]] static float64 TagDotAlpha();
+	[[nodiscard]] static QImage PrepareTagBg(QColor tagBg, QColor dotBg);
+
 private:
 	struct Userpics {
 		QImage image;

From f5fcfaba0c137a27518446bf0bfc79d0d6988c36 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Fri, 5 Jan 2024 16:34:49 +0400
Subject: [PATCH 23/73] Add search in saved sublist.

---
 .../dialogs/dialogs_inner_widget.cpp          | 26 ++++++++-----
 .../dialogs/dialogs_inner_widget.h            |  1 +
 .../SourceFiles/dialogs/dialogs_widget.cpp    | 39 +++++++++++++------
 .../view/history_view_sublist_section.cpp     | 28 +++++++++++++
 .../view/history_view_sublist_section.h       |  2 +
 .../view/history_view_top_bar_widget.cpp      |  4 +-
 6 files changed, 79 insertions(+), 21 deletions(-)

diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
index a9f1d1b41..d809c893e 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
@@ -43,6 +43,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_changes.h"
 #include "data/data_message_reactions.h"
 #include "data/data_saved_messages.h"
+#include "data/data_saved_sublist.h"
 #include "data/data_stories.h"
 #include "data/stickers/data_stickers.h"
 #include "data/data_send_action.h"
@@ -502,7 +503,7 @@ int InnerWidget::searchInChatSkip() const {
 	if (_searchInChat) {
 		result += st::searchedBarHeight + st::dialogsSearchInHeight;
 	}
-	if (_searchFromPeer) {
+	if (_searchFromShown) {
 		if (_searchInChat) {
 			result += st::lineWidth;
 		}
@@ -1139,7 +1140,7 @@ void InnerWidget::paintSearchInChat(
 	auto fullRect = QRect(0, top, width(), height - top);
 	p.fillRect(fullRect, currentBg());
 	if (_searchInChat) {
-		if (_searchFromPeer) {
+		if (_searchFromShown) {
 			p.fillRect(QRect(0, top + st::dialogsSearchInHeight, width(), st::lineWidth), st::shadowFg);
 		}
 		p.setPen(st::dialogsNameFg);
@@ -1153,15 +1154,17 @@ void InnerWidget::paintSearchInChat(
 			} else {
 				paintSearchInPeer(p, peer, _searchInChatUserpic, top, _searchInChatText);
 			}
+		} else if (const auto sublist = _searchInChat.sublist()) {
+			paintSearchInSaved(p, top, _searchInChatText);
 		} else {
 			Unexpected("Empty Key in paintSearchInChat.");
 		}
 		top += st::dialogsSearchInHeight + st::lineWidth;
 	}
-	if (_searchFromPeer) {
+	if (_searchFromShown) {
 		p.setPen(st::dialogsTextFg);
 		p.setTextPalette(st::dialogsSearchFromPalette);
-		paintSearchInPeer(p, _searchFromPeer, _searchFromUserUserpic, top, _searchFromUserText);
+		paintSearchInPeer(p, _searchFromShown, _searchFromUserUserpic, top, _searchFromUserText);
 		p.restoreTextPalette();
 	}
 }
@@ -2967,7 +2970,9 @@ bool InnerWidget::hasFilteredResults() const {
 
 void InnerWidget::searchInChat(Key key, PeerData *from) {
 	_searchInMigrated = nullptr;
-	if (const auto peer = key.peer()) {
+	const auto sublist = key.sublist();
+	const auto peer = sublist ? session().user().get() : key.peer();
+	if (peer) {
 		if (const auto migrateTo = peer->migrateTo()) {
 			return searchInChat(peer->owner().history(migrateTo), from);
 		} else if (const auto migrateFrom = peer->migrateFrom()) {
@@ -3013,15 +3018,16 @@ void InnerWidget::searchInChat(Key key, PeerData *from) {
 	}
 	_searchInChat = key;
 	_searchFromPeer = from;
+	_searchFromShown = key.sublist() ? key.sublist()->peer().get() : from;
 	if (_searchInChat) {
 		onHashtagFilterUpdate(QStringView());
 		_cancelSearchInChat->show();
 	} else {
 		_cancelSearchInChat->hide();
 	}
-	if (_searchFromPeer) {
+	if (_searchFromShown) {
 		_cancelSearchFromUser->show();
-		_searchFromUserUserpic = _searchFromPeer->createUserpicView();
+		_searchFromUserUserpic = _searchFromShown->createUserpicView();
 	} else {
 		_cancelSearchFromUser->hide();
 		_searchFromUserUserpic = {};
@@ -3030,7 +3036,7 @@ void InnerWidget::searchInChat(Key key, PeerData *from) {
 		refreshSearchInChatLabel();
 	}
 
-	if (const auto peer = _searchInChat.peer()) {
+	if (peer) {
 		_searchInChatUserpic = peer->createUserpicView();
 	} else {
 		_searchInChatUserpic = {};
@@ -3059,6 +3065,8 @@ void InnerWidget::refreshSearchInChatLabel() {
 				return tr::lng_replies_messages(tr::now);
 			}
 			return peer->name();
+		} else if (_searchInChat.sublist()) {
+			return tr::lng_saved_messages(tr::now);
 		}
 		return QString();
 	}();
@@ -3068,7 +3076,7 @@ void InnerWidget::refreshSearchInChatLabel() {
 			dialog,
 			Ui::DialogTextOptions());
 	}
-	const auto from = _searchFromPeer ? _searchFromPeer->name() : QString();
+	const auto from = _searchFromShown ? _searchFromShown->name() : u""_q;
 	if (!from.isEmpty()) {
 		const auto fromUserText = tr::lng_dlg_search_from(
 			tr::now,
diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h
index d511c65e8..f252b7add 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h
+++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h
@@ -483,6 +483,7 @@ private:
 	Key _searchInChat;
 	History *_searchInMigrated = nullptr;
 	PeerData *_searchFromPeer = nullptr;
+	PeerData *_searchFromShown = nullptr;
 	mutable Ui::PeerUserpicView _searchInChatUserpic;
 	mutable Ui::PeerUserpicView _searchFromUserUserpic;
 	Ui::Text::String _searchInChatText;
diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
index 2f19148fa..eeeb09c9f 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
@@ -67,6 +67,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_changes.h"
 #include "data/data_download_manager.h"
 #include "data/data_chat_filters.h"
+#include "data/data_saved_sublist.h"
 #include "data/data_stories.h"
 #include "info/downloads/info_downloads_widget.h"
 #include "info/info_memento.h"
@@ -1756,21 +1757,27 @@ bool Widget::searchMessages(bool searchCache) {
 			auto &histories = session().data().histories();
 			const auto type = Data::Histories::RequestType::History;
 			const auto history = session().data().history(peer);
+			const auto sublist = _openedForum
+				? nullptr
+				: _searchInChat.sublist();
+			const auto fromPeer = sublist ? nullptr : _searchQueryFrom;
+			const auto savedPeer = sublist
+				? sublist->peer().get()
+				: nullptr;
 			_searchInHistoryRequest = histories.sendRequest(history, type, [=](Fn<void()> finish) {
 				const auto type = SearchRequestType::PeerFromStart;
 				using Flag = MTPmessages_Search::Flag;
 				_searchRequest = session().api().request(MTPmessages_Search(
 					MTP_flags((topic ? Flag::f_top_msg_id : Flag())
-						| (_searchQueryFrom ? Flag::f_from_id : Flag())
+						| (fromPeer ? Flag::f_from_id : Flag())
+						| (savedPeer ? Flag::f_saved_peer_id : Flag())
 						| (_searchQueryTags.empty()
 							? Flag()
 							: Flag::f_saved_reaction)),
 					peer->input,
 					MTP_string(_searchQuery),
-					(_searchQueryFrom
-						? _searchQueryFrom->input
-						: MTP_inputPeerEmpty()),
-					MTPInputPeer(), // saved_peer_id
+					(fromPeer ? fromPeer->input : MTP_inputPeerEmpty()),
+					(savedPeer ? savedPeer->input : MTP_inputPeerEmpty()),
 					MTP_vector_from_range(
 						_searchQueryTags | ranges::views::transform(
 							Data::ReactionToMTP
@@ -1922,6 +1929,7 @@ void Widget::searchMessages(const QString &query, Key inChat) {
 	const auto inChatChanged = [&] {
 		const auto inPeer = inChat.peer();
 		const auto inTopic = inChat.topic();
+		const auto inSublist = inChat.sublist();
 		if (!inTopic
 			&& _openedForum
 			&& inPeer == _openedForum->channel()
@@ -1931,7 +1939,7 @@ void Widget::searchMessages(const QString &query, Key inChat) {
 		} else if ((inTopic || (inPeer && !inPeer->isForum()))
 			&& (inChat == _searchInChat)) {
 			return false;
-		} else if (const auto inPeer = inChat.peer()) {
+		} else if (inPeer) {
 			if (const auto to = inPeer->migrateTo()) {
 				if (to == _searchInChat.peer() && !_searchInChat.topic()) {
 					return false;
@@ -2005,6 +2013,13 @@ void Widget::searchMore() {
 			const auto topic = searchInTopic();
 			const auto type = Data::Histories::RequestType::History;
 			const auto history = session().data().history(peer);
+			const auto sublist = _openedForum
+				? nullptr
+				: _searchInChat.sublist();
+			const auto fromPeer = sublist ? nullptr : _searchQueryFrom;
+			const auto savedPeer = sublist
+				? sublist->peer().get()
+				: nullptr;
 			_searchInHistoryRequest = histories.sendRequest(history, type, [=](Fn<void()> finish) {
 				const auto type = _lastSearchId
 					? SearchRequestType::PeerFromOffset
@@ -2012,16 +2027,15 @@ void Widget::searchMore() {
 				using Flag = MTPmessages_Search::Flag;
 				_searchRequest = session().api().request(MTPmessages_Search(
 					MTP_flags((topic ? Flag::f_top_msg_id : Flag())
-						| (_searchQueryFrom ? Flag::f_from_id : Flag())
+						| (fromPeer ? Flag::f_from_id : Flag())
+						| (savedPeer ? Flag::f_saved_peer_id : Flag())
 						| (_searchQueryTags.empty()
 							? Flag()
 							: Flag::f_saved_reaction)),
 					peer->input,
 					MTP_string(_searchQuery),
-					(_searchQueryFrom
-						? _searchQueryFrom->input
-						: MTP_inputPeerEmpty()),
-					MTPInputPeer(), // saved_peer_id
+					(fromPeer ? fromPeer->input : MTP_inputPeerEmpty()),
+					(savedPeer ? savedPeer->input : MTP_inputPeerEmpty()),
 					MTP_vector_from_range(
 						_searchQueryTags | ranges::views::transform(
 							Data::ReactionToMTP
@@ -2590,6 +2604,7 @@ bool Widget::setSearchInChat(Key chat, PeerData *from) {
 	}
 	const auto peer = chat.peer();
 	const auto topic = chat.topic();
+	const auto sublist = chat.sublist();
 	const auto forum = peer ? peer->forum() : nullptr;
 	if (chat.folder() || (forum && !topic)) {
 		chat = Key();
@@ -3083,6 +3098,8 @@ void Widget::cancelSearchRequest() {
 PeerData *Widget::searchInPeer() const {
 	return _openedForum
 		? _openedForum->channel().get()
+		: _searchInChat.sublist()
+		? session().user().get()
 		: _searchInChat.peer();
 }
 
diff --git a/Telegram/SourceFiles/history/view/history_view_sublist_section.cpp b/Telegram/SourceFiles/history/view/history_view_sublist_section.cpp
index a1c9578f8..e96738d0d 100644
--- a/Telegram/SourceFiles/history/view/history_view_sublist_section.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_sublist_section.cpp
@@ -8,6 +8,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "history/view/history_view_sublist_section.h"
 
 #include "main/main_session.h"
+#include "core/application.h"
+#include "core/shortcuts.h"
 #include "data/data_saved_messages.h"
 #include "data/data_saved_sublist.h"
 #include "data/data_session.h"
@@ -19,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "history/history.h"
 #include "history/history_item.h"
 #include "lang/lang_keys.h"
+#include "mainwidget.h"
 #include "ui/chat/chat_style.h"
 #include "ui/widgets/buttons.h"
 #include "ui/widgets/scroll_area.h"
@@ -115,6 +118,10 @@ SublistWidget::SublistWidget(
 	) | rpl::start_with_next([=] {
 		clearSelected();
 	}, _topBar->lifetime());
+	_topBar->searchRequest(
+	) | rpl::start_with_next([=] {
+		searchInSublist();
+	}, _topBar->lifetime());
 
 	_translateBar->raise();
 	_topBarShadow->raise();
@@ -134,6 +141,7 @@ SublistWidget::SublistWidget(
 		onScroll();
 	}, lifetime());
 
+	setupShortcuts();
 	setupTranslateBar();
 }
 
@@ -658,4 +666,24 @@ void SublistWidget::clearSelected() {
 	_inner->cancelSelection();
 }
 
+void SublistWidget::setupShortcuts() {
+	Shortcuts::Requests(
+	) | rpl::filter([=] {
+		return Ui::AppInFocus()
+			&& Ui::InFocusChain(this)
+			&& !controller()->isLayerShown()
+			&& (Core::App().activeWindow() == &controller()->window());
+	}) | rpl::start_with_next([=](not_null<Shortcuts::Request*> request) {
+		using Command = Shortcuts::Command;
+		request->check(Command::Search, 1) && request->handle([=] {
+			searchInSublist();
+			return true;
+		});
+	}, lifetime());
+}
+
+void SublistWidget::searchInSublist() {
+	controller()->content()->searchInChat(_sublist);
+}
+
 } // namespace HistoryView
diff --git a/Telegram/SourceFiles/history/view/history_view_sublist_section.h b/Telegram/SourceFiles/history/view/history_view_sublist_section.h
index 819ce363c..ff6d34ace 100644
--- a/Telegram/SourceFiles/history/view/history_view_sublist_section.h
+++ b/Telegram/SourceFiles/history/view/history_view_sublist_section.h
@@ -167,11 +167,13 @@ private:
 	void setupOpenChatButton();
 	void setupAboutHiddenAuthor();
 	void setupTranslateBar();
+	void setupShortcuts();
 
 	void confirmDeleteSelected();
 	void confirmForwardSelected();
 	void clearSelected();
 	void recountChatWidth();
+	void searchInSublist();
 
 	const not_null<Data::SavedSublist*> _sublist;
 	const not_null<History*> _history;
diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp
index 891f45927..a670d8a91 100644
--- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp
@@ -928,7 +928,9 @@ int TopBarWidget::countSelectedButtonsTop(float64 selectedShown) {
 void TopBarWidget::updateSearchVisibility() {
 	const auto searchAllowedMode = (_activeChat.section == Section::History)
 		|| (_activeChat.section == Section::Replies
-			&& _activeChat.key.topic());
+			&& _activeChat.key.topic())
+		|| (_activeChat.section == Section::SavedSublist
+			&& _activeChat.key.sublist());
 	_search->setVisible(searchAllowedMode && !_chooseForReportReason);
 }
 

From 94a542a1d13cf5e49674498025ed699c475ee6b2 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Tue, 9 Jan 2024 11:17:36 +0400
Subject: [PATCH 24/73] Allow change account shortcuts in shortcuts_custom.json

---
 Telegram/SourceFiles/core/shortcuts.cpp | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/Telegram/SourceFiles/core/shortcuts.cpp b/Telegram/SourceFiles/core/shortcuts.cpp
index 56158e2b5..2d10ad484 100644
--- a/Telegram/SourceFiles/core/shortcuts.cpp
+++ b/Telegram/SourceFiles/core/shortcuts.cpp
@@ -133,6 +133,13 @@ const auto CommandNames = base::flat_map<Command, QString>{
 	{ Command::FolderNext     , u"next_folder"_q },
 	{ Command::ShowAllChats   , u"all_chats"_q },
 
+	{ Command::ShowAccount1   , u"account1"_q },
+	{ Command::ShowAccount2   , u"account2"_q },
+	{ Command::ShowAccount3   , u"account3"_q },
+	{ Command::ShowAccount4   , u"account4"_q },
+	{ Command::ShowAccount5   , u"account5"_q },
+	{ Command::ShowAccount6   , u"account6"_q },
+
 	{ Command::ShowFolder1    , u"folder1"_q },
 	{ Command::ShowFolder2    , u"folder2"_q },
 	{ Command::ShowFolder3    , u"folder3"_q },

From d1a0dfbb9790d86750f9d59294177ef86aae0ed1 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Tue, 9 Jan 2024 12:18:55 +0400
Subject: [PATCH 25/73] Search by tag on click in Saved Messages.

---
 .../data/data_message_reaction_id.cpp         | 20 ++++++++++++
 .../data/data_message_reaction_id.h           |  7 +++++
 .../dialogs/dialogs_inner_widget.cpp          | 11 +++++--
 .../dialogs/dialogs_inner_widget.h            |  5 ++-
 .../dialogs/dialogs_search_tags.cpp           | 31 ++++++++++++++++---
 .../SourceFiles/dialogs/dialogs_search_tags.h |  4 ++-
 .../SourceFiles/dialogs/dialogs_widget.cpp    | 27 +++++++++++-----
 Telegram/SourceFiles/dialogs/dialogs_widget.h |  7 +++--
 .../history/view/history_view_message.cpp     |  8 ++++-
 Telegram/lib_base                             |  2 +-
 10 files changed, 102 insertions(+), 20 deletions(-)

diff --git a/Telegram/SourceFiles/data/data_message_reaction_id.cpp b/Telegram/SourceFiles/data/data_message_reaction_id.cpp
index 1103d2e15..2c1a9e503 100644
--- a/Telegram/SourceFiles/data/data_message_reaction_id.cpp
+++ b/Telegram/SourceFiles/data/data_message_reaction_id.cpp
@@ -11,6 +11,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 namespace Data {
 
+QString SearchTagToQuery(const ReactionId &tagId) {
+	if (const auto customId = tagId.custom()) {
+		return u"#tag-custom:%1"_q.arg(customId);
+	} else if (!tagId) {
+		return QString();
+	}
+	return u"#tag-emoji:"_q + tagId.emoji();
+}
+
+ReactionId SearchTagFromQuery(const QString &query) {
+	const auto list = query.split(QChar(' '));
+	const auto tag = list.isEmpty() ? QString() : list[0];
+	if (tag.startsWith(u"#tag-custom:"_q)) {
+		return ReactionId{ DocumentId(tag.mid(12).toULongLong()) };
+	} else if (tag.startsWith(u"#tag-emoji:"_q)) {
+		return ReactionId{ tag.mid(11) };
+	}
+	return {};
+}
+
 QString ReactionEntityData(const ReactionId &id) {
 	if (id.empty()) {
 		return {};
diff --git a/Telegram/SourceFiles/data/data_message_reaction_id.h b/Telegram/SourceFiles/data/data_message_reaction_id.h
index 8c50fd9de..53d4a2df8 100644
--- a/Telegram/SourceFiles/data/data_message_reaction_id.h
+++ b/Telegram/SourceFiles/data/data_message_reaction_id.h
@@ -27,6 +27,10 @@ struct ReactionId {
 		return custom ? *custom : DocumentId();
 	}
 
+	explicit operator bool() const {
+		return !empty();
+	}
+
 	friend inline auto operator<=>(
 		const ReactionId &,
 		const ReactionId &) = default;
@@ -41,6 +45,9 @@ struct MessageReaction {
 	bool my = false;
 };
 
+[[nodiscard]] QString SearchTagToQuery(const ReactionId &tagId);
+[[nodiscard]] ReactionId SearchTagFromQuery(const QString &query);
+
 [[nodiscard]] QString ReactionEntityData(const ReactionId &id);
 
 [[nodiscard]] ReactionId ReactionFromMTP(const MTPReaction &reaction);
diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
index d809c893e..d7d17a37c 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
@@ -2968,13 +2968,17 @@ bool InnerWidget::hasFilteredResults() const {
 	return !_filterResults.empty() && _hashtagResults.empty();
 }
 
-void InnerWidget::searchInChat(Key key, PeerData *from) {
+void InnerWidget::searchInChat(
+		Key key,
+		PeerData *from,
+		std::vector<Data::ReactionId> tags) {
 	_searchInMigrated = nullptr;
 	const auto sublist = key.sublist();
 	const auto peer = sublist ? session().user().get() : key.peer();
 	if (peer) {
 		if (const auto migrateTo = peer->migrateTo()) {
-			return searchInChat(peer->owner().history(migrateTo), from);
+			const auto to = peer->owner().history(migrateTo);
+			return searchInChat(to, from, tags);
 		} else if (const auto migrateFrom = peer->migrateFrom()) {
 			_searchInMigrated = peer->owner().history(migrateFrom);
 		}
@@ -2990,7 +2994,8 @@ void InnerWidget::searchInChat(Key key, PeerData *from) {
 					list()
 				) | rpl::then(
 					reactions->myTagsUpdates() | rpl::map(list)
-				));
+				),
+				tags);
 
 			_searchTags->selectedValue(
 			) | rpl::start_with_next([=](std::vector<Data::ReactionId> &&list) {
diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h
index f252b7add..0f12e7211 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h
+++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h
@@ -139,7 +139,10 @@ public:
 	}
 	[[nodiscard]] bool hasFilteredResults() const;
 
-	void searchInChat(Key key, PeerData *from);
+	void searchInChat(
+		Key key,
+		PeerData *from,
+		std::vector<Data::ReactionId> tags);
 	[[nodiscard]] auto searchTagsValue() const
 		-> rpl::producer<std::vector<Data::ReactionId>>;
 
diff --git a/Telegram/SourceFiles/dialogs/dialogs_search_tags.cpp b/Telegram/SourceFiles/dialogs/dialogs_search_tags.cpp
index 22a15ec55..7f0aa16eb 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_search_tags.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_search_tags.cpp
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "dialogs/dialogs_search_tags.h"
 
+#include "base/qt/qt_key_modifiers.h"
 #include "data/stickers/data_custom_emoji.h"
 #include "data/data_document.h"
 #include "data/data_message_reactions.h"
@@ -30,14 +31,24 @@ struct SearchTags::Tag {
 
 SearchTags::SearchTags(
 	not_null<Data::Session*> owner,
-	rpl::producer<std::vector<Data::Reaction>> tags)
-: _owner(owner) {
+	rpl::producer<std::vector<Data::Reaction>> tags,
+	std::vector<Data::ReactionId> selected)
+: _owner(owner)
+, _added(selected) {
 	std::move(
 		tags
 	) | rpl::start_with_next([=](const std::vector<Data::Reaction> &list) {
 		fill(list);
 	}, _lifetime);
 
+	// Mark the `selected` reactions as selected in `_tags`.
+	for (const auto &id : selected) {
+		const auto i = ranges::find(_tags, id, &Tag::id);
+		if (i != end(_tags)) {
+			i->selected = true;
+		}
+	}
+
 	style::PaletteChanged(
 	) | rpl::start_with_next([=] {
 		_normalBg = _selectedBg = QImage();
@@ -54,13 +65,17 @@ void SearchTags::fill(const std::vector<Data::Reaction> &list) {
 		return std::make_shared<LambdaClickHandler>(crl::guard(this, [=] {
 			const auto i = ranges::find(_tags, id, &Tag::id);
 			if (i != end(_tags)) {
+				if (!i->selected && !base::IsShiftPressed()) {
+					for (auto &tag : _tags) {
+						tag.selected = false;
+					}
+				}
 				i->selected = !i->selected;
 				_selectedChanges.fire({});
 			}
 		}));
 	};
-	for (const auto &reaction : list) {
-		const auto id = reaction.id;
+	const auto push = [&](Data::ReactionId id) {
 		const auto customId = id.custom();
 		_tags.push_back({
 			.id = id,
@@ -75,6 +90,14 @@ void SearchTags::fill(const std::vector<Data::Reaction> &list) {
 		if (!customId) {
 			_owner->reactions().preloadImageFor(id);
 		}
+	};
+	for (const auto &reaction : list) {
+		push(reaction.id);
+	}
+	for (const auto &reaction : _added) {
+		if (!ranges::contains(_tags, reaction, &Tag::id)) {
+			push(reaction);
+		}
 	}
 	if (_width > 0) {
 		layout();
diff --git a/Telegram/SourceFiles/dialogs/dialogs_search_tags.h b/Telegram/SourceFiles/dialogs/dialogs_search_tags.h
index b1c193878..4c52162eb 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_search_tags.h
+++ b/Telegram/SourceFiles/dialogs/dialogs_search_tags.h
@@ -25,7 +25,8 @@ class SearchTags final : public base::has_weak_ptr {
 public:
 	SearchTags(
 		not_null<Data::Session*> owner,
-		rpl::producer<std::vector<Data::Reaction>> tags);
+		rpl::producer<std::vector<Data::Reaction>> tags,
+		std::vector<Data::ReactionId> selected);
 	~SearchTags();
 
 	void resizeToWidth(int width);
@@ -61,6 +62,7 @@ private:
 	[[nodiscard]] const QImage &validateBg(bool selected) const;
 
 	const not_null<Data::Session*> _owner;
+	std::vector<Data::ReactionId> _added;
 	std::vector<Tag> _tags;
 	rpl::event_stream<> _selectedChanges;
 	rpl::event_stream<> _repaintRequests;
diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
index eeeb09c9f..b41257487 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
@@ -1911,7 +1911,7 @@ void Widget::showMainMenu() {
 	controller()->widget()->showMainMenu();
 }
 
-void Widget::searchMessages(const QString &query, Key inChat) {
+void Widget::searchMessages(QString query, Key inChat) {
 	if (_childList) {
 		const auto forum = controller()->shownForum().current();
 		const auto topic = inChat.topic();
@@ -1926,6 +1926,12 @@ void Widget::searchMessages(const QString &query, Key inChat) {
 		controller()->closeFolder();
 	}
 
+	auto tags = std::vector<Data::ReactionId>();
+	if (const auto tagId = Data::SearchTagFromQuery(query)) {
+		inChat = session().data().history(session().user());
+		query = QString();
+		tags.push_back(tagId);
+	}
 	const auto inChatChanged = [&] {
 		const auto inPeer = inChat.peer();
 		const auto inTopic = inChat.topic();
@@ -1948,10 +1954,12 @@ void Widget::searchMessages(const QString &query, Key inChat) {
 		}
 		return true;
 	}();
-	if ((currentSearchQuery() != query) || inChatChanged) {
+	if ((currentSearchQuery() != query)
+		|| inChatChanged
+		|| _searchTags != tags) {
 		if (inChat) {
 			cancelSearch();
-			setSearchInChat(inChat);
+			setSearchInChat(inChat, nullptr, tags);
 		}
 		setSearchQuery(query);
 		applyFilterUpdate(true);
@@ -2595,9 +2603,12 @@ void Widget::searchInChat(Key chat) {
 	searchMessages(QString(), chat);
 }
 
-bool Widget::setSearchInChat(Key chat, PeerData *from) {
+bool Widget::setSearchInChat(
+		Key chat,
+		PeerData *from,
+		std::vector<Data::ReactionId> tags) {
 	if (_childList) {
-		if (_childList->setSearchInChat(chat, from)) {
+		if (_childList->setSearchInChat(chat, from, tags)) {
 			return true;
 		}
 		hideChildList();
@@ -2634,7 +2645,8 @@ bool Widget::setSearchInChat(Key chat, PeerData *from) {
 		if (_layout != Layout::Main) {
 			return false;
 		} else if (const auto migrateTo = peer->migrateTo()) {
-			return setSearchInChat(peer->owner().history(migrateTo), from);
+			const auto to = peer->owner().history(migrateTo);
+			return setSearchInChat(to, from, tags);
 		} else if (const auto migrateFrom = peer->migrateFrom()) {
 			_searchInMigrated = peer->owner().history(migrateFrom);
 		}
@@ -2653,7 +2665,8 @@ bool Widget::setSearchInChat(Key chat, PeerData *from) {
 	if (_searchInChat && _layout == Layout::Main) {
 		controller()->closeFolder();
 	}
-	_inner->searchInChat(_searchInChat, _searchFromAuthor);
+	_searchTags = std::move(tags);
+	_inner->searchInChat(_searchInChat, _searchFromAuthor, _searchTags);
 	_searchTagsLifetime = _inner->searchTagsValue(
 	) | rpl::start_with_next([=](std::vector<Data::ReactionId> &&list) {
 		if (_searchTags != list) {
diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h
index 5ab53e2f9..3c8373cb1 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_widget.h
+++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h
@@ -117,7 +117,7 @@ public:
 
 	void scrollToEntry(const RowDescriptor &entry);
 
-	void searchMessages(const QString &query, Key inChat = {});
+	void searchMessages(QString query, Key inChat = {});
 	void searchTopics();
 	void searchMore();
 
@@ -180,7 +180,10 @@ private:
 	void trackScroll(not_null<Ui::RpWidget*> widget);
 	[[nodiscard]] bool searchForPeersRequired(const QString &query) const;
 	[[nodiscard]] bool searchForTopicsRequired(const QString &query) const;
-	bool setSearchInChat(Key chat, PeerData *from = nullptr);
+	bool setSearchInChat(
+		Key chat,
+		PeerData *from = nullptr,
+		std::vector<Data::ReactionId> tags = {});
 	void showCalendar();
 	void showSearchFrom();
 	void showMainMenu();
diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp
index f319dfc9a..b53fb1454 100644
--- a/Telegram/SourceFiles/history/view/history_view_message.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_message.cpp
@@ -2965,8 +2965,14 @@ void Message::refreshReactions() {
 	if (!_reactions) {
 		const auto handlerFactory = [=](ReactionId id) {
 			const auto weak = base::make_weak(this);
-			return std::make_shared<LambdaClickHandler>([=] {
+			return std::make_shared<LambdaClickHandler>([=](
+					ClickContext context) {
 				if (const auto strong = weak.get()) {
+					if (strong->data()->reactionsAreTags()) {
+						const auto tag = Data::SearchTagToQuery(id);
+						HashtagClickHandler(tag).onClick(context);
+						return;
+					}
 					strong->data()->toggleReaction(
 						id,
 						HistoryItem::ReactionSource::Existing);
diff --git a/Telegram/lib_base b/Telegram/lib_base
index 0d111bd46..bc78a03b1 160000
--- a/Telegram/lib_base
+++ b/Telegram/lib_base
@@ -1 +1 @@
-Subproject commit 0d111bd4633324533195aa0a840730b4bf6ba75b
+Subproject commit bc78a03b12b40ee6264ad2235465197b89588288

From 3c6037a79849f55e6dd269a4107aed06780940ac Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Tue, 9 Jan 2024 13:11:06 +0400
Subject: [PATCH 26/73] Custom context menu for saved-tags reactions.

---
 Telegram/Resources/langs/lang.strings         |  4 +-
 .../data/data_message_reactions.cpp           |  6 +-
 .../SourceFiles/data/data_message_reactions.h |  2 +
 .../view/history_view_context_menu.cpp        | 63 +++++++++++++++++++
 .../history_view_reactions_button.cpp         |  3 +-
 .../reactions/history_view_reactions_button.h |  1 +
 6 files changed, 75 insertions(+), 4 deletions(-)

diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index 9d0707518..98055e5fc 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -2749,7 +2749,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 "lng_context_seen_reacted#other" = "{count} Reacted";
 "lng_context_seen_reacted_none" = "Nobody Reacted";
 "lng_context_seen_reacted_all" = "Show All Reactions";
-"lng_context_set_as_quick" = "Set As Quick";
+"lng_context_set_as_quick" = "Set as Quick";
+"lng_context_filter_by_tag" = "Filter by Tag";
+"lng_context_remove_tag" = "Remove Tag";
 "lng_context_delete_from_disk" = "Delete from disk";
 "lng_context_delete_all_files" = "Delete all files";
 "lng_context_save_custom_sound" = "Save for notifications";
diff --git a/Telegram/SourceFiles/data/data_message_reactions.cpp b/Telegram/SourceFiles/data/data_message_reactions.cpp
index 9e1726b7a..4ba9c2db4 100644
--- a/Telegram/SourceFiles/data/data_message_reactions.cpp
+++ b/Telegram/SourceFiles/data/data_message_reactions.cpp
@@ -179,6 +179,7 @@ PossibleItemReactionsRef LookupPossibleReactions(
 			}
 		}
 		result.customAllowed = premiumPossible;
+		result.tags = true;
 	} else if (limited) {
 		result.recent.reserve(all.size());
 		add([&](const Reaction &reaction) {
@@ -243,11 +244,12 @@ PossibleItemReactionsRef LookupPossibleReactions(
 
 PossibleItemReactions::PossibleItemReactions(
 	const PossibleItemReactionsRef &other)
-	: recent(other.recent | ranges::views::transform([](const auto &value) {
+: recent(other.recent | ranges::views::transform([](const auto &value) {
 	return *value;
 }) | ranges::to_vector)
 , morePremiumAvailable(other.morePremiumAvailable)
-, customAllowed(other.customAllowed) {
+, customAllowed(other.customAllowed)
+, tags(other.tags){
 }
 
 Reactions::Reactions(not_null<Session*> owner)
diff --git a/Telegram/SourceFiles/data/data_message_reactions.h b/Telegram/SourceFiles/data/data_message_reactions.h
index 2743bffc8..5e8721665 100644
--- a/Telegram/SourceFiles/data/data_message_reactions.h
+++ b/Telegram/SourceFiles/data/data_message_reactions.h
@@ -42,6 +42,7 @@ struct PossibleItemReactionsRef {
 	std::vector<not_null<const Reaction*>> recent;
 	bool morePremiumAvailable = false;
 	bool customAllowed = false;
+	bool tags = false;
 };
 
 struct PossibleItemReactions {
@@ -51,6 +52,7 @@ struct PossibleItemReactions {
 	std::vector<Reaction> recent;
 	bool morePremiumAvailable = false;
 	bool customAllowed = false;
+	bool tags = false;
 };
 
 [[nodiscard]] PossibleItemReactionsRef LookupPossibleReactions(
diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp
index 22fcc710b..856639aa8 100644
--- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp
@@ -26,6 +26,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "history/view/media/history_view_web_page.h"
 #include "history/view/reactions/history_view_reactions_list.h"
 #include "ui/widgets/popup_menu.h"
+#include "ui/widgets/menu/menu_action.h"
+#include "ui/widgets/menu/menu_common.h"
 #include "ui/widgets/menu/menu_multiline_action.h"
 #include "ui/image/image.h"
 #include "ui/toast/toast.h"
@@ -1293,6 +1295,62 @@ void AddWhoReactedAction(
 		showAllChosen));
 }
 
+void ShowTagMenu(
+		not_null<base::unique_qptr<Ui::PopupMenu>*> menu,
+		QPoint position,
+		not_null<QWidget*> context,
+		not_null<HistoryItem*> item,
+		const Data::ReactionId &id,
+		not_null<Window::SessionController*> controller) {
+	using namespace Data;
+	const auto itemId = item->fullId();
+	const auto owner = &controller->session().data();
+	*menu = base::make_unique_q<Ui::PopupMenu>(
+		context,
+		st::popupMenuExpandedSeparator);
+	(*menu)->addAction(tr::lng_context_filter_by_tag(tr::now), [=] {
+		HashtagClickHandler(SearchTagToQuery(id)).onClick({
+			.button = Qt::LeftButton,
+			.other = QVariant::fromValue(ClickHandlerContext{
+				.sessionWindow = controller,
+			}),
+		});
+	}, &st::menuIconFave);
+
+	const auto removeTag = [=] {
+		if (const auto item = owner->message(itemId)) {
+			const auto &list = item->reactions();
+			if (ranges::contains(list, id, &MessageReaction::id)) {
+				item->toggleReaction(
+					id,
+					HistoryItem::ReactionSource::Quick);
+			}
+		}
+	};
+	(*menu)->addAction(base::make_unique_q<Ui::Menu::Action>(
+		(*menu)->menu(),
+		st::menuWithIconsAttention,
+		Ui::Menu::CreateAction(
+			(*menu)->menu(),
+			tr::lng_context_remove_tag(tr::now),
+			removeTag),
+		&st::menuIconDisableAttention,
+		&st::menuIconDisableAttention));
+
+	if (const auto custom = id.custom()) {
+		if (const auto set = owner->document(custom)->sticker()) {
+			if (set->set.id) {
+				AddEmojiPacksAction(
+					menu->get(),
+					{ set->set },
+					EmojiPacksSource::Reaction,
+					controller);
+			}
+		}
+	}
+	(*menu)->popup(position);
+}
+
 void ShowWhoReactedMenu(
 		not_null<base::unique_qptr<Ui::PopupMenu>*> menu,
 		QPoint position,
@@ -1301,6 +1359,11 @@ void ShowWhoReactedMenu(
 		const Data::ReactionId &id,
 		not_null<Window::SessionController*> controller,
 		rpl::lifetime &lifetime) {
+	if (item->reactionsAreTags()) {
+		ShowTagMenu(menu, position, context, item, id, controller);
+		return;
+	}
+
 	struct State {
 		int addedToBottom = 0;
 	};
diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_button.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_button.cpp
index 2081a85e7..171ad9d03 100644
--- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_button.cpp
+++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_button.cpp
@@ -451,6 +451,7 @@ void Manager::applyList(const Data::PossibleItemReactionsRef &reactions) {
 			: reactions.morePremiumAvailable
 			? Button::Premium
 			: */Button::None));
+	_tagsStrip = reactions.tags;
 }
 
 QMargins Manager::innerMargins() const {
@@ -814,7 +815,7 @@ bool Manager::showContextMenu(
 		const ReactionId &favorite) {
 	const auto selected = _strip.selected();
 	const auto id = std::get_if<ReactionId>(&selected);
-	if (!id || id->empty()) {
+	if (!id || id->empty() || _tagsStrip) {
 		return false;
 	} else if (*id == favorite) {
 		return true;
diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_button.h b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_button.h
index 793382198..eb9c7fc5d 100644
--- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_button.h
+++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_button.h
@@ -235,6 +235,7 @@ private:
 
 	rpl::variable<int> _uniqueLimit = 0;
 	bool _showingAll = false;
+	bool _tagsStrip = false;
 
 	std::optional<ButtonParameters> _scheduledParameters;
 	base::Timer _buttonShowTimer;

From 4471eb587d9091ffeb9e115d51001e53a60d0a19 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Tue, 9 Jan 2024 13:11:25 +0400
Subject: [PATCH 27/73] Fix possible crash in gift box sticker lookup.

---
 Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.cpp | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.cpp b/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.cpp
index e20c361d6..f54b1f1d3 100644
--- a/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.cpp
+++ b/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.cpp
@@ -26,6 +26,8 @@ DocumentData *GiftBoxPack::lookup(int months) const {
 	const auto fallback = _documents.empty() ? nullptr : _documents[0];
 	if (it == begin(_localMonths)) {
 		return fallback;
+	} else if (it == end(_localMonths)) {
+		return _documents.back();
 	}
 	const auto left = *(it - 1);
 	const auto right = *it;

From 9401e7cb515896d110810ce9181c5903e7871e73 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Tue, 9 Jan 2024 17:51:29 +0400
Subject: [PATCH 28/73] Add "View reactions" phrase to the langpack.

---
 Telegram/Resources/langs/lang.strings                           | 1 +
 .../SourceFiles/media/stories/media_stories_recent_views.cpp    | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index 98055e5fc..347fdbdc7 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -4348,6 +4348,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 "lng_stories_views#one" = "{count} view";
 "lng_stories_views#other" = "{count} views";
 "lng_stories_no_views" = "No views";
+"lng_stories_view_reactions" = "View reactions";
 "lng_stories_unsupported" = "This story is not supported\nby your version of Telegram.";
 "lng_stories_cant_reply" = "You can't reply to this story.";
 "lng_stories_about_silent" = "This video has no sound.";
diff --git a/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp b/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp
index 2521de47f..442199300 100644
--- a/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp
+++ b/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp
@@ -421,7 +421,7 @@ void RecentViews::updatePartsGeometry() {
 
 void RecentViews::updateText() {
 	const auto text = (_data.type == RecentViewsType::Channel)
-		? u"View reactions"_q
+		? tr::lng_stories_view_reactions(tr::now)
 		: _data.views
 		? (tr::lng_stories_views(tr::now, lt_count, _data.views)
 			+ (_data.reactions

From e2439984ae486c7fff6f634a5273028bc51f64ff Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Tue, 9 Jan 2024 21:51:05 -0800
Subject: [PATCH 29/73] Pass whole point in mapFromGlobal(mapToGlobal).

Fixes #27237.
---
 .../history/admin_log/history_admin_log_inner.cpp    |  6 +++---
 .../SourceFiles/history/history_inner_widget.cpp     | 12 +++++++-----
 .../history/view/history_view_list_widget.cpp        |  6 +++---
 .../SourceFiles/window/window_session_controller.cpp |  2 +-
 .../SourceFiles/window/window_session_controller.h   |  6 +++---
 5 files changed, 17 insertions(+), 15 deletions(-)

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 fb6f950e0..2598e1f36 100644
--- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp
+++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp
@@ -943,10 +943,10 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
 	auto clip = e->rect();
 	auto context = _controller->preparePaintContext({
 		.theme = _theme.get(),
-		.visibleAreaTop = _visibleTop,
-		.visibleAreaTopGlobal = mapToGlobal(QPoint(0, _visibleTop)).y(),
-		.visibleAreaWidth = width(),
 		.clip = clip,
+		.visibleAreaPositionGlobal = mapToGlobal(QPoint(0, _visibleTop)),
+		.visibleAreaTop = _visibleTop,
+		.visibleAreaWidth = width(),
 	});
 	if (_items.empty() && _upLoaded && _downLoaded) {
 		paintEmpty(p, context.st);
diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp
index 353bb6870..c824976cf 100644
--- a/Telegram/SourceFiles/history/history_inner_widget.cpp
+++ b/Telegram/SourceFiles/history/history_inner_widget.cpp
@@ -979,14 +979,16 @@ void HistoryInner::paintEmpty(
 
 Ui::ChatPaintContext HistoryInner::preparePaintContext(
 		const QRect &clip) const {
-	const auto visibleAreaTopGlobal = mapToGlobal(
-		QPoint(0, _visibleAreaTop)).y();
+	const auto visibleAreaPositionGlobal = mapToGlobal(
+		QPoint(0, _visibleAreaTop));
+	const auto visibleAreaPositionLocal = mapFromGlobal(
+		visibleAreaPositionGlobal);
 	return _controller->preparePaintContext({
 		.theme = _theme.get(),
-		.visibleAreaTop = _visibleAreaTop,
-		.visibleAreaTopGlobal = visibleAreaTopGlobal,
-		.visibleAreaWidth = width(),
 		.clip = clip,
+		.visibleAreaPositionGlobal = visibleAreaPositionGlobal,
+		.visibleAreaTop = _visibleAreaTop,
+		.visibleAreaWidth = width(),
 	});
 }
 
diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
index 50822615c..984cc0e79 100644
--- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
@@ -2009,10 +2009,10 @@ Ui::ChatPaintContext ListWidget::preparePaintContext(
 		const QRect &clip) const {
 	return controller()->preparePaintContext({
 		.theme = _delegate->listChatTheme(),
-		.visibleAreaTop = _visibleTop,
-		.visibleAreaTopGlobal = mapToGlobal(QPoint(0, _visibleTop)).y(),
-		.visibleAreaWidth = width(),
 		.clip = clip,
+		.visibleAreaPositionGlobal = mapToGlobal(QPoint(0, _visibleTop)),
+		.visibleAreaTop = _visibleTop,
+		.visibleAreaWidth = width(),
 	});
 }
 
diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp
index 3f78e1850..b4be01c6d 100644
--- a/Telegram/SourceFiles/window/window_session_controller.cpp
+++ b/Telegram/SourceFiles/window/window_session_controller.cpp
@@ -2742,7 +2742,7 @@ void SessionController::openPeerStories(
 HistoryView::PaintContext SessionController::preparePaintContext(
 		PaintContextArgs &&args) {
 	const auto visibleAreaTopLocal = content()->mapFromGlobal(
-		QPoint(0, args.visibleAreaTopGlobal)).y();
+		args.visibleAreaPositionGlobal).y();
 	const auto viewport = QRect(
 		0,
 		args.visibleAreaTop - visibleAreaTopLocal,
diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h
index 4ef7b1791..d3452c8ca 100644
--- a/Telegram/SourceFiles/window/window_session_controller.h
+++ b/Telegram/SourceFiles/window/window_session_controller.h
@@ -565,10 +565,10 @@ public:
 
 	struct PaintContextArgs {
 		not_null<Ui::ChatTheme*> theme;
-		int visibleAreaTop = 0;
-		int visibleAreaTopGlobal = 0;
-		int visibleAreaWidth = 0;
 		QRect clip;
+		QPoint visibleAreaPositionGlobal;
+		int visibleAreaTop = 0;
+		int visibleAreaWidth = 0;
 	};
 	[[nodiscard]] Ui::ChatPaintContext preparePaintContext(
 		PaintContextArgs &&args);

From 0dfe37f998b42ca095fd357d835e4255ec1011a7 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Wed, 10 Jan 2024 08:28:39 -0800
Subject: [PATCH 30/73] Mirror my outgoing video in video chats.

---
 .../SourceFiles/calls/calls_video_bubble.cpp     | 16 ++++++++--------
 .../calls/group/calls_group_panel.cpp            |  4 +++-
 .../calls/group/calls_group_viewport.cpp         |  6 ++++--
 .../calls/group/calls_group_viewport.h           |  3 ++-
 .../calls/group/calls_group_viewport_opengl.cpp  |  6 ++++++
 .../calls/group/calls_group_viewport_raster.cpp  |  4 ++--
 .../calls/group/calls_group_viewport_tile.cpp    | 10 ++++++++--
 .../calls/group/calls_group_viewport_tile.h      |  8 +++++++-
 8 files changed, 40 insertions(+), 17 deletions(-)

diff --git a/Telegram/SourceFiles/calls/calls_video_bubble.cpp b/Telegram/SourceFiles/calls/calls_video_bubble.cpp
index aecabfd55..043ee8c41 100644
--- a/Telegram/SourceFiles/calls/calls_video_bubble.cpp
+++ b/Telegram/SourceFiles/calls/calls_video_bubble.cpp
@@ -127,12 +127,13 @@ void VideoBubble::paint() {
 		const auto inner = _content.rect().marginsRemoved(padding);
 		Ui::Shadow::paint(p, inner, _content.width(), st::boxRoundShadow);
 		const auto factor = cIntRetinaFactor();
+		const auto left = _mirrored
+			? (_frame.width() - (inner.width() * factor))
+			: 0;
 		p.drawImage(
 			inner,
 			_frame,
-			QRect(
-				QPoint(_frame.width() - (inner.width() * factor), 0),
-				inner.size() * factor));
+			QRect(QPoint(left, 0), inner.size() * factor));
 	}
 	_track->markFrameShown();
 }
@@ -152,11 +153,10 @@ void VideoBubble::prepareFrame() {
 		.resize = size,
 		.outer = size,
 	};
-	const auto frame = _track->frame(request).mirrored(!_mirrored, false);
+	const auto frame = _track->frame(request);
 	if (_frame.width() < size.width() || _frame.height() < size.height()) {
-		_frame = QImage(
-			size * cIntRetinaFactor(),
-			QImage::Format_ARGB32_Premultiplied);
+		_frame = QImage(size, QImage::Format_ARGB32_Premultiplied);
+		_frame.fill(Qt::transparent);
 	}
 	Assert(_frame.width() >= frame.width()
 		&& _frame.height() >= frame.height());
@@ -174,7 +174,7 @@ void VideoBubble::prepareFrame() {
 		ImageRoundRadius::Large,
 		RectPart::AllCorners,
 		QRect(QPoint(), size)
-	).mirrored(true, false);
+	).mirrored(_mirrored, false);
 }
 
 void VideoBubble::setState(Webrtc::VideoState state) {
diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp
index d3bac6aeb..6455ad040 100644
--- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp
+++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp
@@ -1060,11 +1060,13 @@ void Panel::setupVideo(not_null<Viewport*> viewport) {
 			_call->videoEndpointLargeValue(),
 			_call->videoEndpointPinnedValue()
 		) | rpl::map(_1 == endpoint && _2);
+		const auto self = (endpoint.peer == _call->joinAs());
 		viewport->add(
 			endpoint,
 			VideoTileTrack{ GroupCall::TrackPointer(track), row },
 			GroupCall::TrackSizeValue(track),
-			std::move(pinned));
+			std::move(pinned),
+			self);
 	};
 	for (const auto &[endpoint, track] : _call->activeVideoTracks()) {
 		setupTile(endpoint, track);
diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport.cpp b/Telegram/SourceFiles/calls/group/calls_group_viewport.cpp
index 48c76d0ae..9623171b9 100644
--- a/Telegram/SourceFiles/calls/group/calls_group_viewport.cpp
+++ b/Telegram/SourceFiles/calls/group/calls_group_viewport.cpp
@@ -237,13 +237,15 @@ void Viewport::add(
 		const VideoEndpoint &endpoint,
 		VideoTileTrack track,
 		rpl::producer<QSize> trackSize,
-		rpl::producer<bool> pinned) {
+		rpl::producer<bool> pinned,
+		bool self) {
 	_tiles.push_back(std::make_unique<VideoTile>(
 		endpoint,
 		track,
 		std::move(trackSize),
 		std::move(pinned),
-		[=] { widget()->update(); }));
+		[=] { widget()->update(); },
+		self));
 
 	_tiles.back()->trackSizeValue(
 	) | rpl::filter([](QSize size) {
diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport.h b/Telegram/SourceFiles/calls/group/calls_group_viewport.h
index c6567e037..9d5021f85 100644
--- a/Telegram/SourceFiles/calls/group/calls_group_viewport.h
+++ b/Telegram/SourceFiles/calls/group/calls_group_viewport.h
@@ -80,7 +80,8 @@ public:
 		const VideoEndpoint &endpoint,
 		VideoTileTrack track,
 		rpl::producer<QSize> trackSize,
-		rpl::producer<bool> pinned);
+		rpl::producer<bool> pinned,
+		bool self);
 	void remove(const VideoEndpoint &endpoint);
 	void showLarge(const VideoEndpoint &endpoint);
 
diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp b/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp
index 946c6dca8..e11644e68 100644
--- a/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp
+++ b/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp
@@ -531,6 +531,12 @@ void Viewport::RendererGL::paintTile(
 		{ { 1.f, 0.f } },
 		{ { 0.f, 0.f } },
 	} };
+	if (tile->mirror()) {
+		std::swap(toBlurTexCoords[0], toBlurTexCoords[1]);
+		std::swap(toBlurTexCoords[2], toBlurTexCoords[3]);
+		std::swap(texCoords[0], texCoords[1]);
+		std::swap(texCoords[2], texCoords[3]);
+	}
 	if (const auto shift = (frameRotation / 90); shift > 0) {
 		std::rotate(
 			toBlurTexCoords.begin(),
diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport_raster.cpp b/Telegram/SourceFiles/calls/group/calls_group_viewport_raster.cpp
index f85929ea1..ab335c55c 100644
--- a/Telegram/SourceFiles/calls/group/calls_group_viewport_raster.cpp
+++ b/Telegram/SourceFiles/calls/group/calls_group_viewport_raster.cpp
@@ -105,14 +105,14 @@ void Viewport::RendererSW::paintTile(
 		tileData.blurredFrame = Images::BlurLargeImage(
 			data.original.scaled(
 				VideoTile::PausedVideoSize(),
-				Qt::KeepAspectRatio),
+				Qt::KeepAspectRatio).mirrored(tile->mirror(), false),
 			kBlurRadius);
 	}
 	const auto &image = _userpicFrame
 		? tileData.userpicFrame
 		: _pausedFrame
 		? tileData.blurredFrame
-		: data.original;
+		: data.original.mirrored(tile->mirror(), false);
 	const auto frameRotation = _userpicFrame ? 0 : data.rotation;
 	Assert(!image.isNull());
 
diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.cpp b/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.cpp
index 58ac0fb9d..1b31a2012 100644
--- a/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.cpp
+++ b/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.cpp
@@ -28,12 +28,14 @@ Viewport::VideoTile::VideoTile(
 	VideoTileTrack track,
 	rpl::producer<QSize> trackSize,
 	rpl::producer<bool> pinned,
-	Fn<void()> update)
+	Fn<void()> update,
+	bool self)
 : _endpoint(endpoint)
 , _update(std::move(update))
 , _track(std::move(track))
 , _trackSize(std::move(trackSize))
-, _rtmp(endpoint.rtmp()) {
+, _rtmp(endpoint.rtmp())
+, _self(self) {
 	Expects(_track.track != nullptr);
 	Expects(_track.row != nullptr);
 
@@ -48,6 +50,10 @@ Viewport::VideoTile::VideoTile(
 	setup(std::move(pinned));
 }
 
+bool Viewport::VideoTile::mirror() const {
+	return _self && (_endpoint.type == VideoEndpointType::Camera);
+}
+
 QRect Viewport::VideoTile::pinOuter() const {
 	return _pinOuter;
 }
diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.h b/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.h
index 445bf2d7e..6f85f4a01 100644
--- a/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.h
+++ b/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.h
@@ -28,7 +28,8 @@ public:
 		VideoTileTrack track,
 		rpl::producer<QSize> trackSize,
 		rpl::producer<bool> pinned,
-		Fn<void()> update);
+		Fn<void()> update,
+		bool self);
 
 	[[nodiscard]] not_null<Webrtc::VideoTrack*> track() const {
 		return _track.track;
@@ -54,6 +55,10 @@ public:
 	[[nodiscard]] bool visible() const {
 		return !_hidden && !_geometry.isEmpty();
 	}
+	[[nodiscard]] bool self() const {
+		return _self;
+	}
+	[[nodiscard]] bool mirror() const;
 	[[nodiscard]] QRect pinOuter() const;
 	[[nodiscard]] QRect pinInner() const;
 	[[nodiscard]] QRect backOuter() const;
@@ -123,6 +128,7 @@ private:
 	bool _pinned = false;
 	bool _hidden = true;
 	bool _rtmp = false;
+	bool _self = false;
 	std::optional<VideoQuality> _quality;
 
 	rpl::lifetime _lifetime;

From 3eefaac8857852add4c36e6b984f437e14ebcc5c Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Wed, 10 Jan 2024 09:54:30 -0800
Subject: [PATCH 31/73] Fix screen sharing source choose window.

---
 .../calls/group/ui/desktop_capture_choose_source.cpp          | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp b/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp
index 87ac643d3..ad59290c5 100644
--- a/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp
+++ b/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp
@@ -114,6 +114,7 @@ private:
 	const not_null<RoundButton*> _finish;
 	const not_null<Checkbox*> _withAudio;
 
+	QSize _fixedSize;
 	std::vector<std::unique_ptr<Source>> _sources;
 	Source *_selected = nullptr;
 	QString _selectedId;
@@ -337,7 +338,7 @@ void ChooseSourceProcess::setupPanel() {
 		+ (kRows - 1) * skips.height()
 		+ (st::desktopCaptureSourceSize.height() / 2)
 		+ bottomHeight;
-	_window->setFixedSize({ width, height });
+	_fixedSize = QSize(width, height);
 	_window->setStaysOnTop(true);
 
 	_window->body()->paintRequest(
@@ -598,6 +599,7 @@ void ChooseSourceProcess::setupGeometryWithParent(
 	if (parentScreen && myScreen != parentScreen) {
 		_window->windowHandle()->setScreen(parentScreen);
 	}
+	_window->setFixedSize(_fixedSize);
 	_window->move(
 		parent->x() + (parent->width() - _window->width()) / 2,
 		parent->y() + (parent->height() - _window->height()) / 2);

From fc2f41096fafc620c4cdde84797d9ce471a0169f Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Fri, 12 Jan 2024 13:57:47 +0400
Subject: [PATCH 32/73] Attempt to fix initial window position on Windows.

---
 Telegram/SourceFiles/core/application.cpp         | 2 +-
 Telegram/SourceFiles/window/main_window.cpp       | 5 +++--
 Telegram/SourceFiles/window/main_window.h         | 2 +-
 Telegram/SourceFiles/window/window_controller.cpp | 4 ++++
 Telegram/SourceFiles/window/window_controller.h   | 1 +
 Telegram/lib_ui                                   | 2 +-
 6 files changed, 11 insertions(+), 5 deletions(-)

diff --git a/Telegram/SourceFiles/core/application.cpp b/Telegram/SourceFiles/core/application.cpp
index 399246973..998793df3 100644
--- a/Telegram/SourceFiles/core/application.cpp
+++ b/Telegram/SourceFiles/core/application.cpp
@@ -360,7 +360,7 @@ void Application::run() {
 	startDomain();
 	startTray();
 
-	_lastActivePrimaryWindow->widget()->show();
+	_lastActivePrimaryWindow->firstShow();
 
 	startMediaView();
 
diff --git a/Telegram/SourceFiles/window/main_window.cpp b/Telegram/SourceFiles/window/main_window.cpp
index d25992051..3f8c9b5ba 100644
--- a/Telegram/SourceFiles/window/main_window.cpp
+++ b/Telegram/SourceFiles/window/main_window.cpp
@@ -475,7 +475,6 @@ void MainWindow::init() {
 	}
 	refreshTitleWidget();
 
-	initGeometry();
 	updateTitle();
 	updateWindowIcon();
 }
@@ -772,9 +771,10 @@ QRect MainWindow::countInitialGeometry(
 	return position.rect();
 }
 
-void MainWindow::initGeometry() {
+void MainWindow::firstShow() {
 	updateMinimumSize();
 	if (initGeometryFromSystem()) {
+		show();
 		return;
 	}
 	const auto geometry = countInitialGeometry(initialPosition());
@@ -784,6 +784,7 @@ void MainWindow::initGeometry() {
 		).arg(geometry.width()
 		).arg(geometry.height()));
 	setGeometry(geometry);
+	show();
 }
 
 void MainWindow::positionUpdated() {
diff --git a/Telegram/SourceFiles/window/main_window.h b/Telegram/SourceFiles/window/main_window.h
index e59967e80..8feb329a9 100644
--- a/Telegram/SourceFiles/window/main_window.h
+++ b/Telegram/SourceFiles/window/main_window.h
@@ -132,6 +132,7 @@ public:
 	void recountGeometryConstraints();
 	virtual void updateControlsGeometry();
 
+	void firstShow();
 	bool minimizeToTray();
 	void updateGlobalMenu() {
 		updateGlobalMenuHook();
@@ -197,7 +198,6 @@ private:
 	[[nodiscard]] Core::WindowPosition nextInitialChildPosition(
 		bool primary);
 	[[nodiscard]] QRect countInitialGeometry(Core::WindowPosition position);
-	void initGeometry();
 
 	bool computeIsActive() const;
 
diff --git a/Telegram/SourceFiles/window/window_controller.cpp b/Telegram/SourceFiles/window/window_controller.cpp
index 917cd6d66..19c12c30e 100644
--- a/Telegram/SourceFiles/window/window_controller.cpp
+++ b/Telegram/SourceFiles/window/window_controller.cpp
@@ -311,6 +311,10 @@ void Controller::showTermsDelete() {
 	}));
 }
 
+void Controller::firstShow() {
+	_widget.firstShow();
+}
+
 void Controller::finishFirstShow() {
 	_widget.finishFirstShow();
 	checkThemeEditor();
diff --git a/Telegram/SourceFiles/window/window_controller.h b/Telegram/SourceFiles/window/window_controller.h
index 36c35979e..cd5979006 100644
--- a/Telegram/SourceFiles/window/window_controller.h
+++ b/Telegram/SourceFiles/window/window_controller.h
@@ -74,6 +74,7 @@ public:
 
 	[[nodiscard]] Adaptive &adaptive() const;
 
+	void firstShow();
 	void finishFirstShow();
 
 	void setupPasscodeLock();
diff --git a/Telegram/lib_ui b/Telegram/lib_ui
index 00f5bdacc..aa39793a9 160000
--- a/Telegram/lib_ui
+++ b/Telegram/lib_ui
@@ -1 +1 @@
-Subproject commit 00f5bdaccdff4f53e8ba347f09f2de161b7ca7da
+Subproject commit aa39793a91f1186879c15c214a2671d78eab5085

From f8caa02f108d40358bfe5f4cc87cc6229d630058 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Fri, 12 Jan 2024 13:58:12 +0400
Subject: [PATCH 33/73] Fix animating list->chat transition in single column.

---
 Telegram/SourceFiles/mainwidget.cpp | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp
index fe92a6de1..fef1fb39c 100644
--- a/Telegram/SourceFiles/mainwidget.cpp
+++ b/Telegram/SourceFiles/mainwidget.cpp
@@ -1524,7 +1524,9 @@ bool MainWidget::saveSectionInStack(
 			_history->msgId(),
 			_history->replyReturns()));
 	} else {
-		return false;
+		// We pretend that we "saved" the chats list state in stack,
+		// so that we do animate a transition from chats list to a section.
+		return true;
 	}
 	const auto raw = _stack.back().get();
 	raw->setThirdSectionWeak(_thirdSection.data());

From 37067f17e2523256daab3fa16e9d565262586185 Mon Sep 17 00:00:00 2001
From: 23rd <23rd@vivaldi.net>
Date: Tue, 9 Jan 2024 14:49:35 +0300
Subject: [PATCH 34/73] Added new viewer widget for voice messages with ttl.

---
 Telegram/CMakeLists.txt                       |   2 +
 Telegram/Resources/langs/lang.strings         |   3 +
 .../chat_helpers/chat_helpers.style           |  14 +
 .../chat_helpers/ttl_media_layer_widget.cpp   | 273 ++++++++++++++++++
 .../chat_helpers/ttl_media_layer_widget.h     |  22 ++
 .../data/data_document_resolver.cpp           |  15 +-
 .../history/view/history_view_element.h       |   1 +
 .../history/view/history_view_message.cpp     |   4 +-
 .../view/media/history_view_document.cpp      |  77 +++--
 .../view/media/history_view_document.h        |   2 +
 10 files changed, 379 insertions(+), 34 deletions(-)
 create mode 100644 Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp
 create mode 100644 Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.h

diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt
index 59c701340..b1fc1570d 100644
--- a/Telegram/CMakeLists.txt
+++ b/Telegram/CMakeLists.txt
@@ -406,6 +406,8 @@ PRIVATE
     chat_helpers/tabbed_section.h
     chat_helpers/tabbed_selector.cpp
     chat_helpers/tabbed_selector.h
+    chat_helpers/ttl_media_layer_widget.cpp
+    chat_helpers/ttl_media_layer_widget.h
     core/application.cpp
     core/application.h
     core/base_integration.cpp
diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index 347fdbdc7..cec8bf36e 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -1719,6 +1719,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 "lng_ttl_voice_expired" = "Voice message expired";
 "lng_ttl_round_sent" = "You sent a self-destructing video message.";
 "lng_ttl_round_expired" = "Round message expired";
+"lng_ttl_voice_tooltip_in" = "This voice message can only be played once.";
+"lng_ttl_voice_tooltip_out" = "This message will disappear once **{user}** plays it once.";
+"lng_ttl_voice_close_in" = "Delete and close";
 
 "lng_profile_add_more_after_create" = "You will be able to add more members after you create the group.";
 "lng_profile_camera_title" = "Capture yourself";
diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style
index c86e98b75..a7f94d301 100644
--- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style
+++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style
@@ -1266,3 +1266,17 @@ dragDropColor: windowActiveTextFg;
 dragMargin: margins(0px, 10px, 0px, 10px);
 dragPadding: margins(20px, 10px, 20px, 10px);
 dragHeight: 72px;
+
+ttlMediaImportantTooltipLabel: FlatLabel(defaultImportantTooltipLabel) {
+	style: TextStyle(defaultTextStyle) {
+		font: font(14px);
+	}
+}
+ttlMediaButton: RoundButton(defaultActiveButton) {
+	textBg: shadowFg;
+	textBgOver: shadowFg;
+	ripple: universalRippleAnimation;
+	height: 31px;
+	textTop: 6px;
+}
+ttlMediaButtonBottomSkip: 14px;
diff --git a/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp b/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp
new file mode 100644
index 000000000..91403a2c2
--- /dev/null
+++ b/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.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 "chat_helpers/ttl_media_layer_widget.h"
+
+#include "base/event_filter.h"
+#include "data/data_session.h"
+#include "editor/editor_layer_widget.h"
+#include "history/history.h"
+#include "history/history_item.h"
+#include "history/view/history_view_element.h"
+#include "history/view/media/history_view_document.h"
+#include "lang/lang_keys.h"
+#include "main/main_session.h"
+#include "mainwidget.h"
+#include "media/audio/media_audio.h"
+#include "media/player/media_player_instance.h"
+#include "ui/chat/chat_style.h"
+#include "ui/chat/chat_theme.h"
+#include "ui/effects/path_shift_gradient.h"
+#include "ui/painter.h"
+#include "ui/text/text_utilities.h"
+#include "ui/widgets/buttons.h"
+#include "ui/widgets/labels.h"
+#include "ui/widgets/tooltip.h"
+#include "window/themes/window_theme.h"
+#include "window/window_session_controller.h"
+#include "styles/style_chat.h"
+#include "styles/style_chat_helpers.h"
+#include "styles/style_dialogs.h"
+
+namespace ChatHelpers {
+namespace {
+
+class PreviewDelegate final : public HistoryView::DefaultElementDelegate {
+public:
+	PreviewDelegate(
+		not_null<QWidget*> parent,
+		not_null<Ui::ChatStyle*> st,
+		Fn<void()> update);
+
+	bool elementAnimationsPaused() override;
+	not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override;
+	HistoryView::Context elementContext() override;
+	bool elementIsChatWide() override;
+
+private:
+	const not_null<QWidget*> _parent;
+	const std::unique_ptr<Ui::PathShiftGradient> _pathGradient;
+
+};
+
+PreviewDelegate::PreviewDelegate(
+	not_null<QWidget*> parent,
+	not_null<Ui::ChatStyle*> st,
+	Fn<void()> update)
+: _parent(parent)
+, _pathGradient(HistoryView::MakePathShiftGradient(st, update)) {
+}
+
+bool PreviewDelegate::elementAnimationsPaused() {
+	return _parent->window()->isActiveWindow();
+}
+
+not_null<Ui::PathShiftGradient*> PreviewDelegate::elementPathShiftGradient() {
+	return _pathGradient.get();
+}
+
+HistoryView::Context PreviewDelegate::elementContext() {
+	return HistoryView::Context::TTLViewer;
+}
+
+bool PreviewDelegate::elementIsChatWide() {
+	return true;
+}
+
+class PreviewWrap final : public Ui::RpWidget {
+public:
+	PreviewWrap(not_null<Ui::RpWidget*> parent, not_null<HistoryItem*> item);
+	~PreviewWrap();
+
+	[[nodiscard]] rpl::producer<> closeRequests() const;
+
+private:
+	void paintEvent(QPaintEvent *e) override;
+	[[nodiscard]] QRect elementRect() const;
+
+	const not_null<HistoryItem*> _item;
+	const std::unique_ptr<Ui::ChatTheme> _theme;
+	const std::unique_ptr<Ui::ChatStyle> _style;
+	const std::unique_ptr<PreviewDelegate> _delegate;
+	std::unique_ptr<HistoryView::Element> _element;
+	rpl::lifetime _elementLifetime;
+
+	rpl::event_stream<> _closeRequests;
+
+};
+
+PreviewWrap::PreviewWrap(
+	not_null<Ui::RpWidget*> parent,
+	not_null<HistoryItem*> item)
+: RpWidget(parent)
+, _item(item)
+, _theme(Window::Theme::DefaultChatThemeOn(lifetime()))
+, _style(std::make_unique<Ui::ChatStyle>(
+	item->history()->session().colorIndicesValue()))
+, _delegate(std::make_unique<PreviewDelegate>(
+	parent,
+	_style.get(),
+	[=] { update(elementRect()); })) {
+	_style->apply(_theme.get());
+
+	const auto session = &_item->history()->session();
+	session->data().viewRepaintRequest(
+	) | rpl::start_with_next([=](not_null<const HistoryView::Element*> view) {
+		if (view == _element.get()) {
+			update(elementRect());
+		}
+	}, lifetime());
+
+	const auto closeCallback = [=] { _closeRequests.fire({}); };
+
+	{
+		const auto close = Ui::CreateChild<Ui::RoundButton>(
+			this,
+			item->out()
+				? tr::lng_close()
+				: tr::lng_ttl_voice_close_in(),
+			st::ttlMediaButton);
+		close->setFullRadius(true);
+		close->setClickedCallback(closeCallback);
+		close->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
+
+		sizeValue(
+		) | rpl::start_with_next([=](const QSize &s) {
+			close->moveToLeft(
+				(s.width() - close->width()) / 2,
+				s.height() - close->height() - st::ttlMediaButtonBottomSkip);
+		}, close->lifetime());
+	}
+
+	QWidget::setAttribute(Qt::WA_OpaquePaintEvent, false);
+	_element = _item->createView(_delegate.get());
+
+	{
+		_element->initDimensions();
+		widthValue(
+		) | rpl::filter([=](int width) {
+			return width > st::msgMinWidth;
+		}) | rpl::start_with_next([=](int width) {
+			_element->resizeGetHeight(width);
+		}, _elementLifetime);
+	}
+
+	{
+		auto text = item->out()
+			? tr::lng_ttl_voice_tooltip_out(
+				lt_user,
+				rpl::single(
+					item->history()->peer->name()
+				) | rpl::map(Ui::Text::RichLangValue),
+				Ui::Text::RichLangValue)
+			: tr::lng_ttl_voice_tooltip_in(Ui::Text::RichLangValue);
+		const auto tooltip = Ui::CreateChild<Ui::ImportantTooltip>(
+			this,
+			object_ptr<Ui::PaddingWrap<Ui::FlatLabel>>(
+				this,
+				Ui::MakeNiceTooltipLabel(
+					parent,
+					std::move(text),
+					st::dialogsStoriesTooltipMaxWidth,
+					st::ttlMediaImportantTooltipLabel),
+				st::defaultImportantTooltip.padding),
+			st::dialogsStoriesTooltip);
+		tooltip->toggleFast(true);
+		sizeValue(
+		) | rpl::filter(
+			[](const QSize &s) { return !s.isNull(); }
+		) | rpl::take(1) | rpl::start_with_next([=](const QSize &s) {
+			if (s.isEmpty()) {
+				return;
+			}
+			auto area = elementRect();
+			area.setWidth(_element->media()
+				? _element->media()->width()
+				: _element->width());
+			tooltip->pointAt(area, RectPart::Top, [=](QSize size) {
+				return QPoint{
+					(area.width() - size.width()) / 2,
+					(s.height() - size.height() * 2 - _element->height()) / 2
+						- st::defaultImportantTooltip.padding.top(),
+				};
+			});
+		}, tooltip->lifetime());
+	}
+
+	HistoryView::TTLVoiceStops(
+		item->fullId()
+	) | rpl::start_with_next(closeCallback, lifetime());
+}
+
+QRect PreviewWrap::elementRect() const {
+	return QRect(
+		(width() - _element->width()) / 2,
+		(height() - _element->height()) / 2,
+		_element->width(),
+		_element->height());
+}
+
+rpl::producer<> PreviewWrap::closeRequests() const {
+	return _closeRequests.events();
+}
+
+PreviewWrap::~PreviewWrap() {
+	_elementLifetime.destroy();
+	_element = nullptr;
+}
+
+void PreviewWrap::paintEvent(QPaintEvent *e) {
+	if (!_element) {
+		return;
+	}
+
+	auto p = Painter(this);
+	const auto r = rect();
+
+	auto context = _theme->preparePaintContext(
+		_style.get(),
+		r,
+		e->rect(),
+		!window()->isActiveWindow());
+	context.outbg = _element->hasOutLayout();
+
+	p.translate(
+		(r.width() - _element->width()) / 2,
+		(r.height() - _element->height()) / 2);
+	_element->draw(p, context);
+}
+
+} // namespace
+
+void ShowTTLMediaLayerWidget(
+		not_null<Window::SessionController*> controller,
+		not_null<HistoryItem*> item) {
+	const auto parent = controller->content();
+	const auto show = controller->uiShow();
+	auto preview = base::make_unique_q<PreviewWrap>(parent, item);
+	preview->closeRequests(
+	) | rpl::start_with_next([=] {
+		show->hideLayer();
+	}, preview->lifetime());
+	auto layer = std::make_unique<Editor::LayerWidget>(
+		parent,
+		std::move(preview));
+	layer->lifetime().add([] { ::Media::Player::instance()->stop(); });
+	base::install_event_filter(layer.get(), [=](not_null<QEvent*> e) {
+		if (e->type() == QEvent::KeyPress) {
+			const auto k = static_cast<QKeyEvent*>(e.get());
+			if (k->key() == Qt::Key_Escape) {
+				show->hideLayer();
+			}
+			return base::EventFilterResult::Cancel;
+		}
+		return base::EventFilterResult::Continue;
+	});
+	controller->showLayer(std::move(layer), Ui::LayerOption::KeepOther);
+}
+
+} // namespace ChatHelpers
diff --git a/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.h b/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.h
new file mode 100644
index 000000000..f31d04982
--- /dev/null
+++ b/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.h
@@ -0,0 +1,22 @@
+/*
+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
+
+class HistoryItem;
+
+namespace Window {
+class SessionController;
+} // namespace Window
+
+namespace ChatHelpers {
+
+void ShowTTLMediaLayerWidget(
+	not_null<Window::SessionController*> controller,
+	not_null<HistoryItem*> item);
+
+} // namespace ChatHelpers
diff --git a/Telegram/SourceFiles/data/data_document_resolver.cpp b/Telegram/SourceFiles/data/data_document_resolver.cpp
index 2c34719ff..8ab502564 100644
--- a/Telegram/SourceFiles/data/data_document_resolver.cpp
+++ b/Telegram/SourceFiles/data/data_document_resolver.cpp
@@ -9,7 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 #include "base/options.h"
 #include "base/platform/base_platform_info.h"
-#include "ui/boxes/confirm_box.h"
+#include "boxes/abstract_box.h" // Ui::show().
+#include "chat_helpers/ttl_media_layer_widget.h"
 #include "core/application.h"
 #include "core/core_settings.h"
 #include "core/mime_type.h"
@@ -17,17 +18,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_document_media.h"
 #include "data/data_file_click_handler.h"
 #include "data/data_session.h"
-#include "history/view/media/history_view_gif.h"
 #include "history/history.h"
 #include "history/history_item.h"
-#include "media/player/media_player_instance.h"
+#include "history/view/media/history_view_gif.h"
 #include "lang/lang_keys.h"
+#include "media/player/media_player_instance.h"
 #include "platform/platform_file_utilities.h"
+#include "ui/boxes/confirm_box.h"
 #include "ui/chat/chat_theme.h"
 #include "ui/text/text_utilities.h"
 #include "ui/widgets/checkbox.h"
 #include "window/window_session_controller.h"
-#include "boxes/abstract_box.h" // Ui::show().
 #include "styles/style_layers.h"
 
 #include <QtCore/QBuffer>
@@ -298,6 +299,12 @@ void ResolveDocument(
 			|| document->isVoiceMessage()
 			|| document->isVideoMessage()) {
 			::Media::Player::instance()->playPause({ document, msgId });
+			if (controller
+				&& item
+				&& item->media()
+				&& item->media()->ttlSeconds()) {
+				ChatHelpers::ShowTTLMediaLayerWidget(controller, item);
+			}
 		} else {
 			showDocument();
 		}
diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h
index 3b524915f..134a7bf70 100644
--- a/Telegram/SourceFiles/history/view/history_view_element.h
+++ b/Telegram/SourceFiles/history/view/history_view_element.h
@@ -58,6 +58,7 @@ enum class Context : char {
 	AdminLog,
 	ContactPreview,
 	SavedSublist,
+	TTLViewer,
 };
 
 enum class OnlyEmojiAndSpaces : char {
diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp
index b53fb1454..d7c9e2f16 100644
--- a/Telegram/SourceFiles/history/view/history_view_message.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_message.cpp
@@ -2005,6 +2005,7 @@ bool Message::hasFromPhoto() const {
 		return !item->out() && !item->history()->peer->isUser();
 	} break;
 	case Context::ContactPreview:
+	case Context::TTLViewer:
 		return false;
 	}
 	Unexpected("Context in Message::hasFromPhoto.");
@@ -3200,9 +3201,10 @@ bool Message::hasFromName() const {
 		return false;
 	} break;
 	case Context::ContactPreview:
+	case Context::TTLViewer:
 		return false;
 	}
-	Unexpected("Context in Message::hasFromPhoto.");
+	Unexpected("Context in Message::hasFromName.");
 }
 
 bool Message::displayFromName() const {
diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_document.cpp
index 7ca7bc747..2dc91de74 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_document.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_document.cpp
@@ -134,7 +134,11 @@ void DrawCornerBadgeTTL(
 			});
 			animate(animate);
 		});
+	const auto weak = std::weak_ptr(lifetime);
 	return [=](QPainter &p, QRect r, QColor c) {
+		if (weak.expired()) {
+			return;
+		}
 		(state->idle ? state->idle : state->start)->paintInCenter(p, r, c);
 	};
 }
@@ -326,41 +330,36 @@ Document::Document(
 	}
 
 	if ((_data->isVoiceMessage() || isRound)
-		&& IsVoiceOncePlayable(_parent->data())) {
-		_parent->data()->removeFromSharedMediaIndex();
-		setDocumentLinks(_data, realParent, [=] {
-			_openl = nullptr;
-
+		&& _parent->data()->media()->ttlSeconds()) {
+		const auto fullId = _realParent->fullId();
+		if (_parent->delegate()->elementContext() == Context::TTLViewer) {
 			auto lifetime = std::make_shared<rpl::lifetime>();
-			rpl::merge(
-				::Media::Player::instance()->updatedNotifier(
-				) | rpl::filter([=](::Media::Player::TrackState state) {
-					using State = ::Media::Player::State;
-					const auto badState = state.state == State::Stopped
-						|| state.state == State::StoppedAtEnd
-						|| state.state == State::StoppedAtError
-						|| state.state == State::StoppedAtStart;
-					return (state.id.contextId() != _realParent->fullId())
-						&& !badState;
-				}) | rpl::to_empty,
-				::Media::Player::instance()->tracksFinished(
-				) | rpl::filter([=](AudioMsgId::Type type) {
-					return (type == AudioMsgId::Type::Voice);
-				}) | rpl::to_empty,
-				::Media::Player::instance()->stops(AudioMsgId::Type::Voice)
-			) | rpl::start_with_next([=]() mutable {
-				_drawTtl = nullptr;
-				const auto item = _parent->data();
+			TTLVoiceStops(fullId) | rpl::start_with_next([=]() mutable {
 				if (lifetime) {
 					base::take(lifetime)->destroy();
 				}
-				// Destroys this.
-				ClearMediaAsExpired(item);
 			}, *lifetime);
 			_drawTtl = CreateTtlPaintCallback(lifetime, [=] { repaint(); });
+		} else if (!_parent->data()->out()) {
+			_parent->data()->removeFromSharedMediaIndex();
+			setDocumentLinks(_data, realParent, [=] {
+				_openl = nullptr;
 
-			return false;
-		});
+				auto lifetime = std::make_shared<rpl::lifetime>();
+				TTLVoiceStops(fullId) | rpl::start_with_next([=]() mutable {
+					const auto item = _parent->data();
+					if (lifetime) {
+						base::take(lifetime)->destroy();
+					}
+					// Destroys this.
+					ClearMediaAsExpired(item);
+				}, *lifetime);
+
+				return false;
+			});
+		} else {
+			setDocumentLinks(_data, realParent);
+		}
 	} else {
 		setDocumentLinks(_data, realParent);
 	}
@@ -918,7 +917,8 @@ void Document::draw(
 			.highlight = highlightRequest ? &*highlightRequest : nullptr,
 		});
 	}
-	if (_parent->data()->media() && _parent->data()->media()->ttlSeconds()) {
+	if ((_parent->data()->media() && _parent->data()->media()->ttlSeconds())
+		&& _openl) {
 		const auto &fg = context.outbg
 			? st::historyFileOutIconFg
 			: st::historyFileInIconFg;
@@ -1739,4 +1739,23 @@ bool DrawThumbnailAsSongCover(
 	return true;
 }
 
+rpl::producer<> TTLVoiceStops(FullMsgId fullId) {
+	return rpl::merge(
+		::Media::Player::instance()->updatedNotifier(
+		) | rpl::filter([=](::Media::Player::TrackState state) {
+			using State = ::Media::Player::State;
+			const auto badState = state.state == State::Stopped
+				|| state.state == State::StoppedAtEnd
+				|| state.state == State::StoppedAtError
+				|| state.state == State::StoppedAtStart;
+			return (state.id.contextId() != fullId) && !badState;
+		}) | rpl::to_empty,
+		::Media::Player::instance()->tracksFinished(
+		) | rpl::filter([=](AudioMsgId::Type type) {
+			return (type == AudioMsgId::Type::Voice);
+		}) | rpl::to_empty,
+		::Media::Player::instance()->stops(AudioMsgId::Type::Voice)
+	);
+}
+
 } // namespace HistoryView
diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.h b/Telegram/SourceFiles/history/view/media/history_view_document.h
index 5fa8dc1da..3d29651c4 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_document.h
+++ b/Telegram/SourceFiles/history/view/media/history_view_document.h
@@ -181,4 +181,6 @@ bool DrawThumbnailAsSongCover(
 	const QRect &rect,
 	bool selected = false);
 
+rpl::producer<> TTLVoiceStops(FullMsgId fullId);
+
 } // namespace HistoryView

From 2a81a617e143ee8cb8c414f801cfe3f50fe2fdae Mon Sep 17 00:00:00 2001
From: 23rd <23rd@vivaldi.net>
Date: Tue, 9 Jan 2024 16:01:58 +0300
Subject: [PATCH 35/73] Improved loop animation in voice messages with ttl.

---
 .../view/media/history_view_document.cpp      | 52 ++++++++++---------
 1 file changed, 27 insertions(+), 25 deletions(-)

diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_document.cpp
index 2dc91de74..819c25923 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_document.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_document.cpp
@@ -99,6 +99,7 @@ void DrawCornerBadgeTTL(
 	struct State final {
 		std::unique_ptr<Lottie::Icon> start;
 		std::unique_ptr<Lottie::Icon> idle;
+		bool started = false;
 	};
 	const auto iconSize = Size(std::min(
 		st::historyFileInPause.width(),
@@ -110,36 +111,37 @@ void DrawCornerBadgeTTL(
 		.sizeOverride = iconSize,
 	});
 
-	const auto animateSingle = [=](
-			not_null<Lottie::Icon*> icon,
-			Fn<void()> next) {
-		auto callback = [=] {
-			update();
-			if (icon->frameIndex() == icon->framesCount()) {
-				next();
-			}
-		};
-		icon->animate(std::move(callback), 0, icon->framesCount());
-	};
-	const auto animate = [=](auto reanimate) -> void {
-		animateSingle(state->idle.get(), [=] { reanimate(reanimate); });
-	};
-	animateSingle(
-		state->start.get(),
-		[=] {
-			state->idle = Lottie::MakeIcon({
-				.name = u"voice_ttl_idle"_q,
-				.color = &st::historyFileInIconFg,
-				.sizeOverride = iconSize,
-			});
-			animate(animate);
-		});
 	const auto weak = std::weak_ptr(lifetime);
 	return [=](QPainter &p, QRect r, QColor c) {
 		if (weak.expired()) {
 			return;
 		}
-		(state->idle ? state->idle : state->start)->paintInCenter(p, r, c);
+		{
+			const auto &icon = state->idle;
+			if (icon) {
+				icon->paintInCenter(p, r, c);
+				if (!icon->animating()) {
+					icon->animate(update, 0, icon->framesCount());
+				}
+				return;
+			}
+		}
+		{
+			const auto &icon = state->start;
+			icon->paintInCenter(p, r, c);
+			if (!icon->animating()) {
+				if (!state->started) {
+					icon->animate(update, 0, icon->framesCount());
+					state->started = true;
+				} else {
+					state->idle = Lottie::MakeIcon({
+						.name = u"voice_ttl_idle"_q,
+						.color = &st::historyFileInIconFg,
+						.sizeOverride = iconSize,
+					});
+				}
+			}
+		}
 	};
 }
 

From 53e95a7f7491c597e5df6ea311e8dffcee3d01bb Mon Sep 17 00:00:00 2001
From: 23rd <23rd@vivaldi.net>
Date: Tue, 9 Jan 2024 17:06:37 +0300
Subject: [PATCH 36/73] Fixed display of last frame of voice messages in media
 viewer with ttl.

---
 .../chat_helpers/ttl_media_layer_widget.cpp   | 41 ++++++++++++++-----
 1 file changed, 31 insertions(+), 10 deletions(-)

diff --git a/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp b/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp
index 91403a2c2..baf5c1be6 100644
--- a/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp
+++ b/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp
@@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/chat/chat_theme.h"
 #include "ui/effects/path_shift_gradient.h"
 #include "ui/painter.h"
+#include "ui/rect.h"
 #include "ui/text/text_utilities.h"
 #include "ui/widgets/buttons.h"
 #include "ui/widgets/labels.h"
@@ -96,6 +97,11 @@ private:
 	std::unique_ptr<HistoryView::Element> _element;
 	rpl::lifetime _elementLifetime;
 
+	struct {
+		QImage frame;
+		bool use = false;
+	} _last;
+
 	rpl::event_stream<> _closeRequests;
 
 };
@@ -200,7 +206,10 @@ PreviewWrap::PreviewWrap(
 
 	HistoryView::TTLVoiceStops(
 		item->fullId()
-	) | rpl::start_with_next(closeCallback, lifetime());
+	) | rpl::start_with_next([=] {
+		_last.use = true;
+		closeCallback();
+	}, lifetime());
 }
 
 QRect PreviewWrap::elementRect() const {
@@ -225,20 +234,32 @@ void PreviewWrap::paintEvent(QPaintEvent *e) {
 		return;
 	}
 
-	auto p = Painter(this);
+	auto p = QPainter(this);
 	const auto r = rect();
 
-	auto context = _theme->preparePaintContext(
-		_style.get(),
-		r,
-		e->rect(),
-		!window()->isActiveWindow());
-	context.outbg = _element->hasOutLayout();
-
+	if (!_last.use) {
+		const auto size = _element->currentSize();
+		auto result = QImage(
+			size * style::DevicePixelRatio(),
+			QImage::Format_ARGB32_Premultiplied);
+		result.fill(Qt::transparent);
+		result.setDevicePixelRatio(style::DevicePixelRatio());
+		{
+			auto q = Painter(&result);
+			auto context = _theme->preparePaintContext(
+				_style.get(),
+				Rect(size),
+				Rect(size),
+				!window()->isActiveWindow());
+			context.outbg = _element->hasOutLayout();
+			_element->draw(q, context);
+		}
+		_last.frame = std::move(result);
+	}
 	p.translate(
 		(r.width() - _element->width()) / 2,
 		(r.height() - _element->height()) / 2);
-	_element->draw(p, context);
+	p.drawImage(0, 0, _last.frame);
 }
 
 } // namespace

From c43dfecec60277e7f05dca62c971c91ad7a881a7 Mon Sep 17 00:00:00 2001
From: 23rd <23rd@vivaldi.net>
Date: Tue, 9 Jan 2024 17:25:08 +0300
Subject: [PATCH 37/73] Excluded media with ttl from shared media.

---
 Telegram/SourceFiles/data/data_media_types.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp
index e9c4dd66e..fb6560e51 100644
--- a/Telegram/SourceFiles/data/data_media_types.cpp
+++ b/Telegram/SourceFiles/data/data_media_types.cpp
@@ -902,7 +902,7 @@ bool MediaFile::uploading() const {
 
 Storage::SharedMediaTypesMask MediaFile::sharedMediaTypes() const {
 	using Type = Storage::SharedMediaType;
-	if (_document->sticker()) {
+	if (_document->sticker() || ttlSeconds()) {
 		return {};
 	} else if (_document->isVideoMessage()) {
 		return Storage::SharedMediaTypesMask{}

From 6516c7aef3d4ba6761c8894d59ff55b1e7ff4d59 Mon Sep 17 00:00:00 2001
From: 23rd <23rd@vivaldi.net>
Date: Tue, 9 Jan 2024 19:52:43 +0300
Subject: [PATCH 38/73] Fixed display of ttl badges from voice messages with
 chat themes.

---
 .../view/media/history_view_document.cpp      | 49 ++++++++++---------
 1 file changed, 25 insertions(+), 24 deletions(-)

diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_document.cpp
index 819c25923..1ea9d6fb9 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_document.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_document.cpp
@@ -45,28 +45,25 @@ namespace {
 
 constexpr auto kAudioVoiceMsgUpdateView = crl::time(100);
 
-void DrawCornerBadgeTTL(
-		QPainter &p,
-		const style::color &bg,
-		const style::color &fg,
-		const QRect &circleRect) {
-	p.save();
-	const auto partRect = QRectF(
-		rect::right(circleRect)
+[[nodiscard]] QRect TTLRectFromInner(const QRect &inner) {
+	return QRect(
+		rect::right(inner)
 			- st::dialogsTTLBadgeSize
-			+ rect::m::sum::h(st::dialogsTTLBadgeInnerMargins),
-		rect::bottom(circleRect)
+			+ rect::m::sum::h(st::dialogsTTLBadgeInnerMargins)
+			- st::dialogsTTLBadgeSkip.x(),
+		rect::bottom(inner)
 			- st::dialogsTTLBadgeSize
-			+ rect::m::sum::v(st::dialogsTTLBadgeInnerMargins),
+			+ rect::m::sum::v(st::dialogsTTLBadgeInnerMargins)
+			- st::dialogsTTLBadgeSkip.y(),
 		st::dialogsTTLBadgeSize,
 		st::dialogsTTLBadgeSize);
+}
 
+void DrawCornerBadgeTTL(QPainter &p, const QColor &fg, const QRect &ttlRect) {
+	p.save();
 	auto hq = PainterHighQualityEnabler(p);
-	p.setPen(Qt::NoPen);
-	p.setBrush(bg);
-	p.drawEllipse(partRect);
 
-	const auto innerRect = partRect - st::dialogsTTLBadgeInnerMargins;
+	const auto innerRect = QRectF(ttlRect - st::dialogsTTLBadgeInnerMargins);
 	const auto ttlText = u"1"_q;
 
 	p.setFont(st::dialogsScamFont);
@@ -716,6 +713,11 @@ void Document::draw(
 	} else {
 		p.setPen(Qt::NoPen);
 
+		const auto hasTtlBadge = _parent->data()->media()
+			&& _parent->data()->media()->ttlSeconds()
+			&& _openl;
+		const auto ttlRect = hasTtlBadge ? TTLRectFromInner(inner) : QRect();
+
 		const auto coverDrawn = _data->isSongWithCover()
 			&& DrawThumbnailAsSongCover(
 				p,
@@ -740,9 +742,12 @@ void Document::draw(
 					}
 				}
 			} else {
-				PainterHighQualityEnabler hq(p);
+				auto hq = PainterHighQualityEnabler(p);
 				p.setBrush(stm->msgFileBg);
 				p.drawEllipse(inner);
+				if (hasTtlBadge) {
+					p.drawEllipse(ttlRect);
+				}
 			}
 		}
 
@@ -791,6 +796,9 @@ void Document::draw(
 				QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine)));
 				_animation->radial.draw(q, rinner, st::msgFileRadialLine, stm->historyFileRadialFg);
 			}
+			if (hasTtlBadge) {
+				DrawCornerBadgeTTL(q, stm->historyFileRadialFg->c, ttlRect);
+			}
 		};
 		if (_data->isSongWithCover() || !usesBubblePattern(context)) {
 			paintContent(p);
@@ -799,7 +807,7 @@ void Document::draw(
 				p,
 				context.viewport,
 				context.bubblesPattern->pixmap,
-				inner,
+				hasTtlBadge ? inner.united(ttlRect) : inner,
 				paintContent,
 				_iconCache);
 		}
@@ -919,13 +927,6 @@ void Document::draw(
 			.highlight = highlightRequest ? &*highlightRequest : nullptr,
 		});
 	}
-	if ((_parent->data()->media() && _parent->data()->media()->ttlSeconds())
-		&& _openl) {
-		const auto &fg = context.outbg
-			? st::historyFileOutIconFg
-			: st::historyFileInIconFg;
-		DrawCornerBadgeTTL(p, stm->msgFileBg, fg, inner);
-	}
 }
 
 Ui::BubbleRounding Document::thumbRounding(

From a1369aaad0294a791a0c382c251c1b09e1220f47 Mon Sep 17 00:00:00 2001
From: 23rd <23rd@vivaldi.net>
Date: Tue, 9 Jan 2024 20:32:18 +0300
Subject: [PATCH 39/73] Added references to structured bindings since Apple
 clang supports them.

---
 Telegram/SourceFiles/boxes/auto_download_box.cpp       |  6 +++---
 Telegram/SourceFiles/boxes/connection_box.cpp          |  2 +-
 Telegram/SourceFiles/boxes/language_box.cpp            |  2 +-
 Telegram/SourceFiles/boxes/share_box.cpp               |  2 +-
 Telegram/SourceFiles/calls/group/calls_group_call.cpp  |  2 +-
 .../SourceFiles/chat_helpers/emoji_sets_manager.cpp    |  2 +-
 Telegram/SourceFiles/core/core_cloud_password.cpp      |  2 +-
 Telegram/SourceFiles/core/shortcuts.cpp                |  4 ++--
 Telegram/SourceFiles/data/data_changes.cpp             |  2 +-
 Telegram/SourceFiles/data/data_group_call.cpp          |  4 ++--
 Telegram/SourceFiles/data/data_histories.cpp           |  6 +++---
 Telegram/SourceFiles/data/data_session.cpp             |  8 ++++----
 Telegram/SourceFiles/data/data_stories.cpp             |  2 +-
 Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp  |  6 +++---
 Telegram/SourceFiles/editor/editor_crop.cpp            |  4 ++--
 Telegram/SourceFiles/export/export_api_wrap.cpp        |  2 +-
 .../SourceFiles/export/output/export_output_html.cpp   |  6 +++---
 Telegram/SourceFiles/history/history.cpp               |  2 +-
 Telegram/SourceFiles/history/history_inner_widget.cpp  |  4 ++--
 .../history/view/history_view_list_widget.cpp          |  6 +++---
 .../history/view/media/history_view_poll.cpp           |  2 +-
 Telegram/SourceFiles/info/media/info_media_common.cpp  |  2 +-
 .../media/view/media_view_overlay_widget.cpp           |  6 +++---
 Telegram/SourceFiles/mtproto/dedicated_file_loader.cpp |  2 +-
 .../SourceFiles/passport/passport_form_controller.cpp  | 10 +++++-----
 .../passport/passport_panel_edit_document.cpp          |  2 +-
 .../SourceFiles/storage/download_manager_mtproto.cpp   |  4 ++--
 Telegram/SourceFiles/storage/file_upload.cpp           |  4 ++--
 Telegram/SourceFiles/ui/boxes/single_choice_box.cpp    |  2 +-
 .../ui/chat/attach/attach_album_thumbnail.cpp          |  2 +-
 Telegram/SourceFiles/ui/chat/message_bar.cpp           |  2 +-
 Telegram/SourceFiles/window/notifications_manager.cpp  |  2 +-
 .../SourceFiles/window/window_session_controller.cpp   |  2 +-
 33 files changed, 58 insertions(+), 58 deletions(-)

diff --git a/Telegram/SourceFiles/boxes/auto_download_box.cpp b/Telegram/SourceFiles/boxes/auto_download_box.cpp
index ae42f3cc8..3617a9eb6 100644
--- a/Telegram/SourceFiles/boxes/auto_download_box.cpp
+++ b/Telegram/SourceFiles/boxes/auto_download_box.cpp
@@ -162,7 +162,7 @@ void AutoDownloadBox::setupContent() {
 			*downloadValues,
 			*autoPlayValues);
 		auto allowMore = values | ranges::views::filter([&](Pair pair) {
-			const auto [type, enabled] = pair;
+			const auto &[type, enabled] = pair;
 			const auto value = enabled ? limitByType(type) : 0;
 			const auto old = settings->bytesLimit(_source, type);
 			return (old < value);
@@ -170,7 +170,7 @@ void AutoDownloadBox::setupContent() {
 			return pair.first;
 		});
 		const auto less = ranges::any_of(*autoPlayValues, [&](Pair pair) {
-			const auto [type, enabled] = pair;
+			const auto &[type, enabled] = pair;
 			const auto value = enabled ? limitByType(type) : 0;
 			return value < settings->bytesLimit(_source, type);
 		});
@@ -179,7 +179,7 @@ void AutoDownloadBox::setupContent() {
 			allowMore.end());
 
 		const auto changed = ranges::any_of(values, [&](Pair pair) {
-			const auto [type, enabled] = pair;
+			const auto &[type, enabled] = pair;
 			const auto value = enabled ? limitByType(type) : 0;
 			return value != settings->bytesLimit(_source, type);
 		});
diff --git a/Telegram/SourceFiles/boxes/connection_box.cpp b/Telegram/SourceFiles/boxes/connection_box.cpp
index 3bdaa932d..4403f56fa 100644
--- a/Telegram/SourceFiles/boxes/connection_box.cpp
+++ b/Telegram/SourceFiles/boxes/connection_box.cpp
@@ -748,7 +748,7 @@ void ProxiesBox::applyView(View &&view) {
 		const auto wrap = _wrap
 			? _wrap.data()
 			: _initialWrap.data();
-		const auto [i, ok] = _rows.emplace(id, nullptr);
+		const auto &[i, ok] = _rows.emplace(id, nullptr);
 		i->second.reset(wrap->insert(
 			0,
 			object_ptr<ProxyRow>(
diff --git a/Telegram/SourceFiles/boxes/language_box.cpp b/Telegram/SourceFiles/boxes/language_box.cpp
index ab709a87d..aa69a6fee 100644
--- a/Telegram/SourceFiles/boxes/language_box.cpp
+++ b/Telegram/SourceFiles/boxes/language_box.cpp
@@ -1121,7 +1121,7 @@ void LanguageBox::prepare() {
 
 	using namespace rpl::mappers;
 
-	const auto [recent, official] = PrepareLists();
+	const auto &[recent, official] = PrepareLists();
 	const auto inner = setInnerWidget(
 		object_ptr<Content>(this, recent, official),
 		st::boxScroll,
diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp
index d1b3dbb37..007355317 100644
--- a/Telegram/SourceFiles/boxes/share_box.cpp
+++ b/Telegram/SourceFiles/boxes/share_box.cpp
@@ -882,7 +882,7 @@ auto ShareBox::Inner::getChat(not_null<Dialogs::Row*> row)
 		row->attached = i->second.get();
 		return i->second.get();
 	}
-	const auto [i, ok] = _dataMap.emplace(
+	const auto &[i, ok] = _dataMap.emplace(
 		peer,
 		std::make_unique<Chat>(peer, _st.item, [=] { repaintChat(peer); }));
 	updateChatName(i->second.get());
diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp
index 49dd3db91..5ec38443d 100644
--- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp
+++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp
@@ -3013,7 +3013,7 @@ void GroupCall::checkLastSpoke() {
 	const auto now = crl::now();
 	auto list = base::take(_lastSpoke);
 	for (auto i = list.begin(); i != list.end();) {
-		const auto [ssrc, when] = *i;
+		const auto &[ssrc, when] = *i;
 		if (when.anything + kKeepInListFor >= now) {
 			hasRecent = true;
 			++i;
diff --git a/Telegram/SourceFiles/chat_helpers/emoji_sets_manager.cpp b/Telegram/SourceFiles/chat_helpers/emoji_sets_manager.cpp
index 7c6ae96c8..fed24fc5b 100644
--- a/Telegram/SourceFiles/chat_helpers/emoji_sets_manager.cpp
+++ b/Telegram/SourceFiles/chat_helpers/emoji_sets_manager.cpp
@@ -247,7 +247,7 @@ void Row::paintPreview(QPainter &p) const {
 	const auto width = st::manageEmojiPreviewWidth;
 	const auto height = st::manageEmojiPreviewWidth;
 	auto &&preview = ranges::views::zip(_preview, ranges::views::ints(0, int(_preview.size())));
-	for (const auto [pixmap, index] : preview) {
+	for (const auto &[pixmap, index] : preview) {
 		const auto row = (index / 2);
 		const auto column = (index % 2);
 		const auto left = x + (column ? width - st::manageEmojiPreview : 0);
diff --git a/Telegram/SourceFiles/core/core_cloud_password.cpp b/Telegram/SourceFiles/core/core_cloud_password.cpp
index 99b5d589e..3a65f24d5 100644
--- a/Telegram/SourceFiles/core/core_cloud_password.cpp
+++ b/Telegram/SourceFiles/core/core_cloud_password.cpp
@@ -128,7 +128,7 @@ CloudPasswordResult ComputeCheck(
 		}
 	};
 
-	const auto [a, AForHash, u] = GenerateAndCheckRandom();
+	const auto &[a, AForHash, u] = GenerateAndCheckRandom();
 	const auto g_b = BigNum::ModSub(B, kg_x, p, context);
 	if (!MTP::IsGoodModExpFirst(g_b, p)) {
 		LOG(("API Error: Bad g_b in cloud password check!"));
diff --git a/Telegram/SourceFiles/core/shortcuts.cpp b/Telegram/SourceFiles/core/shortcuts.cpp
index 2d10ad484..ad4a79eb7 100644
--- a/Telegram/SourceFiles/core/shortcuts.cpp
+++ b/Telegram/SourceFiles/core/shortcuts.cpp
@@ -402,7 +402,7 @@ void Manager::fillDefaults() {
 		kShowFolder,
 		ranges::views::ints(1, ranges::unreachable));
 
-	for (const auto [command, index] : folders) {
+	for (const auto &[command, index] : folders) {
 		set(u"%1+%2"_q.arg(ctrl).arg(index), command);
 	}
 
@@ -410,7 +410,7 @@ void Manager::fillDefaults() {
 		kShowAccount,
 		ranges::views::ints(1, ranges::unreachable));
 
-	for (const auto [command, index] : accounts) {
+	for (const auto &[command, index] : accounts) {
 		set(u"%1+shift+%2"_q.arg(ctrl).arg(index), command);
 	}
 
diff --git a/Telegram/SourceFiles/data/data_changes.cpp b/Telegram/SourceFiles/data/data_changes.cpp
index f20041565..773c50d5d 100644
--- a/Telegram/SourceFiles/data/data_changes.cpp
+++ b/Telegram/SourceFiles/data/data_changes.cpp
@@ -56,7 +56,7 @@ rpl::producer<UpdateType> Changes::Manager<DataType, UpdateType>::updates(
 		Flags flags) const {
 	return _stream.events(
 	) | rpl::filter([=](const UpdateType &update) {
-		const auto [updateData, updateFlags] = update;
+		const auto &[updateData, updateFlags] = update;
 		return (updateData == data) && (updateFlags & flags);
 	});
 }
diff --git a/Telegram/SourceFiles/data/data_group_call.cpp b/Telegram/SourceFiles/data/data_group_call.cpp
index 0567477c4..72b4d387f 100644
--- a/Telegram/SourceFiles/data/data_group_call.cpp
+++ b/Telegram/SourceFiles/data/data_group_call.cpp
@@ -847,7 +847,7 @@ void GroupCall::requestUnknownParticipants() {
 		auto result = base::flat_map<uint32, LastSpokeTimes>();
 		result.reserve(kRequestPerPage);
 		while (result.size() < kRequestPerPage) {
-			const auto [ssrc, when] = _unknownSpokenSsrcs.back();
+			const auto &[ssrc, when] = _unknownSpokenSsrcs.back();
 			result.emplace(ssrc, when);
 			_unknownSpokenSsrcs.erase(_unknownSpokenSsrcs.end() - 1);
 		}
@@ -863,7 +863,7 @@ void GroupCall::requestUnknownParticipants() {
 			result.reserve(available);
 			while (result.size() < available) {
 				const auto &back = _unknownSpokenPeerIds.back();
-				const auto [participantPeerId, when] = back;
+				const auto &[participantPeerId, when] = back;
 				result.emplace(participantPeerId, when);
 				_unknownSpokenPeerIds.erase(_unknownSpokenPeerIds.end() - 1);
 			}
diff --git a/Telegram/SourceFiles/data/data_histories.cpp b/Telegram/SourceFiles/data/data_histories.cpp
index dd5b435ee..b171cf0f4 100644
--- a/Telegram/SourceFiles/data/data_histories.cpp
+++ b/Telegram/SourceFiles/data/data_histories.cpp
@@ -123,7 +123,7 @@ not_null<History*> Histories::findOrCreate(PeerId peerId) {
 	if (const auto result = find(peerId)) {
 		return result;
 	}
-	const auto [i, ok] = _map.emplace(
+	const auto &[i, ok] = _map.emplace(
 		peerId,
 		std::make_unique<History>(&owner(), peerId));
 	return i->second.get();
@@ -355,7 +355,7 @@ void Histories::requestDialogEntry(
 		return;
 	}
 
-	const auto [j, ok] = _dialogRequestsPending.try_emplace(history);
+	const auto &[j, ok] = _dialogRequestsPending.try_emplace(history);
 	if (callback) {
 		j->second.push_back(std::move(callback));
 	}
@@ -1130,7 +1130,7 @@ void Histories::finishSentRequest(
 	if (state->postponedRequestEntry && !postponeEntryRequest(*state)) {
 		const auto i = _dialogRequests.find(history);
 		Assert(i != end(_dialogRequests));
-		const auto [j, ok] = _dialogRequestsPending.emplace(
+		const auto &[j, ok] = _dialogRequestsPending.emplace(
 			history,
 			std::move(i->second));
 		Assert(ok);
diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp
index 6001a46c2..de0a3c62d 100644
--- a/Telegram/SourceFiles/data/data_session.cpp
+++ b/Telegram/SourceFiles/data/data_session.cpp
@@ -1097,7 +1097,7 @@ void Session::watchForOffline(not_null<UserData*> user, TimeId now) {
 		return;
 	}
 	const auto till = user->onlineTill;
-	const auto [i, ok] = _watchingForOffline.emplace(user, till);
+	const auto &[i, ok] = _watchingForOffline.emplace(user, till);
 	if (!ok) {
 		if (i->second == till) {
 			return;
@@ -1626,7 +1626,7 @@ HistoryItem *Session::changeMessageId(PeerId peerId, MsgId wasId, MsgId nowId) {
 	}
 	const auto item = i->second;
 	list->erase(i);
-	const auto [j, ok] = list->emplace(nowId, item);
+	const auto &[j, ok] = list->emplace(nowId, item);
 
 	if (!peerIsChannel(peerId)) {
 		if (IsServerMsgId(wasId)) {
@@ -1789,7 +1789,7 @@ void Session::registerHighlightProcess(
 		not_null<HistoryItem*> item) {
 	Expects(item->inHighlightProcess());
 
-	const auto [i, ok] = _highlightings.emplace(processId, item);
+	const auto &[i, ok] = _highlightings.emplace(processId, item);
 
 	Ensures(ok);
 }
@@ -4208,7 +4208,7 @@ not_null<Folder*> Session::folder(FolderId id) {
 	if (const auto result = folderLoaded(id)) {
 		return result;
 	}
-	const auto [it, ok] = _folders.emplace(
+	const auto &[it, ok] = _folders.emplace(
 		id,
 		std::make_unique<Folder>(this, id));
 	return it->second.get();
diff --git a/Telegram/SourceFiles/data/data_stories.cpp b/Telegram/SourceFiles/data/data_stories.cpp
index abf9227fd..a35279be4 100644
--- a/Telegram/SourceFiles/data/data_stories.cpp
+++ b/Telegram/SourceFiles/data/data_stories.cpp
@@ -234,7 +234,7 @@ Story *Stories::applySingle(PeerId peerId, const MTPstoryItem &story) {
 void Stories::requestPeerStories(
 		not_null<PeerData*> peer,
 		Fn<void()> done) {
-	const auto [i, ok] = _requestingPeerStories.emplace(peer);
+	const auto &[i, ok] = _requestingPeerStories.emplace(peer);
 	if (done) {
 		i->second.push_back(std::move(done));
 	}
diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
index d7d17a37c..05e54ec6b 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
@@ -2458,7 +2458,7 @@ void InnerWidget::appendToFiltered(Key key) {
 	}
 	auto row = std::make_unique<Row>(key, 0, 0);
 	row->recountHeight(_narrowRatio);
-	const auto [i, ok] = _filterResultsGlobal.emplace(key, std::move(row));
+	const auto &[i, ok] = _filterResultsGlobal.emplace(key, std::move(row));
 	const auto height = filteredHeight();
 	_filterResults.emplace_back(i->second.get());
 	_filterResults.back().top = height;
@@ -3913,7 +3913,7 @@ void InnerWidget::setupShortcuts() {
 			auto &&folders = ranges::views::zip(
 				Shortcuts::kShowFolder,
 				ranges::views::ints(0, ranges::unreachable));
-			for (const auto [command, index] : folders) {
+			for (const auto &[command, index] : folders) {
 				const auto select = (command == Command::ShowFolderLast)
 					? (filtersCount - 1)
 					: std::clamp(index, 0, filtersCount - 1);
@@ -3940,7 +3940,7 @@ void InnerWidget::setupShortcuts() {
 		auto &&pinned = ranges::views::zip(
 			kPinned,
 			ranges::views::ints(0, ranges::unreachable));
-		for (const auto [command, index] : pinned) {
+		for (const auto &[command, index] : pinned) {
 			request->check(command) && request->handle([=, index = index] {
 				const auto list = (_filterId
 					? session().data().chatsFilters().chatsList(_filterId)
diff --git a/Telegram/SourceFiles/editor/editor_crop.cpp b/Telegram/SourceFiles/editor/editor_crop.cpp
index 78a91aa6a..54f8369c2 100644
--- a/Telegram/SourceFiles/editor/editor_crop.cpp
+++ b/Telegram/SourceFiles/editor/editor_crop.cpp
@@ -212,8 +212,8 @@ void Crop::computeDownState(const QPoint &p) {
 	const auto edge = mouseState(p);
 	const auto &inner = _innerRect;
 	const auto &crop = _cropPaint;
-	const auto [iLeft, iTop, iRight, iBottom] = RectEdges(inner);
-	const auto [cLeft, cTop, cRight, cBottom] = RectEdges(crop);
+	const auto &[iLeft, iTop, iRight, iBottom] = RectEdges(inner);
+	const auto &[cLeft, cTop, cRight, cBottom] = RectEdges(crop);
 	_down = InfoAtDown{
 		.rect = crop,
 		.edge = edge,
diff --git a/Telegram/SourceFiles/export/export_api_wrap.cpp b/Telegram/SourceFiles/export/export_api_wrap.cpp
index 25fe29a71..b23329c03 100644
--- a/Telegram/SourceFiles/export/export_api_wrap.cpp
+++ b/Telegram/SourceFiles/export/export_api_wrap.cpp
@@ -1553,7 +1553,7 @@ void ApiWrap::appendChatsSlice(
 				continue;
 			}
 		}
-		const auto [i, ok] = process.indexByPeer.emplace(
+		const auto &[i, ok] = process.indexByPeer.emplace(
 			info.peerId,
 			nextIndex);
 		if (ok) {
diff --git a/Telegram/SourceFiles/export/output/export_output_html.cpp b/Telegram/SourceFiles/export/output/export_output_html.cpp
index 3f0b03eaf..c543de8a5 100644
--- a/Telegram/SourceFiles/export/output/export_output_html.cpp
+++ b/Telegram/SourceFiles/export/output/export_output_html.cpp
@@ -1563,7 +1563,7 @@ QByteArray HtmlWriter::Wrap::pushStickerMedia(
 		const QString &basePath) {
 	using namespace Data;
 
-	const auto [thumb, size] = WriteImageThumb(
+	const auto &[thumb, size] = WriteImageThumb(
 		basePath,
 		data.file.relativePath,
 		CalculateThumbSize(
@@ -1730,7 +1730,7 @@ QByteArray HtmlWriter::Wrap::pushPhotoMedia(
 		const QString &basePath) {
 	using namespace Data;
 
-	const auto [thumb, size] = WriteImageThumb(
+	const auto &[thumb, size] = WriteImageThumb(
 		basePath,
 		data.image.file.relativePath,
 		CalculateThumbSize(
@@ -2790,7 +2790,7 @@ Result HtmlWriter::writeDialogSlice(const Data::MessagesSlice &data) {
 				_settings.path,
 				FormatDateText(date)));
 		}
-		const auto [info, content] = _chat->pushMessage(
+		const auto &[info, content] = _chat->pushMessage(
 			message,
 			previous,
 			_dialog,
diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp
index bdc0d659c..f5a9765c0 100644
--- a/Telegram/SourceFiles/history/history.cpp
+++ b/Telegram/SourceFiles/history/history.cpp
@@ -465,7 +465,7 @@ not_null<HistoryItem*> History::insertItem(
 		std::unique_ptr<HistoryItem> item) {
 	Expects(item != nullptr);
 
-	const auto [i, ok] = _messages.insert(std::move(item));
+	const auto &[i, ok] = _messages.insert(std::move(item));
 
 	const auto result = i->get();
 	owner().registerMessage(result);
diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp
index c824976cf..9cf3ab373 100644
--- a/Telegram/SourceFiles/history/history_inner_widget.cpp
+++ b/Telegram/SourceFiles/history/history_inner_widget.cpp
@@ -1992,7 +1992,7 @@ void HistoryInner::mouseActionFinish(
 		&& !_selected.empty()
 		&& _selected.cbegin()->second != FullSelection
 		&& !hasCopyRestriction(_selected.cbegin()->first)) {
-		const auto [item, selection] = *_selected.cbegin();
+		const auto &[item, selection] = *_selected.cbegin();
 		if (const auto view = viewByItem(item)) {
 			TextUtilities::SetClipboardText(
 				view->selectedText(selection),
@@ -2925,7 +2925,7 @@ TextForMimeData HistoryInner::getSelectedText() const {
 		return TextForMimeData();
 	}
 	if (selected.cbegin()->second != FullSelection) {
-		const auto [item, selection] = *selected.cbegin();
+		const auto &[item, selection] = *selected.cbegin();
 		if (const auto view = viewByItem(item)) {
 			return view->selectedText(selection);
 		}
diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
index 984cc0e79..6dcc3497f 100644
--- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
@@ -901,7 +901,7 @@ not_null<Element*> ListWidget::enforceViewForItem(
 			return j->second.get();
 		}
 	}
-	const auto [i, ok] = _views.emplace(
+	const auto &[i, ok] = _views.emplace(
 		item,
 		item->createView(this));
 	return i->second.get();
@@ -1094,7 +1094,7 @@ void ListWidget::repaintScrollDateCallback() {
 
 auto ListWidget::collectSelectedItems() const -> SelectedItems {
 	auto transformation = [&](const auto &item) {
-		const auto [itemId, selection] = item;
+		const auto &[itemId, selection] = item;
 		auto result = SelectedItem(itemId);
 		result.canDelete = selection.canDelete;
 		result.canForward = selection.canForward;
@@ -3766,7 +3766,7 @@ void ListWidget::refreshItem(not_null<const Element*> view) {
 			}
 			return nullptr;
 		}();
-		const auto [i, ok] = _views.emplace(
+		const auto &[i, ok] = _views.emplace(
 			item,
 			item->createView(this, was.get()));
 		const auto now = i->second.get();
diff --git a/Telegram/SourceFiles/history/view/media/history_view_poll.cpp b/Telegram/SourceFiles/history/view/media/history_view_poll.cpp
index c08d8fde5..7e96e068f 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_poll.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_poll.cpp
@@ -761,7 +761,7 @@ void Poll::draw(Painter &p, const PaintContext &context) const {
 	auto &&answers = ranges::views::zip(
 		_answers,
 		ranges::views::ints(0, int(_answers.size())));
-	for (const auto [answer, index] : answers) {
+	for (const auto &[answer, index] : answers) {
 		const auto animation = _answersAnimation
 			? &_answersAnimation->data[index]
 			: nullptr;
diff --git a/Telegram/SourceFiles/info/media/info_media_common.cpp b/Telegram/SourceFiles/info/media/info_media_common.cpp
index fde5dd01b..23fba5eec 100644
--- a/Telegram/SourceFiles/info/media/info_media_common.cpp
+++ b/Telegram/SourceFiles/info/media/info_media_common.cpp
@@ -39,7 +39,7 @@ bool ChangeItemSelection(
 		return false;
 	};
 	if (selected.size() < MaxSelectedItems) {
-		const auto [i, ok] = selected.try_emplace(item, selectionData);
+		const auto &[i, ok] = selected.try_emplace(item, selectionData);
 		if (ok) {
 			return true;
 		}
diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp
index c533fd805..3bbc98fb9 100644
--- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp
+++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp
@@ -1720,7 +1720,7 @@ bool OverlayWidget::stateAnimationCallback(crl::time now) {
 		now += st::mediaviewShowDuration + st::mediaviewHideDuration;
 	}
 	for (auto i = begin(_animations); i != end(_animations);) {
-		const auto [state, started] = *i;
+		const auto &[state, started] = *i;
 		updateOverRect(state);
 		const auto dt = float64(now - started) / st::mediaviewFadeDuration;
 		if (dt >= 1) {
@@ -5494,12 +5494,12 @@ void OverlayWidget::preloadData(int delta) {
 	for (auto index = from; index != till + 1; ++index) {
 		auto entity = entityByIndex(index);
 		if (auto photo = std::get_if<not_null<PhotoData*>>(&entity.data)) {
-			const auto [i, ok] = photos.emplace((*photo)->createMediaView());
+			const auto &[i, ok] = photos.emplace((*photo)->createMediaView());
 			(*i)->wanted(Data::PhotoSize::Small, fileOrigin(entity));
 			(*photo)->load(fileOrigin(entity), LoadFromCloudOrLocal, true);
 		} else if (auto document = std::get_if<not_null<DocumentData*>>(
 				&entity.data)) {
-			const auto [i, ok] = documents.emplace(
+			const auto &[i, ok] = documents.emplace(
 				(*document)->createMediaView());
 			(*i)->thumbnailWanted(fileOrigin(entity));
 			if (!(*i)->canBePlayed(entity.item)) {
diff --git a/Telegram/SourceFiles/mtproto/dedicated_file_loader.cpp b/Telegram/SourceFiles/mtproto/dedicated_file_loader.cpp
index 676aa047c..850d5c6c1 100644
--- a/Telegram/SourceFiles/mtproto/dedicated_file_loader.cpp
+++ b/Telegram/SourceFiles/mtproto/dedicated_file_loader.cpp
@@ -453,7 +453,7 @@ void StartDedicatedLoader(
 		ready(nullptr);
 	};
 
-	const auto [username, postId] = location;
+	const auto &[username, postId] = location;
 	ResolveChannel(mtp, username, [=, postId = postId](
 			const MTPInputChannel &channel) {
 		mtp->send(
diff --git a/Telegram/SourceFiles/passport/passport_form_controller.cpp b/Telegram/SourceFiles/passport/passport_form_controller.cpp
index cf9f4c775..684163163 100644
--- a/Telegram/SourceFiles/passport/passport_form_controller.cpp
+++ b/Telegram/SourceFiles/passport/passport_form_controller.cpp
@@ -1791,7 +1791,7 @@ void FormController::loadFile(File &file) {
 		return;
 	}
 	file.downloadStatus.set(LoadStatus::Status::InProgress, 0);
-	const auto [j, ok] = _fileLoaders.emplace(
+	const auto &[j, ok] = _fileLoaders.emplace(
 		key,
 		std::make_unique<mtpFileLoader>(
 			&_controller->session(),
@@ -1823,7 +1823,7 @@ void FormController::loadFile(File &file) {
 }
 
 void FormController::fileLoadDone(FileKey key, const QByteArray &bytes) {
-	if (const auto [value, file] = findFile(key); file != nullptr) {
+	if (const auto &[value, file] = findFile(key); file != nullptr) {
 		const auto decrypted = DecryptData(
 			bytes::make_span(bytes),
 			file->hash,
@@ -1843,7 +1843,7 @@ void FormController::fileLoadDone(FileKey key, const QByteArray &bytes) {
 }
 
 void FormController::fileLoadProgress(FileKey key, int offset) {
-	if (const auto [value, file] = findFile(key); file != nullptr) {
+	if (const auto &[value, file] = findFile(key); file != nullptr) {
 		file->downloadStatus.set(LoadStatus::Status::InProgress, offset);
 		if (const auto fileInEdit = findEditFile(key)) {
 			fileInEdit->fields.downloadStatus = file->downloadStatus;
@@ -1853,7 +1853,7 @@ void FormController::fileLoadProgress(FileKey key, int offset) {
 }
 
 void FormController::fileLoadFail(FileKey key) {
-	if (const auto [value, file] = findFile(key); file != nullptr) {
+	if (const auto &[value, file] = findFile(key); file != nullptr) {
 		file->downloadStatus.set(LoadStatus::Status::Failed);
 		if (const auto fileInEdit = findEditFile(key)) {
 			fileInEdit->fields.downloadStatus = file->downloadStatus;
@@ -2591,7 +2591,7 @@ bool FormController::parseForm(const MTPaccount_AuthorizationForm &result) {
 		const auto row = CollectRequestedRow(required);
 		for (const auto &requested : row.values) {
 			const auto type = requested.type;
-			const auto [i, ok] = _form.values.emplace(type, Value(type));
+			const auto &[i, ok] = _form.values.emplace(type, Value(type));
 			auto &value = i->second;
 			value.translationRequired = requested.translationRequired;
 			value.selfieRequired = requested.selfieRequired;
diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp b/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp
index 9f71e2e3a..c3715e977 100644
--- a/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp
+++ b/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp
@@ -530,7 +530,7 @@ void PanelEditDocument::createDetailsRow(
 	const auto isoByPhone = Countries::Instance().countryISO2ByPhone(
 		_controller->bot()->session().user()->phone());
 
-	const auto [it, ok] = _details.emplace(
+	const auto &[it, ok] = _details.emplace(
 		i,
 		container->add(Ui::PanelDetailsRow::Create(
 			container,
diff --git a/Telegram/SourceFiles/storage/download_manager_mtproto.cpp b/Telegram/SourceFiles/storage/download_manager_mtproto.cpp
index d6752e768..77aae801b 100644
--- a/Telegram/SourceFiles/storage/download_manager_mtproto.cpp
+++ b/Telegram/SourceFiles/storage/download_manager_mtproto.cpp
@@ -804,8 +804,8 @@ void DownloadMtprotoTask::placeSentRequest(
 		dcId(),
 		requestData.sessionIndex,
 		Storage::kDownloadPartSize);
-	const auto [i, ok1] = _sentRequests.emplace(requestId, requestData);
-	const auto [j, ok2] = _requestByOffset.emplace(
+	const auto &[i, ok1] = _sentRequests.emplace(requestId, requestData);
+	const auto &[j, ok2] = _requestByOffset.emplace(
 		requestData.offset,
 		requestId);
 
diff --git a/Telegram/SourceFiles/storage/file_upload.cpp b/Telegram/SourceFiles/storage/file_upload.cpp
index f2a014f7e..72ebdaafe 100644
--- a/Telegram/SourceFiles/storage/file_upload.cpp
+++ b/Telegram/SourceFiles/storage/file_upload.cpp
@@ -359,7 +359,7 @@ void Uploader::upload(
 void Uploader::currentFailed() {
 	auto j = queue.find(uploadingId);
 	if (j != queue.end()) {
-		const auto [msgId, file] = std::move(*j);
+		const auto &[msgId, file] = std::move(*j);
 		queue.erase(j);
 		notifyFailed(msgId, file);
 	}
@@ -640,7 +640,7 @@ void Uploader::cancelAll() {
 		currentFailed();
 	}
 	while (!queue.empty()) {
-		const auto [msgId, file] = std::move(*queue.begin());
+		const auto &[msgId, file] = std::move(*queue.begin());
 		queue.erase(queue.begin());
 		notifyFailed(msgId, file);
 	}
diff --git a/Telegram/SourceFiles/ui/boxes/single_choice_box.cpp b/Telegram/SourceFiles/ui/boxes/single_choice_box.cpp
index a75d8a030..2083a0f48 100644
--- a/Telegram/SourceFiles/ui/boxes/single_choice_box.cpp
+++ b/Telegram/SourceFiles/ui/boxes/single_choice_box.cpp
@@ -29,7 +29,7 @@ void SingleChoiceBox(
 		layout,
 		st::boxOptionListPadding.top() + st::autolockButton.margin.top()));
 	auto &&ints = ranges::views::ints(0, ranges::unreachable);
-	for (const auto [i, text] : ranges::views::zip(ints, args.options)) {
+	for (const auto &[i, text] : ranges::views::zip(ints, args.options)) {
 		layout->add(
 			object_ptr<Ui::Radiobutton>(
 				layout,
diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.cpp
index 3c8f155d6..adf38109b 100644
--- a/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.cpp
+++ b/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.cpp
@@ -324,7 +324,7 @@ void AlbumThumbnail::drawSimpleFrame(QPainter &p, QRect to, QSize size) const {
 	const auto Round = [](float64 value) {
 		return int(base::SafeRound(value));
 	};
-	const auto [from, fillBlack] = [&] {
+	const auto &[from, fillBlack] = [&] {
 		if (previewWidth < width && previewHeight < height) {
 			const auto toWidth = Round(previewWidth * scaleWidth);
 			const auto toHeight = Round(previewHeight * scaleHeight);
diff --git a/Telegram/SourceFiles/ui/chat/message_bar.cpp b/Telegram/SourceFiles/ui/chat/message_bar.cpp
index 47f7056e4..c3cf6e113 100644
--- a/Telegram/SourceFiles/ui/chat/message_bar.cpp
+++ b/Telegram/SourceFiles/ui/chat/message_bar.cpp
@@ -20,7 +20,7 @@ namespace Ui {
 namespace {
 
 [[nodiscard]] int SameFirstPartLength(const QString &a, const QString &b) {
-	const auto [i, j] = ranges::mismatch(a, b);
+	const auto &[i, j] = ranges::mismatch(a, b);
 	return (i - a.begin());
 }
 
diff --git a/Telegram/SourceFiles/window/notifications_manager.cpp b/Telegram/SourceFiles/window/notifications_manager.cpp
index e0dc8d193..9ab93b934 100644
--- a/Telegram/SourceFiles/window/notifications_manager.cpp
+++ b/Telegram/SourceFiles/window/notifications_manager.cpp
@@ -309,7 +309,7 @@ System::Timing System::countTiming(
 
 void System::registerThread(not_null<Data::Thread*> thread) {
 	if (const auto topic = thread->asTopic()) {
-		const auto [i, ok] = _watchedTopics.emplace(topic, rpl::lifetime());
+		const auto &[i, ok] = _watchedTopics.emplace(topic, rpl::lifetime());
 		if (ok) {
 			topic->destroyed() | rpl::start_with_next([=] {
 				clearFromTopic(topic);
diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp
index b4be01c6d..cd537957c 100644
--- a/Telegram/SourceFiles/window/window_session_controller.cpp
+++ b/Telegram/SourceFiles/window/window_session_controller.cpp
@@ -1243,7 +1243,7 @@ void SessionController::setupShortcuts() {
 		auto &&accounts = ranges::views::zip(
 			Shortcuts::kShowAccount,
 			ranges::views::ints(0, accountsCount));
-		for (const auto [command, index] : accounts) {
+		for (const auto &[command, index] : accounts) {
 			request->check(command) && request->handle([=] {
 				const auto list = app->domain().orderedAccounts();
 				if (index >= list.size()) {

From fa773c3024daf6e90f564e4f0ea865c4d66c352a Mon Sep 17 00:00:00 2001
From: 23rd <23rd@vivaldi.net>
Date: Tue, 9 Jan 2024 20:42:50 +0300
Subject: [PATCH 40/73] Fixed incorrect seeking of voice messages with
 transcribe button.

---
 .../view/media/history_view_document.cpp      | 26 +++++++++++--------
 1 file changed, 15 insertions(+), 11 deletions(-)

diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_document.cpp
index 1ea9d6fb9..32763b683 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_document.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_document.cpp
@@ -1262,18 +1262,22 @@ TextState Document::textState(
 void Document::updatePressed(QPoint point) {
 	// LayoutMode should be passed here.
 	if (const auto voice = Get<HistoryDocumentVoice>()) {
-		if (voice->seeking()) {
-			const auto thumbed = Get<HistoryDocumentThumbed>();
-			const auto &st = thumbed ? st::msgFileThumbLayout : st::msgFileLayout;
-			const auto nameleft = st.padding.left() + st.thumbSize + st.thumbSkip;
-			const auto nameright = st.padding.right();
-			voice->setSeekingCurrent(std::clamp(
-				(point.x() - nameleft)
-					/ float64(width() - nameleft - nameright),
-				0.,
-				1.));
-			repaint();
+		if (!voice->seeking()) {
+			return;
 		}
+		const auto thumbed = Get<HistoryDocumentThumbed>();
+		const auto &st = thumbed ? st::msgFileThumbLayout : st::msgFileLayout;
+		const auto nameleft = st.padding.left() + st.thumbSize + st.thumbSkip;
+		const auto nameright = st.padding.right();
+		const auto transcribeWidth = voice->transcribe
+			? (st::historyTranscribeSkip + voice->transcribe->size().width())
+			: 0;
+		voice->setSeekingCurrent(std::clamp(
+			(point.x() - nameleft)
+				/ float64(width() - transcribeWidth - nameleft - nameright),
+			0.,
+			1.));
+		repaint();
 	}
 }
 

From b017cc07cec56da8d9e7b18dafbb19e2a0bf63a9 Mon Sep 17 00:00:00 2001
From: 23rd <23rd@vivaldi.net>
Date: Wed, 10 Jan 2024 23:41:01 +0300
Subject: [PATCH 41/73] Removed uppercase from phrase in calls top bar.

---
 Telegram/SourceFiles/calls/calls_top_bar.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Telegram/SourceFiles/calls/calls_top_bar.cpp b/Telegram/SourceFiles/calls/calls_top_bar.cpp
index 17f0008e8..6a3314578 100644
--- a/Telegram/SourceFiles/calls/calls_top_bar.cpp
+++ b/Telegram/SourceFiles/calls/calls_top_bar.cpp
@@ -267,7 +267,7 @@ TopBar::TopBar(
 	? object_ptr<Ui::LabelSimple>(
 		this,
 		st::callBarLabel,
-		tr::lng_call_bar_hangup(tr::now).toUpper())
+		tr::lng_call_bar_hangup(tr::now))
 	: object_ptr<Ui::LabelSimple>(nullptr))
 , _mute(this, st::callBarMuteToggle)
 , _info(this)

From 40ff71b2cd3360d0d61aa1a602a045f33c3702d6 Mon Sep 17 00:00:00 2001
From: 23rd <23rd@vivaldi.net>
Date: Wed, 10 Jan 2024 23:58:01 +0300
Subject: [PATCH 42/73] Added delay to button for media replacement in compose
 control.

---
 Telegram/SourceFiles/history/history_widget.cpp    | 13 ++++++++-----
 .../controls/history_view_compose_controls.cpp     | 14 +++++++++-----
 2 files changed, 17 insertions(+), 10 deletions(-)

diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp
index 7cbe283fe..97c586aed 100644
--- a/Telegram/SourceFiles/history/history_widget.cpp
+++ b/Telegram/SourceFiles/history/history_widget.cpp
@@ -2600,12 +2600,15 @@ bool HistoryWidget::updateReplaceMediaButton() {
 		return false;
 	}
 	_replaceMedia.create(this, st::historyReplaceMedia);
+	const auto hideDuration = st::historyReplaceMedia.ripple.hideDuration;
 	_replaceMedia->setClickedCallback([=] {
-		EditCaptionBox::StartMediaReplace(
-			controller(),
-			{ _history->peer->id, _editMsgId },
-			_field->getTextWithTags(),
-			crl::guard(_list, [=] { cancelEdit(); }));
+		base::call_delayed(hideDuration, this, [=] {
+			EditCaptionBox::StartMediaReplace(
+				controller(),
+				{ _history->peer->id, _editMsgId },
+				_field->getTextWithTags(),
+				crl::guard(_list, [=] { cancelEdit(); }));
+		});
 	});
 	return true;
 }
diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp
index b25be58f9..57cff6c0c 100644
--- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp
+++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "history/view/controls/history_view_compose_controls.h"
 
+#include "base/call_delayed.h"
 #include "base/event_filter.h"
 #include "base/platform/base_platform_info.h"
 #include "base/qt_signal_producer.h"
@@ -2692,12 +2693,15 @@ bool ComposeControls::updateReplaceMediaButton() {
 	_replaceMedia = std::make_unique<Ui::IconButton>(
 		_wrap.get(),
 		st::historyReplaceMedia);
+	const auto hideDuration = st::historyReplaceMedia.ripple.hideDuration;
 	_replaceMedia->setClickedCallback([=] {
-		EditCaptionBox::StartMediaReplace(
-			_regularWindow,
-			_editingId,
-			_field->getTextWithTags(),
-			crl::guard(_wrap.get(), [=] { cancelEditMessage(); }));
+		base::call_delayed(hideDuration, _wrap.get(), [=] {
+			EditCaptionBox::StartMediaReplace(
+				_regularWindow,
+				_editingId,
+				_field->getTextWithTags(),
+				crl::guard(_wrap.get(), [=] { cancelEditMessage(); }));
+		});
 	});
 	return true;
 }

From 21dcb7b13c634044c4e9f325d31338a8e13e2938 Mon Sep 17 00:00:00 2001
From: 23rd <23rd@vivaldi.net>
Date: Sun, 14 Jan 2024 03:49:09 +0300
Subject: [PATCH 43/73] Added initial ability to play video messages with ttl.

---
 .../Resources/art/ttl/video_message_icon.svg  |   4 +
 Telegram/Resources/icons/chat/audio_once.png  | Bin 0 -> 776 bytes
 .../Resources/icons/chat/audio_once@2x.png    | Bin 0 -> 1426 bytes
 .../Resources/icons/chat/audio_once@3x.png    | Bin 0 -> 2115 bytes
 Telegram/Resources/qrc/telegram/telegram.qrc  |   1 +
 .../history/history_item_helpers.cpp          |   4 -
 .../history/history_item_helpers.h            |   1 -
 .../view/media/history_view_document.cpp      |   2 +-
 .../history/view/media/history_view_gif.cpp   | 124 +++++++++++++++---
 .../history/view/media/history_view_gif.h     |   7 +
 Telegram/SourceFiles/ui/chat/chat.style       |   2 +
 Telegram/SourceFiles/ui/chat/chat_style.cpp   |   4 +
 Telegram/SourceFiles/ui/chat/chat_style.h     |   1 +
 13 files changed, 128 insertions(+), 22 deletions(-)
 create mode 100644 Telegram/Resources/art/ttl/video_message_icon.svg
 create mode 100644 Telegram/Resources/icons/chat/audio_once.png
 create mode 100644 Telegram/Resources/icons/chat/audio_once@2x.png
 create mode 100644 Telegram/Resources/icons/chat/audio_once@3x.png

diff --git a/Telegram/Resources/art/ttl/video_message_icon.svg b/Telegram/Resources/art/ttl/video_message_icon.svg
new file mode 100644
index 000000000..aeadc8385
--- /dev/null
+++ b/Telegram/Resources/art/ttl/video_message_icon.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg baseProfile="tiny" version="1.2" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
+<path d="m27.57 30.79q0.77-0.44 1.14-1.28 4.38-9.86 4.67-24.25 0.03-1.64 1.63-1.54 1.14 0.07 1.9 0.65c14.45 10.9 28.35 31.97 18.06 50.37-9.55 17.08-32.38 15.75-41.59-0.69-5.25-9.37-0.83-23.06 4.26-32.03a2.13 2.12 43.5 0 1 3.64-0.09l5.53 8.68a0.57 0.56-31.3 0 0 0.76 0.18z" fill="#fff"/>
+</svg>
diff --git a/Telegram/Resources/icons/chat/audio_once.png b/Telegram/Resources/icons/chat/audio_once.png
new file mode 100644
index 0000000000000000000000000000000000000000..4d1f93652302cd716fe27732e3c5dbe8aba3f60c
GIT binary patch
literal 776
zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9jKx9jP7LeL$-D$|Tv8)E(|mmy
zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDq2jfvL#T#WBP}
zaBGNdww9yF)guZ*M~-McViz+@@=z3WQp#AUk)^n(b-@x((T-MEoi)xTEZVy#_zALb
z|8I!sXl*TPYF*GF->dY<-C$OEP1@bq^1HA0a$6Tazf+u6{%-DWn|t<~E+_w<GiQ!O
zuiNgs?@FwG*3|93Yj@11%X{f17bQa(zIu++&p%sl3tL@VTRZjiQ}?648}7fC?>~NH
z;msVU$3;88RQ<hq^Jd?NFJD?Nq!?9JRNU~6m=mGHb}zwzgRR*@YBHmNQ0JMn&F|mm
zPxLsVk~8u5@9H-}>#tAsQnhY&DBN=Uc65^K{Hs}Ci*~*#u?i0l|H5(IYx(85^XFfd
zcv&JfKY8=b42|bZrUw~~XY!ofwP#Pv+OVTZ6I(d=^?TeF-^}^;=uy*wy7W7oa}qX2
zc&JQ@=U934xdHdW0F40amIS%}=LyO>@o{kipQ`qLV6Di@%QKkfrP>qX9Hc$<)bZo)
zHge(|+s-goS64@`zrLhk`hvq-1Q!Kpv|RsHW5<**fBSS+n;r{e>sH~rtG7vit+MSo
z!LhtWHN!w+*=5aNf*aquZH?kxd^9QXVUg1cexXhI3UaT$fBhP&H$5^`#`4wo-!H5e
z26%AU+uKjn+<x2FsinNU+<gA|LM|qS=P`QU|NQY;_i*8*4A(8Iy_POw{io;W-~Vsh
zj1)PpJvVctdfVI<CvpZWOg=fqN9|~mA?xDZ4+|v3b4;Xc?d&FLE-CmRU0Gj${CC2O
z2(OlhRx*4t0zY4s?Oqw8CBQM`?6beIu1f`-x+B+ywM}7kXtR%5eYGhqz11o4xYObV
zn@{L1&Dfb|Z1?ZiuPC+257R<chjzWH-&N0WVj|~^$r|0WL8;Kw)z4*}Q$iB}r1wNV

literal 0
HcmV?d00001

diff --git a/Telegram/Resources/icons/chat/audio_once@2x.png b/Telegram/Resources/icons/chat/audio_once@2x.png
new file mode 100644
index 0000000000000000000000000000000000000000..f66cfbbcb64460699be84a0b5fb13782246fb078
GIT binary patch
literal 1426
zcmV;D1#S9?P)<h;3K|Lk000e1NJLTq001xm001xu0ssI2*kEqZ00001b5ch_0Itp)
z=>Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@
z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NG{7FPXR9Fe^SXn4`T@*HD4jIZE
z$&i#GQs#Lolth{0Lbx;}TqsIOnKFcwF(o%jp$v(VL|hOVE@V!I%rbq?|DU&QpY!f_
zlGlInf9GoLwVt*1bJpHx@3oGK%HQWVGN7%kt)`~-8#6KI(9zL}kB{%@=$M|K-rU^0
zzP|qa{5(HD_w@8+;+3UTRaH$%Nf{j-eRy~P1M#Q1xmhr`y1KfrudlJOu|WR=!<d+u
zwY4>I5=8U<{+^haD3}Q;1OP8kRaK>@rzfy7hpJ31t#oj3=<e<g2??Q<kk8J}=H}*(
zj*d=GPft!x*4Nj!x3_7Gg@pz5L<3}IW+q&S<~u@UWaQP=6-fgG#I32R!9rjKrANdz
zUtL|LeUZhAiVEpo(yWDrg>P?fBqErVmX=~iOY`B|^!4>yTU)_{1cBh><itnGH>#|x
zq@xTB3>X<1$>%CW`T6;6Y;17RK+e|IR)~-}#@F=m@xg^al8TFqWx7ZQXliOAyAu-=
zX=!QFkqoQ1xA)`YBgf|D<s~*YmI3-T70%p)g9EZyIXOAMHZ^;qva&M5rmn7z4f+Xf
zSXfv?Lql+Iu(V*NrlvPHHyk0HE6Tf`9336G?-yIb+1Xhlr>3SRk{+Bu5^;P34y3ra
zxbpIHcXxNbMdAqQArrN?x3h5?8XA|EmmF?#a*_=a)4I92EiW&V=$J_i!cb^wX>nIF
z9PjV%4Gatzpz!c;5;r0uf`Q<xkeH&PqUYyl;sv3rtBa44IO^=|B#x=6sSI38OACj<
ziHvM95PTIK@%8nU^Box(;iDvu0s;aEA)LAl9QoS&`}-MYF%`GIx3{<1+1WEQGl~*I
zy#4)s&TDCDNf=&NSC{ZjPfr(`S>x#FXl!IFD=QekM<^pB12+kt+}vE@45%>(p}@dE
zp;^}WdxQ`b?_+IkO@|ll74Ahj16{<>P-V!+$H#)OqRQOT(vlc{lMtDd2qHpQ$S>7@
z*gvSKhzQYHp_h-aRFZ(Q1m6=KTttXoOM3Ze=#R^&bC3ue*#!j!B)EtW3U#8gx3?#<
zQUw&HCMG6G5{?ftk%$m(-QC??PK%q8n3X3O8XDpx<Km;qBom1UVT=xt)1n1KnMM#w
z5)u*;kQ**I+BY%_gAiJUsHiA{P?C_GoJ=B*kB<}iOF%h>oy48ji;Ih2205hR;bG2E
zVsXO{g4{ekJ#j8OJ3D+MHL9+zCKm|(1{8!XVwLf+TwGkB<6$HCTJ-#+|3nSLH&P=k
z78+!Z)85`5$|%YxLqr2f_Jj=Rzh?C2V`F2rwY4%zo&5ZKlBB-AK6yeb7X!Ye%LFqt
zA<CUkp6KYk$pWK){i)Ur`vxDci;Igq9E2Fu>ZB?~p3p!coDihQ*wxi_Wn~4S(EWV#
zOv%g3Bby1r)YO!not+{aL|B|AB_$<vFMww28*%<|K{G)Ay~Fi_pLS+uX7YzKH#c8c
zSfF1Fi4O`2l0SkF>EYo)ihnNd-Q8VpZ*Njk63z%AT5R0Y(?he_+S>B*@exBQs6a{v
z2M5XD88QJ@VSavoXlSUuz8?Q;LPJBv1H+NSqW}c#C$C7f$+&R{8>Wzzl_lV0V`GEU
zZ+3R}8;ye?6np6J@88zec6fNmB-Gg0$aYgitEve23+v_Og|Z1hG4QJu4~k5%#Px>@
g=L_^#`hR5L4={7U!d34{WB>pF07*qoM6N<$f-Fvk6951J

literal 0
HcmV?d00001

diff --git a/Telegram/Resources/icons/chat/audio_once@3x.png b/Telegram/Resources/icons/chat/audio_once@3x.png
new file mode 100644
index 0000000000000000000000000000000000000000..bce2481c5534a96ce9c1659f2e5b8becf9c2eb20
GIT binary patch
literal 2115
zcmV-J2)y@+P)<h;3K|Lk000e1NJLTq002k;002k`0ssI2+K(g<00001b5ch_0Itp)
z=>Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@
z1ONa40RR91NT34%1ONa40RR91NB{r;0FW_8?*IS?tw}^dRA>e5n@5NhO%Q;0U31Q2
zR?Ha{RLqF7tcnrLC`Lr_Vz>w<P!UngARde;dl17x%&1_-A0vu62h94bnE(I7fqC_M
zrl)6SXWX~2?^NAgvAd_bx~gAVTK+G82J&Yh&KXFLiFttn1!~u>-KbHc1`QgND_5>m
zsZu3NmVEZ?+4}YCU%h%2lhC->rl+U3Z{L3Q?AeD8AO8IL^M7u?2M->UE?qh<4I<~;
zuwlc63m4wMf8Wg(wQtz4VUa5kJ;QeG+U?%G`}gnPs`~nN|Ni}TdW;=A_Mbn$88c=S
zDN;m-7H>Zv&%S;8^!mGxoIZWJP@zJunhh8*K+@d1cW>y>p{|&II>R^*=5p@bxie?Z
zEL^y-hm7yvzu&!k_tvdjj~_q&_s{S4?c00z?)~xOhbz^N9Xo~(AFd1?KYo1l=+SUc
z`lD}Fty=ZWnKN=~75Ln_b93g*>C&Z(x7kk3)~#Eou(FzO-@Y9*Xpj>>${xO`r%#`{
zivRfW<Dx~2*q@>0)vsUw_3PKJ%;{XPU_ofk212&P*RNllg6UkbVnxM@6%F)0hHKQQ
zk)56G6k+u=ZQ9fakANskmo9b6`}OPB=FOWqhyyY+pm*rdf&J_hH+b-117*-*{De~s
zTfbMYUO`#;#Lvjcm_L91&!0bSx!=Bht5vI(PjX*qef#!hWo!W!aP8W)diCo0qR#<R
z=gyt4UcHLMckkYf7%?IToE)SrTC~6`C{}SI6Z<)EW9cAFt5T(k?&u3CvU26hgm#Km
zn>KAiSV)d3LU#1%QN^kgCr$)Col?Y>gc>+^#VW#x3Kc3OF!CbA4eI5~m(sBJ@836R
z(j-M}A*><Px^d&ik|j%m?V!br7fY*v$BY@1B39L@Q%A-L#0Gv9W4S(j_#h3tc<~}P
zV!!knJnomc@3w8*=Fr7Wn>J1AhLJcqjm+e3B6mp%+_PuTWRzg7YSpT3*s#GqZ8-y(
z^<XRnHV2xb9x=BV*MPY4hYuf0%KiKIYjAyaPna-)D~n_Z%t`2r*bRx5R~V8?G4FyN
zJ$figv~1bZjla3CQl(0J_Uuu(Sxv$*Grb;T&6+g|+pMfCJ^G)KjT<*gnp3Av83;7P
zd`>T3ypXIESVQNlo6C`sjNmS3pj56~lO|0v5V#F<zxeXyi?l>(GUP@Vpzp$k3z9#d
zj~8*{#*HO5v%+@rBG8N}w}B%^j*ym<oO5p2ym|8!hF*8v$&)8b>`Rv}X*4%<H*enD
z@`0Z|eVRUfIu3~&q~r{2U|||=-MY2HuvxQaTB5aU*GlY_D_3e!O#`@ia!dr~7GmUv
z!It2|)|Q>=r4f6^Mg<r@e!LfUkg+%nD_gcKD?w(7w7KDoe)Q;(G^|snPC=CS!i~c)
zFL`NcNr94*kTJL#Z^pSjnlOwsL$PATq(0o8WixNV3B#PXeSBKpGDd2fFpN`64glvo
zqE{IH&1S+d)(6iaWmBq?&AbIC3}eNp)=BNllqn;dsR>uFUcGqn;!fQXhA~DuKw|ub
z%%Kz)6h=L=j!&LEA=)Np@*-F2T~nq^@xl%=7RNb8%FI4>*>2RkrAA3BcJt;<Io7IG
zE7?pbm>t-)Ygbz(!cpREy9tnF*yYQY<rtSv*-R<enXO<?YBj7`v!=bc77$e>CGxGf
zZQC}fD)&7@&6>)pB$zd8mUbF~3`A=fZjd~u)br4xLy9^{Tahs(b(1>jM5*_XAw!g-
z=-|PF62S!~?<Z#hIFc`oTqTan=1iK&MYiv*lP6EwtN^IiQyDi?llPNk+~FcAuSjjE
z$C~xfV32-O>UDmTc~hrOm4*S2963@WB|vwdK7Du&0k<nxu5|3!F#u&UYSE%a?cA%S
zXYby<lTkD+C`my|+o+Nme2#{d(#^`(ty@>=4AES^e7UV2w|>`PJ;TY@nvcxPOnZn+
zIR2EqWcP#y@MfWDZ9;q?i_a@MoIa0}bT49T`~<-ISlh;)7>FbBA_*C}1@R)lw#ov?
zOMB(+a!b(=a<^>RqS!_-iQ^8zDz!*Rft){oUa^hBwCnvRwNy81!@);zsn|vxFsc~R
zsKB^!<M7EG=u}Helqg}u4}j37O&jMsSUgg<ZruWK366>x+qZ9*b4v9nI2%t29x=&)
z$P$L1<S6%~3qMF|{`&Rn(kkFPckYDesG}s^&1)t_63w$`&vx(LT}Kt7-%4C7v4th~
z*s)_F7@A4Qc<X9;2M!!yj?FAg7`%sk{`|Rf`sg7-VARAQ>^L`3rQ&8Q!Cu|9YgfO1
z{mkWHjdTJoHk-{*0z?b3=P5V?ly6#U0L&{<UiVI%IFT}9!GZ-fR=9LiIZ#+x&7C`U
z{=KLAqehL=?HQ>Y@0~f*iBq{*Y~8w*cMBdRQdUekTG;u#d~%tCSi6|TWy_X1i_6k5
zC(NPbG5v(2Z{ECt3YWWT)hgwh9IH7hb1H7UPf{`vPJ3jJ_dg}5jo>88moKkM%9S=j
z7MD@-C9ZB-S_SO!Pl;Sn-j`5jdf>o;t5>fk>~h_u9{-f^CBQdQjBiD820TyZro)AR
t4aSd0Bp&~i@U3$`V*U)|&p-;y!2gyI^B>FjSK$Bv002ovPDHLkV1mRq0BryO

literal 0
HcmV?d00001

diff --git a/Telegram/Resources/qrc/telegram/telegram.qrc b/Telegram/Resources/qrc/telegram/telegram.qrc
index 57afe3882..ccf1a1d7a 100644
--- a/Telegram/Resources/qrc/telegram/telegram.qrc
+++ b/Telegram/Resources/qrc/telegram/telegram.qrc
@@ -26,6 +26,7 @@
     <file alias="recording/info_audio.svg">../../art/recording/recording_info_audio.svg</file>
     <file alias="recording/info_video_landscape.svg">../../art/recording/recording_info_video_landscape.svg</file>
     <file alias="recording/info_video_portrait.svg">../../art/recording/recording_info_video_portrait.svg</file>
+    <file alias="ttl/video_message_icon.svg">../../art/ttl/video_message_icon.svg</file>
     <file alias="icons/settings/dino.svg">../../icons/settings/dino.svg</file>
     <file alias="icons/settings/star.svg">../../icons/settings/star.svg</file>
     <file alias="icons/settings/starmini.svg">../../icons/settings/starmini.svg</file>
diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp
index 4a29e4e93..a249195c9 100644
--- a/Telegram/SourceFiles/history/history_item_helpers.cpp
+++ b/Telegram/SourceFiles/history/history_item_helpers.cpp
@@ -808,7 +808,3 @@ void ClearMediaAsExpired(not_null<HistoryItem*> item) {
 		}
 	}
 }
-
-[[nodiscard]] bool IsVoiceOncePlayable(not_null<HistoryItem*> item) {
-	return !item->out() && item->media()->ttlSeconds();
-}
diff --git a/Telegram/SourceFiles/history/history_item_helpers.h b/Telegram/SourceFiles/history/history_item_helpers.h
index f96d9c092..c438eccbe 100644
--- a/Telegram/SourceFiles/history/history_item_helpers.h
+++ b/Telegram/SourceFiles/history/history_item_helpers.h
@@ -158,4 +158,3 @@ ClickHandlerPtr JumpToStoryClickHandler(
 void ShowTrialTranscribesToast(int left, TimeId until);
 
 void ClearMediaAsExpired(not_null<HistoryItem*> item);
-[[nodiscard]] bool IsVoiceOncePlayable(not_null<HistoryItem*> item);
diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_document.cpp
index 32763b683..cf5560009 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_document.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_document.cpp
@@ -425,7 +425,7 @@ void Document::createComponents(bool caption) {
 			_realParent->fullId());
 	}
 	if (const auto voice = Get<HistoryDocumentVoice>()) {
-		voice->seekl = !IsVoiceOncePlayable(_parent->data())
+		voice->seekl = !_parent->data()->media()->ttlSeconds()
 			? std::make_shared<VoiceSeekClickHandler>(_data, [](FullMsgId) {})
 			: nullptr;
 		if (_transcribedRound) {
diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp
index 87a0e00bf..6176bf050 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp
@@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "media/view/media_view_playback_progress.h"
 #include "ui/boxes/confirm_box.h"
 #include "ui/painter.h"
+#include "ui/rect.h"
 #include "history/history_item_components.h"
 #include "history/history_item_helpers.h"
 #include "history/history_item.h"
@@ -30,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "history/view/history_view_cursor_state.h"
 #include "history/view/history_view_reply.h"
 #include "history/view/history_view_transcribe_button.h"
+#include "history/view/media/history_view_document.h" // TTLVoiceStops
 #include "history/view/media/history_view_media_common.h"
 #include "history/view/media/history_view_media_spoiler.h"
 #include "window/window_session_controller.h"
@@ -52,6 +54,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_document_media.h"
 #include "styles/style_chat.h"
 
+#include <QSvgRenderer>
+
 namespace HistoryView {
 namespace {
 
@@ -65,6 +69,41 @@ int gifMaxStatusWidth(DocumentData *document) {
 	return result;
 }
 
+[[nodiscard]] HistoryView::TtlRoundPaintCallback CreateTtlPaintCallback(
+		Fn<void()> update) {
+	const auto iconSize = Size(std::min(
+		st::historyFileInPause.width(),
+		st::historyFileInPause.height()));
+	const auto centerMargins = Margins(st::historyFileInPause.width() * 3);
+
+	const auto renderer = std::make_shared<QSvgRenderer>(
+		u":/gui/ttl/video_message_icon.svg"_q);
+
+	return [=](QPainter &p, QRect r, const PaintContext &context) {
+		const auto centerRect = r - centerMargins;
+		const auto &icon = context.imageStyle()->historyVideoMessageTtlIcon;
+		const auto iconRect = QRect(
+			rect::right(centerRect) - icon.width() * 0.75,
+			rect::bottom(centerRect) - icon.height() * 0.75,
+			icon.width(),
+			icon.height());
+		{
+			auto hq = PainterHighQualityEnabler(p);
+			auto path = QPainterPath();
+			path.setFillRule(Qt::WindingFill);
+			path.addEllipse(centerRect);
+			path.addEllipse(iconRect);
+			p.fillPath(path, st::shadowFg);
+			p.fillPath(path, st::shadowFg);
+			p.fillPath(path, st::shadowFg);
+		}
+
+		renderer->render(&p, centerRect - Margins(centerRect.width() / 4));
+
+		icon.paint(p, iconRect.topLeft(), centerRect.width());
+	};
+}
+
 } // namespace
 
 struct Gif::Streamed {
@@ -83,6 +122,12 @@ Gif::Streamed::Streamed(
 : instance(std::move(shared), std::move(waitingCallback)) {
 }
 
+[[nodiscard]] bool IsHiddenRoundMessage(not_null<Element*> parent) {
+	return parent->delegate()->elementContext() != Context::TTLViewer
+		&& parent->data()->media()
+		&& parent->data()->media()->ttlSeconds();
+}
+
 Gif::Gif(
 	not_null<Element*> parent,
 	not_null<HistoryItem*> realParent,
@@ -95,18 +140,45 @@ Gif::Gif(
 	: FullStoryId())
 , _caption(
 	st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right())
-, _spoiler(spoiler ? std::make_unique<MediaSpoiler>() : nullptr)
+, _spoiler((spoiler || IsHiddenRoundMessage(_parent))
+	? std::make_unique<MediaSpoiler>()
+	: nullptr)
 , _downloadSize(Ui::FormatSizeText(_data->size)) {
-	setDocumentLinks(_data, realParent, [=] {
-		if (!_data->createMediaView()->canBePlayed(realParent)
-			|| !_data->isAnimation()
-			|| _data->isVideoMessage()
-			|| !CanPlayInline(_data)) {
-			return false;
+	auto hasDefaultDocumentLinks = false;
+	if (_data->isVideoMessage() && _parent->data()->media()->ttlSeconds()) {
+		if (_spoiler) {
+			_drawTtl = CreateTtlPaintCallback([=] { repaint(); });
 		}
-		playAnimation(false);
-		return true;
-	});
+		const auto fullId = _realParent->fullId();
+		_parent->data()->removeFromSharedMediaIndex();
+		setDocumentLinks(_data, realParent, [=] {
+			auto lifetime = std::make_shared<rpl::lifetime>();
+			TTLVoiceStops(fullId) | rpl::start_with_next([=]() mutable {
+				const auto item = _parent->data();
+				if (lifetime) {
+					base::take(lifetime)->destroy();
+				}
+				if (!item->out()) {
+					// Destroys this.
+					ClearMediaAsExpired(item);
+				}
+			}, *lifetime);
+
+			return false;
+		});
+	} else {
+		setDocumentLinks(_data, realParent, [=] {
+			if (!_data->createMediaView()->canBePlayed(realParent)
+				|| !_data->isAnimation()
+				|| _data->isVideoMessage()
+				|| !CanPlayInline(_data)) {
+				return false;
+			}
+			playAnimation(false);
+			return true;
+		});
+	}
+
 	setStatusSize(Ui::FileStatusSizeReady);
 
 	if (_spoiler) {
@@ -403,7 +475,13 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
 
 	QRect rthumb(style::rtlrect(usex + paintx, painty, usew, painth, width()));
 
-	const auto revealed = (!isRound && _spoiler)
+	const auto inTTLViewer = _parent->delegate()->elementContext()
+		== Context::TTLViewer;
+	const auto revealed = (isRound
+			&& item->media()->ttlSeconds()
+			&& !inTTLViewer)
+		? 0
+		: (!isRound && _spoiler)
 		? _spoiler->revealAnimation.value(_spoiler->revealed ? 1. : 0.)
 		: 1.;
 	const auto fullHiddenBySpoiler = (revealed == 0.);
@@ -525,10 +603,19 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
 		p.drawImage(rthumb, _thumbCache);
 	}
 
-	if (!isRound && revealed < 1.) {
+	if (revealed < 1.) {
 		p.setOpacity(1. - revealed);
-		p.drawImage(rthumb.topLeft(), _spoiler->background);
-		fillImageSpoiler(p, _spoiler.get(), rthumb, context);
+		if (!isRound) {
+			p.drawImage(rthumb.topLeft(), _spoiler->background);
+			fillImageSpoiler(p, _spoiler.get(), rthumb, context);
+		} else {
+			auto frame = _spoiler->background;
+			{
+				auto q = QPainter(&frame);
+				fillImageSpoiler(q, _spoiler.get(), rthumb, context);
+			}
+			p.drawImage(rthumb.topLeft(), Images::Circle(std::move(frame)));
+		}
 		p.setOpacity(1.);
 	}
 	if (context.selected()) {
@@ -793,6 +880,9 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
 			paintTranscribe(p, usex, fullBottom, false, context);
 		}
 	}
+	if (_drawTtl) {
+		_drawTtl(p, rthumb, context);
+	}
 }
 
 void Gif::paintTranscribe(
@@ -1108,7 +1198,9 @@ TextState Gif::textState(QPoint point, StateRequest request) const {
 	}
 	if (QRect(usex + paintx, painty, usew, painth).contains(point)) {
 		ensureDataMediaCreated();
-		result.link = (_spoiler && !_spoiler->revealed)
+		result.link = (isRound && _parent->data()->media()->ttlSeconds())
+			? _openl // Overriden.
+			: (_spoiler && !_spoiler->revealed)
 			? _spoiler->link
 			: _data->uploading()
 			? _cancell
@@ -1970,7 +2062,7 @@ bool Gif::needCornerStatusDisplay() const {
 
 void Gif::ensureTranscribeButton() const {
 	if (_data->isVideoMessage()
-		&& !IsVoiceOncePlayable(_parent->data())
+		&& !_parent->data()->media()->ttlSeconds()
 		&& (_data->session().premium()
 			|| _data->session().api().transcribes().trialsSupport())) {
 		if (!_transcribe) {
diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.h b/Telegram/SourceFiles/history/view/media/history_view_gif.h
index 1d1b30e4a..dad49b351 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_gif.h
+++ b/Telegram/SourceFiles/history/view/media/history_view_gif.h
@@ -40,6 +40,11 @@ namespace HistoryView {
 class Reply;
 class TranscribeButton;
 
+using TtlRoundPaintCallback = Fn<void(
+	QPainter&,
+	QRect,
+	const PaintContext &context)>;
+
 class Gif final : public File {
 public:
 	Gif(
@@ -214,6 +219,8 @@ private:
 
 	void togglePollingStory(bool enabled) const;
 
+	TtlRoundPaintCallback _drawTtl;
+
 	const not_null<DocumentData*> _data;
 	const FullStoryId _storyId;
 	Ui::Text::String _caption;
diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style
index bc54f7366..378393643 100644
--- a/Telegram/SourceFiles/ui/chat/chat.style
+++ b/Telegram/SourceFiles/ui/chat/chat.style
@@ -536,6 +536,8 @@ historyVideoMessageMute: icon {{ "volume_mute", historyFileThumbIconFg }};
 historyVideoMessageMuteSelected: icon {{ "volume_mute", historyFileThumbIconFgSelected }};
 historyVideoMessageMuteSize: 25px;
 historyVideoMessageProgressOpacity: 0.72;
+historyVideoMessageTtlIcon: icon {{ "chat/audio_once", historyFileThumbIconFg }};
+historyVideoMessageTtlIconSelected: icon {{ "chat/audio_once", historyFileThumbIconFgSelected }};
 
 historyAdminLogEmptyWidth: 260px;
 historyAdminLogEmptyPadding: margins(10px, 12px, 10px, 12px);
diff --git a/Telegram/SourceFiles/ui/chat/chat_style.cpp b/Telegram/SourceFiles/ui/chat/chat_style.cpp
index 0fc4840f1..4acf416ba 100644
--- a/Telegram/SourceFiles/ui/chat/chat_style.cpp
+++ b/Telegram/SourceFiles/ui/chat/chat_style.cpp
@@ -528,6 +528,10 @@ ChatStyle::ChatStyle(rpl::producer<ColorIndicesCompressed> colorIndices) {
 		&MessageImageStyle::historyVideoMessageMute,
 		st::historyVideoMessageMute,
 		st::historyVideoMessageMuteSelected);
+	make(
+		&MessageImageStyle::historyVideoMessageTtlIcon,
+		st::historyVideoMessageTtlIcon,
+		st::historyVideoMessageTtlIconSelected);
 	make(
 		&MessageImageStyle::historyPageEnlarge,
 		st::historyPageEnlarge,
diff --git a/Telegram/SourceFiles/ui/chat/chat_style.h b/Telegram/SourceFiles/ui/chat/chat_style.h
index 45a4f3caf..11890a061 100644
--- a/Telegram/SourceFiles/ui/chat/chat_style.h
+++ b/Telegram/SourceFiles/ui/chat/chat_style.h
@@ -118,6 +118,7 @@ struct MessageImageStyle {
 	style::icon historyVideoDownload = { Qt::Uninitialized };
 	style::icon historyVideoCancel = { Qt::Uninitialized };
 	style::icon historyVideoMessageMute = { Qt::Uninitialized };
+	style::icon historyVideoMessageTtlIcon = { Qt::Uninitialized };
 	style::icon historyPageEnlarge = { Qt::Uninitialized };
 };
 

From 5ba918d2134d59c27393fc2fe94706351e2e2c6a Mon Sep 17 00:00:00 2001
From: 23rd <23rd@vivaldi.net>
Date: Sun, 14 Jan 2024 04:00:59 +0300
Subject: [PATCH 44/73] Added support of chat themes to viewer widget for
 messages with ttl.

---
 .../chat_helpers/ttl_media_layer_widget.cpp   | 27 ++++++++++++++-----
 1 file changed, 21 insertions(+), 6 deletions(-)

diff --git a/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp b/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp
index baf5c1be6..e6a71d509 100644
--- a/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp
+++ b/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp
@@ -28,6 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/widgets/buttons.h"
 #include "ui/widgets/labels.h"
 #include "ui/widgets/tooltip.h"
+#include "window/section_widget.h" // Window::ChatThemeValueFromPeer.
 #include "window/themes/window_theme.h"
 #include "window/window_session_controller.h"
 #include "styles/style_chat.h"
@@ -81,7 +82,10 @@ bool PreviewDelegate::elementIsChatWide() {
 
 class PreviewWrap final : public Ui::RpWidget {
 public:
-	PreviewWrap(not_null<Ui::RpWidget*> parent, not_null<HistoryItem*> item);
+	PreviewWrap(
+		not_null<Ui::RpWidget*> parent,
+		not_null<HistoryItem*> item,
+		rpl::producer<std::shared_ptr<Ui::ChatTheme>> theme);
 	~PreviewWrap();
 
 	[[nodiscard]] rpl::producer<> closeRequests() const;
@@ -91,9 +95,9 @@ private:
 	[[nodiscard]] QRect elementRect() const;
 
 	const not_null<HistoryItem*> _item;
-	const std::unique_ptr<Ui::ChatTheme> _theme;
 	const std::unique_ptr<Ui::ChatStyle> _style;
 	const std::unique_ptr<PreviewDelegate> _delegate;
+	std::shared_ptr<Ui::ChatTheme> _theme;
 	std::unique_ptr<HistoryView::Element> _element;
 	rpl::lifetime _elementLifetime;
 
@@ -108,17 +112,23 @@ private:
 
 PreviewWrap::PreviewWrap(
 	not_null<Ui::RpWidget*> parent,
-	not_null<HistoryItem*> item)
+	not_null<HistoryItem*> item,
+	rpl::producer<std::shared_ptr<Ui::ChatTheme>> theme)
 : RpWidget(parent)
 , _item(item)
-, _theme(Window::Theme::DefaultChatThemeOn(lifetime()))
 , _style(std::make_unique<Ui::ChatStyle>(
 	item->history()->session().colorIndicesValue()))
 , _delegate(std::make_unique<PreviewDelegate>(
 	parent,
 	_style.get(),
 	[=] { update(elementRect()); })) {
-	_style->apply(_theme.get());
+
+	std::move(
+		theme
+	) | rpl::start_with_next([=](std::shared_ptr<Ui::ChatTheme> theme) {
+		_theme = std::move(theme);
+		_style->apply(_theme.get());
+	}, lifetime());
 
 	const auto session = &_item->history()->session();
 	session->data().viewRepaintRequest(
@@ -269,7 +279,12 @@ void ShowTTLMediaLayerWidget(
 		not_null<HistoryItem*> item) {
 	const auto parent = controller->content();
 	const auto show = controller->uiShow();
-	auto preview = base::make_unique_q<PreviewWrap>(parent, item);
+	auto preview = base::make_unique_q<PreviewWrap>(
+		parent,
+		item,
+		Window::ChatThemeValueFromPeer(
+			controller,
+			item->history()->peer));
 	preview->closeRequests(
 	) | rpl::start_with_next([=] {
 		show->hideLayer();

From 268613e1dbfe68bed3b0b485d026622b0eb14a1f Mon Sep 17 00:00:00 2001
From: 23rd <23rd@vivaldi.net>
Date: Sun, 14 Jan 2024 04:50:51 +0300
Subject: [PATCH 45/73] Slightly improved style of voice record bar.

---
 .../icons/voice_lock/audio_once_bg.png        | Bin 0 -> 496 bytes
 .../icons/voice_lock/audio_once_bg@2x.png     | Bin 0 -> 895 bytes
 .../icons/voice_lock/audio_once_bg@3x.png     | Bin 0 -> 1304 bytes
 .../icons/voice_lock/audio_once_number.png    | Bin 0 -> 269 bytes
 .../icons/voice_lock/audio_once_number@2x.png | Bin 0 -> 389 bytes
 .../icons/voice_lock/audio_once_number@3x.png | Bin 0 -> 529 bytes
 .../icons/voice_lock/recorded_delete.png      | Bin 0 -> 614 bytes
 .../icons/voice_lock/recorded_delete@2x.png   | Bin 0 -> 1112 bytes
 .../icons/voice_lock/recorded_delete@3x.png   | Bin 0 -> 1556 bytes
 .../chat_helpers/chat_helpers.style           |  15 ++++--
 .../history_view_voice_record_bar.cpp         |  45 +++---------------
 11 files changed, 16 insertions(+), 44 deletions(-)
 create mode 100644 Telegram/Resources/icons/voice_lock/audio_once_bg.png
 create mode 100644 Telegram/Resources/icons/voice_lock/audio_once_bg@2x.png
 create mode 100644 Telegram/Resources/icons/voice_lock/audio_once_bg@3x.png
 create mode 100644 Telegram/Resources/icons/voice_lock/audio_once_number.png
 create mode 100644 Telegram/Resources/icons/voice_lock/audio_once_number@2x.png
 create mode 100644 Telegram/Resources/icons/voice_lock/audio_once_number@3x.png
 create mode 100644 Telegram/Resources/icons/voice_lock/recorded_delete.png
 create mode 100644 Telegram/Resources/icons/voice_lock/recorded_delete@2x.png
 create mode 100644 Telegram/Resources/icons/voice_lock/recorded_delete@3x.png

diff --git a/Telegram/Resources/icons/voice_lock/audio_once_bg.png b/Telegram/Resources/icons/voice_lock/audio_once_bg.png
new file mode 100644
index 0000000000000000000000000000000000000000..62778676ce6400ba75ae200f873734be4be7d0ff
GIT binary patch
literal 496
zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9jKx9jP7LeL$-D$|Tv8)E(|mmy
zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDlgfEXvcxF~mY}
z>y(3i?gj$wk$3hq>?l;8DA0dk_6Iq+!v~EOTV_x0*ioc%^KY)l6s><J^MsA&o~xPb
zmwu|S-sFdLU+(Q?nOa>&=ZmkGX3jG6El#eFo0MwVwP;)J{*{3ut`coc3Ug{-?0o<G
zut7je^P*K+E?&Ri%~If)mTGBXsP@<*>Ga*nQ#O2<wCT{nT^-z&l{SL1GUqDJiEr}0
zz4xNR_a_^7oVKoFK4kk&;mT=g`DwlDUMJK(+<oHazSEnZSe#>XdTTd(vu;S}T?d=`
z>l!(SwkdgDV*9nGS)<_ocj*@sDo?!&Y|Ofq`^hFf_S9av(#=72`Mk<~k3)ZanXhv4
zAnVjV=6m^m>ldxQ5X~;2HZ64_e`fYHHraDE3^wOyFYA2j`O5L&?Bc%GInr%`*G*qK
zpSBP*7MrBnrBt;`$6Kn=`jB<uMDf1et22Es$6R-1XXst!yf-D%E$^rPCw^(24I&KB
Rg+PJL;OXk;vd$@?2>|hXzrX+h

literal 0
HcmV?d00001

diff --git a/Telegram/Resources/icons/voice_lock/audio_once_bg@2x.png b/Telegram/Resources/icons/voice_lock/audio_once_bg@2x.png
new file mode 100644
index 0000000000000000000000000000000000000000..225d6af3b8da0d7bebd0f07b0fa325f91ba8aa95
GIT binary patch
literal 895
zcmV-_1AzRAP)<h;3K|Lk000e1NJLTq001xm001xu0ssI2*kEqZ00001b5ch_0Itp)
z=>Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@
z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NE=}AOER9Fe^SUrz%K@{c{u@oW-
z@fC$A=p=L!4Hb#~1&Y=A4+;&PAK@cZ3WbhRLPDq!3E2&@B2nxnHd*ic<dw|5%jM4a
znD^aeZ*g<(nKSb|_sq;WXGWp;dA^wklo#+-sZ@bLAR3LjTrRWOY_VAI-fp+g&(AM^
zpTS^ItJP++*#(2MbqR;V`Fwu0TK%SYy4`Lnm9p7vY#5Z*KA*4AXi(5NG<ko2&tx)s
zy`B<C-ppt;Rw|X<Zg&X9^8NbynoK6;!DKBw9?xhrVu3zqTPl?v9v);tPa0s^ZZ?~9
z2<&a0P6wOdB;=U}u=)J_WJi?mF`Z7CIbo)5?i(>v_=#ceu!?WEZp1vqGS<_X`T5d>
zJtgjWKbdOZA>LfM!|8tjvEVp9slGTzp(qxMPN(xgd8bdEPKS}3&F0DRks0X!?<SUr
z;|ICrmzZ5V9v6RdkhNOv)6<hQ5OO$W*1U;t$RNnU(u9J+ATi=BI2;bbNtiN(W}@XR
zJU%`WM1c^hEu!Ttc)ebNC=f!`N3@&;yWLI@1wzaTkBo7(Al~@`p?~NhX+mnX`s!c3
zZ~%=)LwX7x!kK|+ISY%$f*=ZnhQlGzau(+EIYAT%p#~ya&cc7^q49Vu;R3hY%{gNb
zIAkpeLQwU3Jz~U7Kt@MiCxnDG1Xa0QCJo$#dcBV0jWiqr)W^pMH{a}mLZOf}o4q!d
z%kd<H`+{?9(1xmexm@z$gwq7h6f%U+oJb`25W;y&o((LNN@cZLy*DSsVVp>*2*C)o
zce~xbA19pCaayNkv$r#uOq0pv9-OerBasLjo4qw^@0*88_~xqO9d{IkLg7|^!n$Fe
zDM?S_R$#qeUqcH!9D9mk6-)9ED-8xiHk*BWd%J)Zq90Yf5#v$HsJqkYbid!Hp@po8
zJ7k&6a0GnG#W@DSdXL3oe!t&pwIT!Jjk*|h7HU7dQQshCwpy)|S@Vd;kK+4!;16gm
V2KWrE74!fA002ovPDHLkV1jt{gmnM_

literal 0
HcmV?d00001

diff --git a/Telegram/Resources/icons/voice_lock/audio_once_bg@3x.png b/Telegram/Resources/icons/voice_lock/audio_once_bg@3x.png
new file mode 100644
index 0000000000000000000000000000000000000000..1d82b2d6e73cd0cec973a1b417096b19ab1e17d7
GIT binary patch
literal 1304
zcmV+z1?T#SP)<h;3K|Lk000e1NJLTq002k;002k`0ssI2+K(g<00001b5ch_0Itp)
z=>Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@
z1ONa40RR91NT34%1ONa40RR91NB{r;0FW_8?*IS<f=NU{RA>e5Tg@wVK^W&1<s-uF
zDxwr+LloU~QL^AqKuY%h3pN%kSSgXRR5msiFG<R0ws&z$lCPrM^;yLG{@(Z2HRrkK
z`#f{*8Qrs*IWzOj?>95&nP;A7PF`N_$&Em61Z0jtzC7WjrKQcy%}q^Bm6er9<N5gb
zczk?JCX?6K*Rfda?(R-r@65y+8yg1(2KxK^dwYB9>+8*NClZOXv$Nm-pOcf5&(BZu
z`dKIqg+h~)lLrR}f80K|x3{yivu$l{SwOGbWkW;5{QUgu>#JKvwB6p`-r(S%ZeKpV
ztf;71SXg*}f2Ytm@<&HU(P-2sCSo^ZV`Er6925*?e0_Z_FE3+}i~UMW7Zw&SE-o6v
z;V!tky6WoclE^0|8soRSyUPvULf+fk+sMd>L~gCEt>@?G7AT|@Pft(h=QD3k{|0Ml
zXz1zbNg8Zhf;TocN=iyNrw((Kbyg|r;AV5)3M>Zz-__2}PB<L4tm#9AmX;Q1ukGz^
zuYEy^|JuTGT{7qrKQ+50pL7N2Nl$wKcHy+Fpe3-sx!k*9G0C<qJPsD@5;tEO>_Cql
z%We*v%dtHPn_ZH1%gYwFva?AG{&b}W$iW#0A0^}YDvdx=0_Qi~N&A)gc6fNWxVSj;
zeK6nj^z`VSyLE^`%LkK#5vuX=aigN|_wdF@S~K#*Vp}RGptd2MG5DCiSt^pMsj2Dg
z>?B|*F?h~M4JERf^FfKhAE*Qq0y2}B=1f{vR)%vIX`@6IzSpX%DiNw?#^8<?%M?n(
zi;gc%1pGsYffCN3hQz>IEK@68TU#pv*31|JB4U|ZX`Hh}z(0prd3m`ZF|k0oG~6>H
zX3dN#s_RpHUS3`diNWX+%hXDn5xcp$QHzX-7JIaq)hxw}iwm(#t#mvd7XfQbDiqw`
z-&a>xi#1e9!yXh@qh`jy;q>%W6(%Ci%galV6^bY^1jxk(s-!bLAC%bU=B6rSM4Skl
zh*_O9f;7;L@{aM6B*UXY$j})>$Ls6sq*B1Mhlhs<wGxn&7>W^LA(aB2U0YkTixA(U
zq&34X7QS8G{YhuWz<y?C#_kwoyWY~$(!;}p0~GuF`|P93E)bCu#7`WsLe;*$K6V`J
z0uelM#wt~NWo3mO3Zp=rMaZeyE=jmRg1o1BiZQ?u8O#VE!=5xTF~PWgSVZB525|(~
zU4*3G)YKFUAQmAoGF;vlAgoeZ?CvbEJPLtV2KS$I5xRU?R8+LJwdE0($L3&#yJ{{8
zm^4FhZEkMPBRJR1u>zUriclRL9rZu-4sF5tT@gy@Y<qjVpEok0*?Dwent(|H2KDOd
zD(=R(URrh<!nW|V3!4xyFY4~@K0ZFS1JP+6SOu^HU0O`$pzW7JQ!(CY#0LyYj%{}P
zaGlod=;(mX^kQ4WL5BZGxReBSm^$S{>SIfYL?XC)fCpC;EY}kJfjF(h$Amw3ppb|(
zFIi?{1PbE9h@jTh)ggF=2g(g@O+4_k!y}F7`T3bN$z^jRkQ)ItM&K`hc6KrbLxH3K
O0000<MNUMnLSTXm?^1~X

literal 0
HcmV?d00001

diff --git a/Telegram/Resources/icons/voice_lock/audio_once_number.png b/Telegram/Resources/icons/voice_lock/audio_once_number.png
new file mode 100644
index 0000000000000000000000000000000000000000..61e22f105da97f4ed4b33d91552f4b9cd957ce76
GIT binary patch
literal 269
zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9jKx9jP7LeL$-D$|Tv8)E(|mmy
zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDlfUm3z84hFAzD
zCrGd^R&@9*@Zj0Abt_gxY)ooxXkhp`?c|9QcXpM&wwGXGV|!%Y!P&oM%a$)6A0Iz`
z`gCJfL`1}g6)av4jbA)DIr-zqkNR<Y4&<}Ah|PCA@}hmU<VCi-yUXAI{{H^^`}^(e
n>_2(Z($YSe_P>%jqR7BdJW2j{^zmQ=kW)Ne{an^LB{Ts5y|q@-

literal 0
HcmV?d00001

diff --git a/Telegram/Resources/icons/voice_lock/audio_once_number@2x.png b/Telegram/Resources/icons/voice_lock/audio_once_number@2x.png
new file mode 100644
index 0000000000000000000000000000000000000000..15047cb489de2c2d3655642ee1b618a6d63b20d9
GIT binary patch
literal 389
zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1SD@H<Xr$#jKx9jP7LeL$-D$|Tv8)E(|mmy
zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(;Zuz(rC1}VI4p2GvA-g~+@hD30_
zon*_^Y#`A#c^ikE>jxG|<Ic^Kqt`un;rr;xJI-BJRl<P<1%fiSwj{ATD*aUOzMub5
zy|TXIhs5Cq1`a4{2xls<l6r3Ads*l7v&whB=jC}6bv)kjde?QkAg@WXt~m!pTsP{R
zj(hfb&8n_Po|m%L)`wmzy?bx=GR2ujWyRNKFExzXZ0@ykTkhOiKBw<amGh>jO1cKd
zUS|(J_55(;pFe$@|M<M{dvRvs5Bb&{21kyT9G0BtHx%t#juqZ%YD?UpT4m`!DP@-0
wWjBt8svn#@B-$JWVs><BD=;uYP`%#+-eZ>|h0fLfR|Extr>mdKI;Vst0JcAc^Z)<=

literal 0
HcmV?d00001

diff --git a/Telegram/Resources/icons/voice_lock/audio_once_number@3x.png b/Telegram/Resources/icons/voice_lock/audio_once_number@3x.png
new file mode 100644
index 0000000000000000000000000000000000000000..6e39511e17a9cc3d47e44d8e9ecab7656e4728ff
GIT binary patch
literal 529
zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1SD_us|Wxo#^NA%Cx&(BWL^R}E~ycoX}-P;
zT0k}j17mw80}DtA5K93u0|WB{Mh0de%?J`(zyz1|Sip>6gA`6MbbAj}R_*EH7?Q#I
z_J(a&vx5ZNgB{T~Z+JTB_(<nm?&w`BcYwQsssFL+mW^A!GE6$h9nqorXXTOD$ix{p
zatzP$v|U#^qjF}?XWMwC8K+srSrix;IUE?61R5BSnJL^g+|~`tGW(A^U;nx-cY3Pe
zw9|$s40=>NpH=?*xqIIE<*AV}pO56ox(43=9_{(W)Hm4hWlYeu{Mc!!OC+i;l*&%`
zV4S^c-E9-z*fV)L4|tBd-+BK%bJh#5vddo=KMuZM{-<ibobl({_|VWz;ejIao=<$)
z$-XT&|NDVNiANS;B1cVpcQrCu9qZ2Dd$c{VVsG5`hVz;^>}vZA^!9Iz4GmhR;lpL^
ztYLMmK|NK|O09LSqKItrfytXhWG5dwC(zYr_`ox=t8e4R{>L8nG3)pj<oy1-KPupO
z-1_jyX-_OF=S}|jlHF0@O6lHjYgcI<FZ7G&k3I0~`SG`9i|-$lKo1RQ7|qxD!0o#2
Vw#!!Y_pPAl@O1TaS?83{1OQg1zrX+h

literal 0
HcmV?d00001

diff --git a/Telegram/Resources/icons/voice_lock/recorded_delete.png b/Telegram/Resources/icons/voice_lock/recorded_delete.png
new file mode 100644
index 0000000000000000000000000000000000000000..f204f8f2206338ead7b6064d93547a8490ff43e8
GIT binary patch
literal 614
zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9jKx9jP7LeL$-D$|Tv8)E(|mmy
zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDlgf?1HC@V~B;|
z*^u4q7z0JVN~j3Abhu}@E)Q6F(bL6wiriEV`v=S+Rk~B>{b1?za8cYo;l&f7j~*F&
z?v}otoh*~Q?vkEb_WHf&X4R&hx8ypyyftXq!T=2s4i*zBUX$gQSKfcW{dTV8-jbCe
zTCwY|KP~!M+kG^taZAujkJYI+u4aX<4V!wZ`ZQ3yz#?O7)X7k$<c(@GeWZBU46?S~
z$}#(0v#)CJIhMTb(zkWQx@Vtdds4PL*F-8~GUvaByLs(Qt>(;0v(FyeT~jd6Z+Y^@
z88yuh3w8u(oX8eg8@Bpp&Locec5?l~=bnE)m@uJWZP@D)t2q<9P6OR_`K5}{M30os
zk%uJ0RD=|H7*p;zSt_47nqhK@iD8!2rR_awn<d*;1^wOkVCuxf4;$Y6?_+V?5VbZ*
z*`R9gJgY<YnHwYS-I6lZH(PXnZqEh2a|iyrT`{hGe=xy7hVS8y`|tPXG4d$e%-_!T
zNwLIAw$DRrYE^=(r-@W+gYUG^A1fa|ef{;;(;`Dw_JCb6dY^yJdHjC)e9>KZ^VBB4
z{93i6N5b<!iqX>|%PHrc8;4Kw*c<o0!ltg2-A7G$|NZ}a-qhuHX3yAu{PDpPM^!!m
jL#Nm6_~VI>?T!92^7ip(me+*^gW}lJ)z4*}Q$iB}!$SPn

literal 0
HcmV?d00001

diff --git a/Telegram/Resources/icons/voice_lock/recorded_delete@2x.png b/Telegram/Resources/icons/voice_lock/recorded_delete@2x.png
new file mode 100644
index 0000000000000000000000000000000000000000..8bebe1a61bb735034f539f35c930514b00020427
GIT binary patch
literal 1112
zcmV-e1gHCnP)<h;3K|Lk000e1NJLTq001xm001xu0ssI2*kEqZ00001b5ch_0Itp)
z=>Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@
z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NF!bwCyR9Fe^Sh-3pK@`<-#~s84
zMMW@hK@`+L6eln;6*My*4a5&{iOdZI5x>B|z(5R4L<B)a6c;cuFmMDHP{D<ndGGix
z9E$3ty6w^6(H%5Z_ue}9)T!>;GBUy^R3KE~OM#J*k?rm6$H&J%mgnW=Wp{UXVqzjQ
zGxL|kT3%kZ(6BLVZEa;`W%=dgyAz|MqibtxlIHsQ8X{>ai;Ih!o0~<RnVFe|g#|4j
z&DFiVJyBzPeB22-8jXH_ej+R$k2}Sr#~z79USD64>EhzT9dTo0LwMKL*1AV(-iTIr
zuND*(6c!dD=KlWPJ(2+x6&3E0n%Djt%FD}JTwLtw>CvL0P*+zMjHy)W{rz3@s@2@w
z-0bXZSS~Lw-`?I-7s`i+htt#3A0Ho@-LX16JS>9$+#4wz(_0<o<>dzl2S3LK;ALfH
z*1Vx5RNz+>=<DliX=w?bs=mH{XlN)Xj{N_aFd7;ff?#1fy}P@^Z#y+L<&I;RsYM&i
za{c}N8c;_^M{8@VX2i@nI5=2URi)Y6+uJKED`7N>Z6L(ev8JX*&C%W6eR_I&etzE8
z)~4DgCntAyc3{uR$x-dq)zwmkPO9Bn_DYC|0@2yoi30W*0|NspDC9vE>sMV}ooZ))
zZf|c@yR~c}#P&oKi09{LDU_C$s-Pt$B~rz{QMI!<0adSB4oZj&1e8@W7CQ;?8&FB8
zWdot7rzcsHwIO3ZG7wM^3>yJ#(9|?+9#kQKN}t$8N#jIF_|Z#F(j32i(2p93(kCp=
z`T6-wz?7Oo?t~s59yH|$$bw<W5XVuc>@eCA!cSLJRHS+(4eT-r5lZZ~Z!;l|x;kpM
zgmB)F!v?*Q60?9zON6qeBF1kL!WI_0eL$$cJ%>*t#Ga&w5OpT5bVag~1dJ?eggDlO
z5(L|~4~2*;K!Tn~B#4_#CRG>h>`8hQA_{RO)BODW(b3WD>?{*sU0vanje|YjG@`(U
zgi^h}%Q)%DZhv!g(|6;44RE3o1X=0aLCiyjsSKYmP_!X)$5p+)6fzUSS0cwy;;PBK
zl-V#nLxz+mCnwMx%y6Rh!a$3`B?+3#G@-eyRkK29v%kNOH!gd|#>T7=?iOsx8yg!D
zBOva<@O~Y?jnDe}dMp-0*y`$PZ*T9}*%^9lY=wZPrY2maNhCnLH1Qg<va$ja-+pla
zhwZl4WM1O17|qSiVWZ=h6K2b$r6o?{#$5K-F;2~J34!Yv41~w--rU?AA0Hd{Ku+mG
ed#J#Fp}=2f*X$qSd%yPp0000<MNUMnLSTX}EABY}

literal 0
HcmV?d00001

diff --git a/Telegram/Resources/icons/voice_lock/recorded_delete@3x.png b/Telegram/Resources/icons/voice_lock/recorded_delete@3x.png
new file mode 100644
index 0000000000000000000000000000000000000000..db0067c534ed82bdcff4bcd50e337d90956e69fb
GIT binary patch
literal 1556
zcmV+v2J88WP)<h;3K|Lk000e1NJLTq002k;002k`0ssI2+K(g<00001b5ch_0Itp)
z=>Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@
z1ONa40RR91NT34%1ONa40RR91NB{r;0FW_8?*IS=en~_@RA>e5T1zi2OB6odZ{m?4
zh<Jwq5+ovFf-u3vfW*+m$b<<oHu4upj5s5~NMa(0NJu>584SJRO}x3^J$<vzm(up`
zZoT`Q8@)%X)?T%~^{rY}T~*ybez-q318xS~47eHi1{v`41;?PEpsK2>{QUg*_;^1*
zeTet>_nVuW{r&yv>1hbQ2*XaF{{H@*ot>wrr-<9&@BaRNU|=9BD$33{?DZ8A5;8tM
zZjg^+w!Xfenwn~FJhpio8X8i<HPu{NT0&~<ibj6|Snyt1SvfN^!yR{ac6N7n!HtzH
zr=+A578V8s1c<V$s|#f*&A&nz85xoB!L(^?Y%~&9QBl#s!GUzdT4JP^qc*%?^!D}|
z$F8ld6=D!FGc%3%^|@teXeb9)Qc_|ZGB7ak;o(8L<2hlxuLnzi5C0ke3&hy?;^Kmv
zp5>R9m)qN0u~A%!-mDO4qyrwfd3kvrmMrBd)YR03$~59xvtiFUIXP!%XP@<be0<#A
z-hO_5{>;FH9&6X$-ky}o9~Te|Lh?$$dntBwbo^Bl8o?kW8;D(9U3vB5uz@`xMMOl5
zjg6tW|9BB10EB^H5R${Gbm80#xEXLW@EtP{78b^bD4(VPS0<5>kv@g^&v}1;KelSv
z&ehk~`x-AhJ6k)dPE1S$2M7BQE-Ncb9@AS}TRuek<$8E{Sm0V)TTQ`v-z&1a*w|QU
z=H}+&C?!orz+azTyon_a4I=9b1qB77XJ}D+5X=!fIyw?xh&wScQJO5Yx3}X+hF?od
z3+qJ&?Vmq?Vt=%{x|)%ZA$mnfPEHmLLyOWj0Xcqqdz11gEiL`%8cU0uh%YZMb(a|s
z7AJ#)gCCthgDXl*W-+U%sL(b0OlfY!v}HqlaYjXnnvsKJOiYaKsG^L;J}D_lWEK(<
z61db44-ffYud+E}aK`|;F<oC@Gir2nG&ePwm5RL>D_JvJieDn8U~4hm-QA&FB|he$
zt29CkC5<aR>25?YO^h?AOANDuUou!G#^u1EMp(hdG%>HG2&X&<mWgrJ7}N=3jLmp1
zv469{S>WtAU5Z@px=T5N<I9%+Whvr$6&DxBY1U*O1U$+<8ZcYV0$XyOJ3Yq`2g6HP
zOpG~*0V~;93?MjNinwRTlS(46Kn#~>ywB1lhC6e<V{>A}K#5^EufmpN<-ySm6b&1T
z2PI#OC^nu2)6X%uV;Cg@4UA(|Xuv?xu(8C-ZgB{}K|%g9C|DuJ3nND$kQ}Q*12(L|
z=8NAUi3ctOh7$v?f)!#sVjO{h87Br-*n&w%5jbBACk9@HuM*=31dLa3UIR1(*07~?
zhrn)~8!<-W2qdzVm6fNbC#{JOI7F`ro12@m{a;*MRQ}bPTnN@iOxLX!WnEpJ9Bgr*
z(6z=_1M~Cqcr&7V@^5QvTUb~a9Uaxp3mi^OO$kI_U!N|7{YreCA*c{=7hpTa+S-~x
z;0DEle=Bp0OA(NtW!*wF(uGS=ffF0gf)!$TagJC0;+KOE<yQn;8R4@z@nCI^Ss{iZ
zIX*rXVB1Fwnc_Q1=ZJw4gZYA(WDV=*$;pW{jSE&rOd*DQZ{v7IEOBKoW=h$M-m74d
z*v7_&xNc}@u=kBYb91veXCP7fP^hV?k>wZy-X4U9hkqRpQ>(kXn?3jR^jLFi@q0x?
zu)MrnTwE-uSd?dHXV1^iMPpXLR;#S6j4z4t^v8$A)|Pv-=)djp(GzxhoLzfmEE@(t
zdi))eT@pS9!)e-HKAJbMa&9N3n~wDK^vTIdd%0l`U0z<U3ujzuagHefm~7S6)oE#I
zU)eX}RSu@p+}s?#Eq;A{RUq7^n*lciZU)>8xEc6P8TbbrS#tjwMqW(-0000<MNUMn
GLSTZE7@*kz

literal 0
HcmV?d00001

diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style
index a7f94d301..2ad067367 100644
--- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style
+++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style
@@ -1063,12 +1063,17 @@ historyRecordVoiceShowDuration: 120;
 historyRecordVoiceDuration: 120;
 historyRecordVoice: icon {{ "chat/input_record", historyRecordVoiceFg }};
 historyRecordVoiceOver: icon {{ "chat/input_record", historyRecordVoiceFgOver }};
+historyRecordVoiceOnceBg: icon {{ "voice_lock/audio_once_bg", historySendIconFg }};
+historyRecordVoiceOnceBgOver: icon {{ "voice_lock/audio_once_bg", historySendIconFgOver }};
+historyRecordVoiceOnceFg: icon {{ "voice_lock/audio_once_number", windowFgActive }};
+historyRecordVoiceOnceFgOver: icon {{ "voice_lock/audio_once_number", windowFgActive }};
+historyRecordVoiceOnceInactive: icon {{ "chat/audio_once", windowSubTextFg }};
 historyRecordVoiceActive: icon {{ "chat/input_record_filled", historyRecordVoiceFgActiveIcon }};
 historyRecordSendIconPosition: point(2px, 0px);
 historyRecordVoiceRippleBgActive: lightButtonBgOver;
 historyRecordSignalRadius: 5px;
 historyRecordCancel: windowSubTextFg;
-historyRecordCancelActive: windowActiveTextFg;
+historyRecordCancelActive: historySendIconFg;
 historyRecordFont: font(13px);
 historyRecordDurationSkip: 12px;
 historyRecordDurationFg: historyComposeAreaFg;
@@ -1111,16 +1116,16 @@ historyRecordLockArrow: icon {{ "voice_lock/voice_arrow", historyToDownFg }};
 historyRecordLockRippleMargin: margins(6px, 6px, 6px, 6px);
 
 historyRecordDelete: IconButton(historyAttach) {
-	icon: icon {{ "info/info_media_delete", historyComposeIconFg }};
-	iconOver: icon {{ "info/info_media_delete", historyComposeIconFgOver }};
+	icon: icon {{ "voice_lock/recorded_delete", historyComposeIconFg }};
+	iconOver: icon {{ "voice_lock/recorded_delete", historyComposeIconFgOver }};
 	iconPosition: point(10px, 11px);
 }
 historyRecordWaveformRightSkip: 10px;
-historyRecordWaveformBgMargins: margins(5px, 7px, 5px, 7px);
+historyRecordWaveformBgMargins: margins(5px, 8px, 5px, 9px);
 
 historyRecordWaveformBar: 3px;
 
-historyRecordLockPosition: point(1px, 35px);
+historyRecordLockPosition: point(1px, 22px);
 
 historyRecordCancelButtonWidth: 100px;
 historyRecordCancelButtonFg: lightButtonFg;
diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp
index d878583eb..1f812250b 100644
--- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp
+++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp
@@ -312,49 +312,16 @@ TTLButton::TTLButton(
 
 		Ui::RippleButton::paintRipple(p, _rippleRect.x(), _rippleRect.y());
 
-		const auto innerRect = QRectF(inner)
-			- st::historyRecordLockMargin * 2;
-		auto hq = PainterHighQualityEnabler(p);
-
-		p.setFont(st::semiboldFont);
-		p.setPen(_st.lock.fg);
-		p.drawText(inner, _text, style::al_center);
-
-		const auto penWidth = st::historyRecordTTLLineWidth;
-		auto pen = QPen(_st.lock.fg);
-		pen.setJoinStyle(Qt::RoundJoin);
-		pen.setCapStyle(Qt::RoundCap);
-		pen.setWidthF(penWidth);
-
-		p.setPen(pen);
-		p.setBrush(Qt::NoBrush);
-		p.drawArc(innerRect, arc::kQuarterLength, arc::kHalfLength);
-
-		{
-			p.setClipRect(innerRect
-				- QMarginsF(
-					innerRect.width() / 2,
-					-penWidth,
-					-penWidth,
-					-penWidth));
-			pen.setStyle(Qt::DotLine);
-			p.setPen(pen);
-			p.drawEllipse(innerRect);
-			p.setClipping(false);
-		}
-
 		const auto activeProgress = _activeAnimation.value(
 			!Ui::AbstractButton::isDisabled() ? 1 : 0);
+
+		p.setOpacity(1. - activeProgress);
+		st::historyRecordVoiceOnceInactive.paintInCenter(p, inner);
+
 		if (activeProgress) {
 			p.setOpacity(activeProgress);
-			pen.setStyle(Qt::SolidLine);
-			pen.setBrush(st::windowBgActive);
-			p.setPen(pen);
-			p.setBrush(pen.brush());
-			p.drawEllipse(innerRect);
-
-			p.setPen(st::windowFgActive);
-			p.drawText(innerRect, _text, style::al_center);
+			st::historyRecordVoiceOnceBg.paintInCenter(p, inner);
+			st::historyRecordVoiceOnceFg.paintInCenter(p, inner);
 		}
 
 	}, lifetime());

From c686ac860397f1117e91e0c2fa65d289bf15e287 Mon Sep 17 00:00:00 2001
From: 23rd <23rd@vivaldi.net>
Date: Sun, 14 Jan 2024 05:04:44 +0300
Subject: [PATCH 46/73] Slightly improved style of ttl badge in voice messages.

---
 .../Resources/icons/chat/mini_media_once.png  | Bin 0 -> 561 bytes
 .../icons/chat/mini_media_once@2x.png         | Bin 0 -> 1011 bytes
 .../icons/chat/mini_media_once@3x.png         | Bin 0 -> 1465 bytes
 .../view/media/history_view_document.cpp      |  41 ++++--------------
 Telegram/SourceFiles/ui/chat/chat.style       |   5 +++
 Telegram/SourceFiles/ui/chat/chat_style.cpp   |   6 +++
 Telegram/SourceFiles/ui/chat/chat_style.h     |   1 +
 7 files changed, 21 insertions(+), 32 deletions(-)
 create mode 100644 Telegram/Resources/icons/chat/mini_media_once.png
 create mode 100644 Telegram/Resources/icons/chat/mini_media_once@2x.png
 create mode 100644 Telegram/Resources/icons/chat/mini_media_once@3x.png

diff --git a/Telegram/Resources/icons/chat/mini_media_once.png b/Telegram/Resources/icons/chat/mini_media_once.png
new file mode 100644
index 0000000000000000000000000000000000000000..8984491b7ae14c859e3ee0d51a336e8a75197115
GIT binary patch
literal 561
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|jKx9jP7LeL$-D$|Tv8)E(|mmy
zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uuz(rC1}QWNE&K#jHrvz1F+@YO
zc9Q>U#z2W<{f?cV^pEJIEMC@;vu>$}PQ>{slb$d~XlTnHXgZ&ed%8&|^28~xlU^Nj
zr%duFdEY$K*!cUkTJz6)_CL2Yes7t+#U=J&g2D9DoJ|QvGpC(idMl*-Vn&J4%$ohL
zORTQGF8xuqd++6!N0JOnr%ec2S+f8BMD6LP^R~~vmA1LkZoa)-zt>VH#gl29&p$6V
zkl3K39qcLDdn|1A*8A^GrOtLQbl-e4M@OuCN~Nu9v=h(aj!h9dNpD<~6eCq+`1;)z
zKmPcl#7c9C)AW-m*It_ni4@A1&p!L;qlH<qg^b4ft66%}yFdP{=|0*N)KRwk?)&dk
zpDf;g?`Xoj_{aZxgPJz*eNIqy{ofI{K7DJH=fZ#kyF#><_OLNEe*XF9W5tw!D{Re)
zyYId$vz~o+*_NlpDlAgbQ}32o_3oDGKOVO}eA%QT3mJ>Ux8E+i{#w<|!)`wNA&IM5
zp*wl5?}}QhH=X<Z=b~SWl_s7jusBi|aQ*exn>kI7*Y>(8i*Q~1^85P&S;e~j_A`AR
jDPMlM<@Q@GnZSPx`>!r6lAHQj7!+Neu6{1-oD!M<P^aoc

literal 0
HcmV?d00001

diff --git a/Telegram/Resources/icons/chat/mini_media_once@2x.png b/Telegram/Resources/icons/chat/mini_media_once@2x.png
new file mode 100644
index 0000000000000000000000000000000000000000..f8514d3af5b634e8db539d644ce7ac5860077641
GIT binary patch
literal 1011
zcmV<P0}T9$P)<h;3K|Lk000e1NJLTq001BW001Be0ssI2{21+{00001b5ch_0Itp)
z=>Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@
z1ONa40RR91AfN*P1ONa40RR91AOHXW0IY^$^8f$?T}ebiR7ee_Ryj*+K@^Vn#YkMh
z;D%caQ3MM?1wj$DQ3O%Yq!5xO#6J+R6R}YPDN<O75JbUJENn!<t+2GP5-SB2w<Ic(
zMH8d%eeWF{ZgOXCuD%pEGv|C~nKNgZUpmb{lgVVYT8&2I%gc*qp=`jmwzip>nd9T*
zx3{<7|9&4IA45Y!+2l0v2L=Y9my~9FXJ=>S3dYCBr>3Tgii+e!wRKHR&Eer;hEi;w
zo}NxmPuJJiD=I3O{MgtSA+N5k1_uWjUAXA&?R|K7VA^A`*viU^&1Mrh*wxjQN~IVc
zM-i$9V{~-%{r#QL$z*b2VL|S!NOELk<o^Di+3<S3d}MojJNzZw&CN|`XD1)Wqawj}
zc6LZM9*^hd=4PTwOH1$W?g(*qcBaMg3>8qiw6uhCudS_Ru>Nir7Z*u;Fc?I#a}e4{
zs1gpasi}#DK5HiE^?Hs9by6S@AosVow^fj^y}7yh`ud6_HW&;l$TXHDNeBxW=;-K3
z<9%)N^76K}wg^Mt;o;#gX10GMKFOd`a&mIGaH^*3>+9X!T>?8C4la_21qB7m%gYA`
z2W4etx`~Mil0goLWDxi6?rxl8etuqy0jJYRFrUw-ud1pdz31oWA_J(^qoX4cS4l_8
z5Yb|>NY&NVv_Wwxp+BR9!(kFcc}bt2pR~bk|4f}yid8a>BHiBJ5`rB?DezrNd3iaB
zpl?bnG7Sw4-&G{SN25_<ot&KLF;!@TIfXRE?Tf^;(@~FZw;MN%4y1!pM2WJhr>93<
z%k=a#Au%D;k!f7k>Cgkx7>Z3xOUvWqBT5qeS-}(&>g($hiG)Vi(1m`IZ+^d@3_L$S
zqq&NmQRARjqlOzB8#$oj;^NE8OR{lsaZy)S#{sF5&=K$?fCj{{u&|JWZEkMHi;8TZ
z6#M%6I3NKD;}&j_Y$OuN&(G%r@fMOluvS-Bk!pOXj5;|v$>tUA(r&lQn2IHQL6VR!
z_+rLxY;3q(F4Q@<nYXsKqS+800f37Nl?noyI4XtNU<(+-NWkj>FEb`oTU#3ng@D=L
z-#44hjGldgXA33<(=K1k&CSW>D=RB8Ex6~C!}x17D38apx3`C}N})iM`}_O(=>Mdm
h2_rZqB_-d|^9MrUT|a$+sx$xq002ovPDHLkV1g{Rzg_?U

literal 0
HcmV?d00001

diff --git a/Telegram/Resources/icons/chat/mini_media_once@3x.png b/Telegram/Resources/icons/chat/mini_media_once@3x.png
new file mode 100644
index 0000000000000000000000000000000000000000..3900061be980c1112d770648672640ff6bd25f14
GIT binary patch
literal 1465
zcmV;q1xEUbP)<h;3K|Lk000e1NJLTq001xm001xu0ssI2*kEqZ00001b5ch_0Itp)
z=>Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@
z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NHBS}O-R9Fe!SxYFbQ53$8Lmqh(
z7kP$43CSbME98}dVt|pMC=5hmV1Ob9C}e;_Vxp7?p}dNa0VB`6Bk%XU-?`_u@3#N*
zpZ{?=-R>E!wf9=zT6_O%ueJ6*-@g50KRtQ=xVX5)#Kbr|JAeQ6b8v8Yd3kAQXc!q8
zkq0P8OHWTPEG(?1re<wz?T4n{^YgQZhld<!89M3d>Few3n%wv>mXVRc1;<8aXJ>nR
zdvmAcjKsyoEi5d2Fw4VSU0vDR+cP$TgM;tx?x6AZ_SV$YWNK>42uNU!jg7mzyLqgu
zhTq@c&(6-Ub7Ny;t*x#8{{CF_j*bo*VP|J2At8Y)COqQm>bkPBLSqqndwaXFu`xC_
z*3i&U5U8%Mjwq@LETg!%SWxkU0F0fVpEE#UafpI~f<A}}F?M!#%gf7LsQUVPoDw1J
zU-O}%q4)Rq48-*GG>(`keU6ZUfk8z@#pB~6gNcyh$Z`hU+}uu1PH0W+a8*^6*wt_V
z$$*cK&&I|EgKTMONdz<y78Vwpo13)G<>h5cN{R+iSwk>CGc!Z*k+%d6-N3*Akp^*l
zdn+|!1pPEJGV1N^1vtDZC@9b-9~l`*d-MAGnwpxbP2n%*P;acPth50jM3$D8Xorf5
zinJ+w^;}L)4($z6v(zXyF)`8C*Z(v>nK~u@gv0NwtSqEZ9L9r#103&9@X5=|i{75T
zzCMwl$mSCh6SOX99B+~8ykcc#Wi)71R1~i$HG%qneSJ+Us_F9ba!N`TNr&_C@v%ao
z5QM=Q!MWvngOS?WT0wPMz}?-wr>6%+DLy`4HI7<C<F>W6(b9ahxw$#A6cisG9!g3|
z=p{*V2q8#PF;)oYl)k>cwAJzPahm0$@v$NzB2a^ehKBa{_cJpy`6RWEBX<)AQ1Sq%
zWMUBL+9Aj+4kmttl}Dh8(~9_93L6_6dg$=*kY>M{etLSkxw#=$R1TS$nGqgcCQ3bF
zbwi-%B!+BlZ50Si!Xw1+Bdl%+G|I#fHcUb97!EvcSY%oU1lEtwL=55kDCix-gp-pK
znbrY;e5O<?iJ{ZeQ^nTS7U4ml8qn-l)2M)S%<S#$DUg>iN(?6@B@tQ&1g>(pX{imt
z6I`cM^z7^`N!Aw^7xW&A^l5&6o*+od)WAp6lAN53tF5}7Rh6yb;bCIf+S)ogI-0N|
z33}UUT8uh!&FJW8e}6wNxR7TZ!cbvhp%@@q1~)IlqOK8IKEd4OeivTRcmV+c>R6nb
znxgZHXaJ5NUM)xvfIyLw(T_4!h+{<J02W94R9#&y5#Y1M0|NsQU$jrTxw)ThM1_Z@
zr@g(stE<b;&rblqv$K<S2$e;~iPLT)BqRixMQsIn8I7JcdBo%5;v(%68p6E1JZ(Bs
z=CC}b4Hxd;@dSo9CE6#@EiEl*=cJfsWl^?EOG}vt5Tt3OJyw*<<I_Tv@d@WHEiH{J
zEM^2jGdDNK0HP(AkX-OExVyW{K!C-KHZd_#Qbxd*qoH7+@Td|V9xi4ve`L_6OioTR
zKx!6`Yq+?e`Zzi|3ObjcpU;3GB#=n|SQ1ac7~aCw9RdSNSY(Q$qodW;)xp6*yq|Ev
z@rX-=LEsIPZkoCB!XsD>ZgDgyAC2mWCuuHXWo0E!IHC|$ojWUY1fQJm3)22+d^neB
zQjD6XrzhTx@W7z)9?iEyVvvc9%sU1$EFwnz=L_xycoD;&FU`%(INk*MKOues(1;fM
Tj0eD$00000NkvXXu0mjfuA7-D

literal 0
HcmV?d00001

diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_document.cpp
index cf5560009..f6e60f877 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_document.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_document.cpp
@@ -59,37 +59,6 @@ constexpr auto kAudioVoiceMsgUpdateView = crl::time(100);
 		st::dialogsTTLBadgeSize);
 }
 
-void DrawCornerBadgeTTL(QPainter &p, const QColor &fg, const QRect &ttlRect) {
-	p.save();
-	auto hq = PainterHighQualityEnabler(p);
-
-	const auto innerRect = QRectF(ttlRect - st::dialogsTTLBadgeInnerMargins);
-	const auto ttlText = u"1"_q;
-
-	p.setFont(st::dialogsScamFont);
-	p.setPen(fg);
-	p.drawText(innerRect, ttlText, style::al_center);
-
-	constexpr auto kPenWidth = 1.5;
-
-	const auto penWidth = style::ConvertScaleExact(kPenWidth);
-	auto pen = QPen(fg);
-	pen.setJoinStyle(Qt::RoundJoin);
-	pen.setCapStyle(Qt::RoundCap);
-	pen.setWidthF(penWidth);
-
-	p.setPen(pen);
-	p.setBrush(Qt::NoBrush);
-	p.drawArc(innerRect, arc::kQuarterLength, arc::kHalfLength);
-
-	p.setClipRect(innerRect
-		- QMarginsF(innerRect.width() / 2, -penWidth, -penWidth, -penWidth));
-	pen.setStyle(Qt::DotLine);
-	p.setPen(pen);
-	p.drawEllipse(innerRect);
-	p.restore();
-}
-
 [[nodiscard]] HistoryView::TtlPaintCallback CreateTtlPaintCallback(
 		std::shared_ptr<rpl::lifetime> lifetime,
 		Fn<void()> update) {
@@ -797,7 +766,15 @@ void Document::draw(
 				_animation->radial.draw(q, rinner, st::msgFileRadialLine, stm->historyFileRadialFg);
 			}
 			if (hasTtlBadge) {
-				DrawCornerBadgeTTL(q, stm->historyFileRadialFg->c, ttlRect);
+				{
+					auto hq = PainterHighQualityEnabler(q);
+					auto pen = stm->msgBg->p;
+					pen.setWidthF(style::ConvertScaleExact(1.5));
+					q.setPen(pen);
+					q.setBrush(Qt::NoBrush);
+					q.drawEllipse(ttlRect);
+				}
+				stm->historyVoiceMessageTTL.paintInCenter(q, ttlRect);
 			}
 		};
 		if (_data->isSongWithCover() || !usesBubblePattern(context)) {
diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style
index 378393643..1b463cf50 100644
--- a/Telegram/SourceFiles/ui/chat/chat.style
+++ b/Telegram/SourceFiles/ui/chat/chat.style
@@ -513,6 +513,11 @@ msgWaveformSkip: 1px;
 msgWaveformMin: 3px;
 msgWaveformMax: 17px;
 
+historyVoiceMessageInTTL: icon {{ "chat/mini_media_once", historyFileInIconFg }};
+historyVoiceMessageInTTLSelected: icon {{ "chat/mini_media_once", historyFileInIconFgSelected }};
+historyVoiceMessageOutTTL: icon {{ "chat/mini_media_once", historyFileOutIconFg }};
+historyVoiceMessageOutTTLSelected: icon {{ "chat/mini_media_once", historyFileOutIconFgSelected }};
+
 historyTranscribeSkip: 10px;
 historyTranscribeSize: size(28px, 22px);
 historyTranscribeRadius: 4px;
diff --git a/Telegram/SourceFiles/ui/chat/chat_style.cpp b/Telegram/SourceFiles/ui/chat/chat_style.cpp
index 4acf416ba..88f5ddd38 100644
--- a/Telegram/SourceFiles/ui/chat/chat_style.cpp
+++ b/Telegram/SourceFiles/ui/chat/chat_style.cpp
@@ -536,6 +536,12 @@ ChatStyle::ChatStyle(rpl::producer<ColorIndicesCompressed> colorIndices) {
 		&MessageImageStyle::historyPageEnlarge,
 		st::historyPageEnlarge,
 		st::historyPageEnlargeSelected);
+	make(
+		&MessageStyle::historyVoiceMessageTTL,
+		st::historyVoiceMessageInTTL,
+		st::historyVoiceMessageInTTLSelected,
+		st::historyVoiceMessageOutTTL,
+		st::historyVoiceMessageOutTTLSelected);
 
 	updateDarkValue();
 }
diff --git a/Telegram/SourceFiles/ui/chat/chat_style.h b/Telegram/SourceFiles/ui/chat/chat_style.h
index 11890a061..dca47f0f8 100644
--- a/Telegram/SourceFiles/ui/chat/chat_style.h
+++ b/Telegram/SourceFiles/ui/chat/chat_style.h
@@ -91,6 +91,7 @@ struct MessageStyle {
 	style::icon historyTranscribeIcon = { Qt::Uninitialized };
 	style::icon historyTranscribeLock = { Qt::Uninitialized };
 	style::icon historyTranscribeHide = { Qt::Uninitialized };
+	style::icon historyVoiceMessageTTL = { Qt::Uninitialized };
 	std::array<
 		std::unique_ptr<Text::QuotePaintCache>,
 		kColorPatternsCount> quoteCache;

From a2c0491ae033483e99531ffda99915c44fad7a32 Mon Sep 17 00:00:00 2001
From: 23rd <23rd@vivaldi.net>
Date: Sun, 14 Jan 2024 05:42:21 +0300
Subject: [PATCH 47/73] Added phrases to ttl viewer widget for video messages.

---
 Telegram/Resources/langs/lang.strings         |  2 ++
 .../chat_helpers/ttl_media_layer_widget.cpp   | 24 +++++++++++++------
 2 files changed, 19 insertions(+), 7 deletions(-)

diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index cec8bf36e..8c1560cd1 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -1722,6 +1722,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 "lng_ttl_voice_tooltip_in" = "This voice message can only be played once.";
 "lng_ttl_voice_tooltip_out" = "This message will disappear once **{user}** plays it once.";
 "lng_ttl_voice_close_in" = "Delete and close";
+"lng_ttl_round_tooltip_in" = "This video message can only be played once.";
+"lng_ttl_round_tooltip_out" = "This message will disappear once **{user}** plays it once.";
 
 "lng_profile_add_more_after_create" = "You will be able to add more members after you create the group.";
 "lng_profile_camera_title" = "Capture yourself";
diff --git a/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp b/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp
index e6a71d509..120f89f9e 100644
--- a/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp
+++ b/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "chat_helpers/ttl_media_layer_widget.h"
 
 #include "base/event_filter.h"
+#include "data/data_document.h"
 #include "data/data_session.h"
 #include "editor/editor_layer_widget.h"
 #include "history/history.h"
@@ -123,6 +124,11 @@ PreviewWrap::PreviewWrap(
 	_style.get(),
 	[=] { update(elementRect()); })) {
 
+	const auto isRound = _item
+		&& _item->media()
+		&& _item->media()->document()
+		&& _item->media()->document()->isVideoMessage();
+
 	std::move(
 		theme
 	) | rpl::start_with_next([=](std::shared_ptr<Ui::ChatTheme> theme) {
@@ -174,13 +180,17 @@ PreviewWrap::PreviewWrap(
 
 	{
 		auto text = item->out()
-			? tr::lng_ttl_voice_tooltip_out(
-				lt_user,
-				rpl::single(
-					item->history()->peer->name()
-				) | rpl::map(Ui::Text::RichLangValue),
-				Ui::Text::RichLangValue)
-			: tr::lng_ttl_voice_tooltip_in(Ui::Text::RichLangValue);
+			? (isRound
+				? tr::lng_ttl_round_tooltip_out
+				: tr::lng_ttl_voice_tooltip_out)(
+					lt_user,
+					rpl::single(
+						item->history()->peer->shortName()
+					) | rpl::map(Ui::Text::RichLangValue),
+					Ui::Text::RichLangValue)
+			: (isRound
+				? tr::lng_ttl_round_tooltip_in
+				: tr::lng_ttl_voice_tooltip_in)(Ui::Text::RichLangValue);
 		const auto tooltip = Ui::CreateChild<Ui::ImportantTooltip>(
 			this,
 			object_ptr<Ui::PaddingWrap<Ui::FlatLabel>>(

From 46ddc7364c4548693c9bcedc30ec9e5ad771434f Mon Sep 17 00:00:00 2001
From: 23rd <23rd@vivaldi.net>
Date: Sun, 14 Jan 2024 12:43:32 +0300
Subject: [PATCH 48/73] Fixed possible crash on deleting of media message with
 ttl.

---
 .../history/view/media/history_view_document.cpp      |  8 +++++---
 .../history/view/media/history_view_gif.cpp           | 11 +++++++----
 2 files changed, 12 insertions(+), 7 deletions(-)

diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_document.cpp
index f6e60f877..541e0c463 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_document.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_document.cpp
@@ -309,18 +309,20 @@ Document::Document(
 			}, *lifetime);
 			_drawTtl = CreateTtlPaintCallback(lifetime, [=] { repaint(); });
 		} else if (!_parent->data()->out()) {
+			const auto &data = &_parent->data()->history()->owner();
 			_parent->data()->removeFromSharedMediaIndex();
 			setDocumentLinks(_data, realParent, [=] {
 				_openl = nullptr;
 
 				auto lifetime = std::make_shared<rpl::lifetime>();
 				TTLVoiceStops(fullId) | rpl::start_with_next([=]() mutable {
-					const auto item = _parent->data();
 					if (lifetime) {
 						base::take(lifetime)->destroy();
 					}
-					// Destroys this.
-					ClearMediaAsExpired(item);
+					if (const auto item = data->message(fullId)) {
+						// Destroys this.
+						ClearMediaAsExpired(item);
+					}
 				}, *lifetime);
 
 				return false;
diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp
index 6176bf050..51150dace 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp
@@ -150,17 +150,20 @@ Gif::Gif(
 			_drawTtl = CreateTtlPaintCallback([=] { repaint(); });
 		}
 		const auto fullId = _realParent->fullId();
+		const auto &data = &_parent->data()->history()->owner();
+		const auto isOut = _parent->data()->out();
 		_parent->data()->removeFromSharedMediaIndex();
 		setDocumentLinks(_data, realParent, [=] {
 			auto lifetime = std::make_shared<rpl::lifetime>();
 			TTLVoiceStops(fullId) | rpl::start_with_next([=]() mutable {
-				const auto item = _parent->data();
 				if (lifetime) {
 					base::take(lifetime)->destroy();
 				}
-				if (!item->out()) {
-					// Destroys this.
-					ClearMediaAsExpired(item);
+				if (!isOut) {
+					if (const auto item = data->message(fullId)) {
+						// Destroys this.
+						ClearMediaAsExpired(item);
+					}
 				}
 			}, *lifetime);
 

From ad6890e7ddc3197f14185f536c9631400e8cca76 Mon Sep 17 00:00:00 2001
From: 23rd <23rd@vivaldi.net>
Date: Sun, 14 Jan 2024 12:59:04 +0300
Subject: [PATCH 49/73] Slightly improved progress radial for video messages
 with ttl.

---
 .../history/view/media/history_view_gif.cpp         | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)

diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp
index 51150dace..ca29639ec 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp
@@ -582,18 +582,19 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
 			const auto value = playback->value();
 			if (value > 0.) {
 				auto pen = st->historyVideoMessageProgressFg()->p;
-				auto was = p.pen();
+				const auto was = p.pen();
 				pen.setWidth(st::radialLine);
 				pen.setCapStyle(Qt::RoundCap);
 				p.setPen(pen);
 				p.setOpacity(st::historyVideoMessageProgressOpacity);
 
-				auto from = arc::kQuarterLength;
-				auto len = -qRound(arc::kFullLength * value);
-				auto stepInside = st::radialLine / 2;
+				const auto from = arc::kQuarterLength;
+				const auto len = std::round(arc::kFullLength
+					* (inTTLViewer ? (1. - value) : -value));
+				const auto stepInside = st::radialLine / 2;
 				{
-					PainterHighQualityEnabler hq(p);
-					p.drawArc(rthumb.marginsRemoved(QMargins(stepInside, stepInside, stepInside, stepInside)), from, len);
+					auto hq = PainterHighQualityEnabler(p);
+					p.drawArc(rthumb - Margins(stepInside), from, len);
 				}
 
 				p.setPen(was);

From 5273fbf57b3bb2178deb4a59c3931419811b7293 Mon Sep 17 00:00:00 2001
From: 23rd <23rd@vivaldi.net>
Date: Sun, 14 Jan 2024 13:36:24 +0300
Subject: [PATCH 50/73] Slightly improved progress radial for voice messages
 with ttl.

---
 .../view/media/history_view_document.cpp      | 35 ++++++++++++++++---
 1 file changed, 31 insertions(+), 4 deletions(-)

diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_document.cpp
index 541e0c463..2c963c344 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_document.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_document.cpp
@@ -186,7 +186,8 @@ void PaintWaveform(
 		const PaintContext &context,
 		const VoiceData *voiceData,
 		int availableWidth,
-		float64 progress) {
+		float64 progress,
+		bool ttl) {
 	const auto wf = [&]() -> const VoiceWaveform* {
 		if (!voiceData) {
 			return nullptr;
@@ -198,11 +199,14 @@ void PaintWaveform(
 		}
 		return &voiceData->waveform;
 	}();
+	if (ttl) {
+		progress = 1. - progress;
+	}
 	const auto stm = context.messageStyle();
 
 	// Rescale waveform by going in waveform.size * bar_count 1D grid.
 	const auto active = stm->msgWaveformActive;
-	const auto inactive = stm->msgWaveformInactive;
+	const auto inactive = ttl ? stm->msgBg : stm->msgWaveformInactive;
 	const auto wfSize = wf
 		? int(wf->size())
 		: ::Media::Player::kWaveformSamplesCount;
@@ -755,8 +759,28 @@ void Document::draw(
 			: nullptr;
 
 		const auto paintContent = [&](QPainter &q) {
+			constexpr auto kPenWidth = 1.5;
 			if (_drawTtl) {
 				_drawTtl(q, inner, context.st->historyFileInIconFg()->c);
+
+				const auto voice = Get<HistoryDocumentVoice>();
+				const auto progress = (voice && voice->playback)
+					? voice->playback->progress.current()
+					: 0.;
+
+				if (progress > 0.) {
+					auto pen = stm->msgBg->p;
+					pen.setWidthF(style::ConvertScaleExact(kPenWidth));
+					pen.setCapStyle(Qt::RoundCap);
+					q.setPen(pen);
+
+					const auto from = arc::kQuarterLength;
+					const auto len = std::round(arc::kFullLength
+						* (1. - progress));
+					const auto stepInside = pen.widthF() * 2;
+					auto hq = PainterHighQualityEnabler(q);
+					q.drawArc(inner - Margins(stepInside), from, len);
+				}
 			} else if (previous && radialOpacity > 0. && radialOpacity < 1.) {
 				PaintInterpolatedIcon(q, icon, *previous, radialOpacity, inner);
 			} else {
@@ -771,7 +795,7 @@ void Document::draw(
 				{
 					auto hq = PainterHighQualityEnabler(q);
 					auto pen = stm->msgBg->p;
-					pen.setWidthF(style::ConvertScaleExact(1.5));
+					pen.setWidthF(style::ConvertScaleExact(kPenWidth));
 					q.setPen(pen);
 					q.setBrush(Qt::NoBrush);
 					q.drawEllipse(ttlRect);
@@ -843,11 +867,14 @@ void Document::draw(
 		if (_transcribedRound) {
 			FillWaveform(_data->round());
 		}
+		const auto inTTLViewer = _parent->delegate()->elementContext()
+			== Context::TTLViewer;
 		PaintWaveform(p,
 			context,
 			_transcribedRound ? _data->round() : _data->voice(),
 			namewidth + st::msgWaveformSkip,
-			progress);
+			progress,
+			inTTLViewer);
 		p.restore();
 	} else if (auto named = Get<HistoryDocumentNamed>()) {
 		p.setFont(st::semiboldFont);

From f9b5789cf727561012e5bd9288bc342f43b98164 Mon Sep 17 00:00:00 2001
From: 23rd <23rd@vivaldi.net>
Date: Sun, 14 Jan 2024 14:01:48 +0300
Subject: [PATCH 51/73] Fixed button position in voice record bar for ttl voice
 messages.

---
 .../controls/history_view_voice_record_bar.cpp     | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp
index 1f812250b..093e26bd9 100644
--- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp
+++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp
@@ -1325,6 +1325,7 @@ void VoiceRecordBar::init() {
 				}
 				updateTTLGeometry(TTLAnimationType::TopBottom, 1. - value);
 			};
+			_showListenAnimation.stop();
 			_showListenAnimation.start(std::move(callback), 0., to, duration);
 		}, lifetime());
 
@@ -1456,13 +1457,18 @@ void VoiceRecordBar::setTTLFilter(FilterCallback &&callback) {
 }
 
 void VoiceRecordBar::initLockGeometry() {
-	rpl::combine(
-		_lock->heightValue(),
-		geometryValue(),
-		static_cast<Ui::RpWidget*>(parentWidget())->geometryValue()
+	const auto parent = static_cast<Ui::RpWidget*>(parentWidget());
+	rpl::merge(
+		_lock->heightValue() | rpl::to_empty,
+		geometryValue() | rpl::to_empty,
+		parent->geometryValue() | rpl::to_empty
 	) | rpl::start_with_next([=] {
 		updateLockGeometry();
 	}, lifetime());
+	parent->geometryValue(
+	) | rpl::start_with_next([=] {
+		updateTTLGeometry(TTLAnimationType::RightLeft, 1.);
+	}, lifetime());
 }
 
 void VoiceRecordBar::initLevelGeometry() {

From d1f4463c2a5dbe00eefc79d0bd611aa05afe2684 Mon Sep 17 00:00:00 2001
From: 23rd <23rd@vivaldi.net>
Date: Sun, 14 Jan 2024 17:53:34 +0300
Subject: [PATCH 52/73] Added simple tooltip to ttl button from voice record
 bar.

---
 Telegram/Resources/langs/lang.strings         |  1 +
 .../chat_helpers/chat_helpers.style           |  6 +++
 .../history_view_voice_record_bar.cpp         | 45 +++++++++++++++++++
 Telegram/lib_ui                               |  2 +-
 4 files changed, 53 insertions(+), 1 deletion(-)

diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index 8c1560cd1..1bbb82bd5 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -2492,6 +2492,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 "lng_record_listen_cancel_sure" = "Are you sure you want to discard your recorded voice message?";
 "lng_record_lock_discard" = "Discard";
 "lng_record_hold_tip" = "Please hold the mouse button pressed to record a voice message.";
+"lng_record_once_active_tooltip" = "The recipients will be able to listen to it only once.";
 "lng_will_be_notified" = "Members will be notified when you post";
 "lng_wont_be_notified" = "Members will not be notified when you post";
 "lng_willbe_history" = "Please select a chat to start messaging";
diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style
index 2ad067367..99f6f9900 100644
--- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style
+++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style
@@ -1130,6 +1130,12 @@ historyRecordLockPosition: point(1px, 22px);
 historyRecordCancelButtonWidth: 100px;
 historyRecordCancelButtonFg: lightButtonFg;
 
+historyRecordTooltip: ImportantTooltip(defaultImportantTooltip) {
+	padding: margins(4px, 4px, 4px, 4px);
+	radius: 11px;
+	arrow: 6px;
+}
+
 historySilentToggle: IconButton(historyBotKeyboardShow) {
 	icon: icon {{ "chat/input_silent", historyComposeIconFg }};
 	iconOver: icon {{ "chat/input_silent", historyComposeIconFgOver }};
diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp
index 093e26bd9..bc80a899a 100644
--- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp
+++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp
@@ -31,7 +31,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/effects/animation_value.h"
 #include "ui/effects/ripple_animation.h"
 #include "ui/text/format_values.h"
+#include "ui/text/text_utilities.h"
 #include "ui/painter.h"
+#include "ui/widgets/tooltip.h"
 #include "ui/rect.h"
 #include "styles/style_chat.h"
 #include "styles/style_chat_helpers.h"
@@ -304,6 +306,49 @@ TTLButton::TTLButton(
 			st::historyRecordVoiceShowDuration);
 	});
 
+	{
+		const auto tooltip = Ui::CreateChild<Ui::ImportantTooltip>(
+			parent.get(),
+			object_ptr<Ui::PaddingWrap<Ui::FlatLabel>>(
+				parent.get(),
+				Ui::MakeNiceTooltipLabel(
+					parent,
+					tr::lng_record_once_active_tooltip(
+						Ui::Text::RichLangValue),
+					st::historyMessagesTTLLabel.minWidth,
+					st::ttlMediaImportantTooltipLabel),
+				st::defaultImportantTooltip.padding),
+			st::historyRecordTooltip);
+		geometryValue(
+		) | rpl::start_with_next([=](const QRect &r) {
+			if (r.isEmpty()) {
+				return;
+			}
+			tooltip->pointAt(r, RectPart::Right, [=](QSize size) {
+				return QPoint(
+					r.left()
+						- size.width()
+						- st::defaultImportantTooltip.padding.left(),
+					r.top()
+						+ r.height()
+						- size.height()
+						+ st::historyRecordTooltip.padding.top());
+			});
+		}, tooltip->lifetime());
+		tooltip->show();
+
+		clicks(
+		) | rpl::start_with_next([=] {
+			const auto toggled = !Ui::AbstractButton::isDisabled();
+			tooltip->toggleAnimated(toggled);
+
+			if (toggled) {
+				constexpr auto kTimeout = crl::time(3000);
+				tooltip->hideAfter(kTimeout);
+			}
+		}, tooltip->lifetime());
+	}
+
 	paintRequest(
 	) | rpl::start_with_next([=](const QRect &clip) {
 		auto p = QPainter(this);
diff --git a/Telegram/lib_ui b/Telegram/lib_ui
index aa39793a9..08a19be80 160000
--- a/Telegram/lib_ui
+++ b/Telegram/lib_ui
@@ -1 +1 @@
-Subproject commit aa39793a91f1186879c15c214a2671d78eab5085
+Subproject commit 08a19be802cef8ee801121906c5f0ce9d2fa65b0

From 8895e494669de733d88edc087f658e4846893d61 Mon Sep 17 00:00:00 2001
From: 23rd <23rd@vivaldi.net>
Date: Sun, 14 Jan 2024 18:07:36 +0300
Subject: [PATCH 53/73] Fixed display of corner button in section with ttl
 button in record bar.

---
 Telegram/SourceFiles/history/history_widget.cpp            | 3 ++-
 .../view/controls/history_view_compose_controls.cpp        | 4 ++++
 .../history/view/controls/history_view_compose_controls.h  | 1 +
 .../view/controls/history_view_voice_record_bar.cpp        | 4 ++++
 .../history/view/controls/history_view_voice_record_bar.h  | 1 +
 .../history/view/history_view_replies_section.cpp          | 7 +++++--
 .../history/view/history_view_scheduled_section.cpp        | 6 ++++--
 7 files changed, 21 insertions(+), 5 deletions(-)

diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp
index 97c586aed..1d518a32e 100644
--- a/Telegram/SourceFiles/history/history_widget.cpp
+++ b/Telegram/SourceFiles/history/history_widget.cpp
@@ -6283,7 +6283,8 @@ std::optional<bool> HistoryWidget::cornerButtonsDownShown() {
 	if (!_list || _firstLoadRequest) {
 		return false;
 	}
-	if (_voiceRecordBar->isLockPresent()) {
+	if (_voiceRecordBar->isLockPresent()
+		|| _voiceRecordBar->isTTLButtonShown()) {
 		return false;
 	}
 	if (!_history->loadedAtBottom() || _cornerButtons.replyReturn()) {
diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp
index 57cff6c0c..e8e81511e 100644
--- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp
+++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp
@@ -2925,6 +2925,10 @@ bool ComposeControls::isLockPresent() const {
 	return _voiceRecordBar->isLockPresent();
 }
 
+bool ComposeControls::isTTLButtonShown() const {
+	return _voiceRecordBar->isTTLButtonShown();
+}
+
 rpl::producer<bool> ComposeControls::lockShowStarts() const {
 	return _voiceRecordBar->lockShowStarts();
 }
diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h
index 9ae515d6c..a846cd4ee 100644
--- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h
+++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h
@@ -220,6 +220,7 @@ public:
 
 	[[nodiscard]] rpl::producer<bool> lockShowStarts() const;
 	[[nodiscard]] bool isLockPresent() const;
+	[[nodiscard]] bool isTTLButtonShown() const;
 	[[nodiscard]] bool isRecording() const;
 	[[nodiscard]] bool isRecordingPressed() const;
 	[[nodiscard]] rpl::producer<bool> recordingActiveValue() const;
diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp
index bc80a899a..f8bd585b2 100644
--- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp
+++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp
@@ -1838,6 +1838,10 @@ bool VoiceRecordBar::isRecordingByAnotherBar() const {
 	return !isRecording() && ::Media::Capture::instance()->started();
 }
 
+bool VoiceRecordBar::isTTLButtonShown() const {
+	return !_ttlButton->isHidden();
+}
+
 bool VoiceRecordBar::hasDuration() const {
 	return _recordingSamples > 0;
 }
diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h
index 31c0eb30e..8b59ad5ea 100644
--- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h
+++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h
@@ -98,6 +98,7 @@ public:
 	[[nodiscard]] bool isListenState() const;
 	[[nodiscard]] bool isActive() const;
 	[[nodiscard]] bool isRecordingByAnotherBar() const;
+	[[nodiscard]] bool isTTLButtonShown() const;
 
 private:
 	enum class StopType {
diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp
index 530af5fae..157a61977 100644
--- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp
@@ -1838,7 +1838,8 @@ bool RepliesWidget::cornerButtonsIgnoreVisibility() {
 }
 
 std::optional<bool> RepliesWidget::cornerButtonsDownShown() {
-	if (_composeControls->isLockPresent()) {
+	if (_composeControls->isLockPresent()
+		|| _composeControls->isTTLButtonShown()) {
 		return false;
 	}
 	const auto top = _scroll->scrollTop() + st::historyToDownShownAfter;
@@ -1851,7 +1852,9 @@ std::optional<bool> RepliesWidget::cornerButtonsDownShown() {
 }
 
 bool RepliesWidget::cornerButtonsUnreadMayBeShown() {
-	return _loaded && !_composeControls->isLockPresent();
+	return _loaded
+		&& !_composeControls->isLockPresent()
+		&& !_composeControls->isTTLButtonShown();
 }
 
 bool RepliesWidget::cornerButtonsHas(CornerButtonType type) {
diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp
index ed57d5ee8..a086d71cb 100644
--- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp
@@ -843,7 +843,8 @@ bool ScheduledWidget::cornerButtonsIgnoreVisibility() {
 }
 
 std::optional<bool> ScheduledWidget::cornerButtonsDownShown() {
-	if (_composeControls->isLockPresent()) {
+	if (_composeControls->isLockPresent()
+		|| _composeControls->isTTLButtonShown()) {
 		return false;
 	}
 	const auto top = _scroll->scrollTop() + st::historyToDownShownAfter;
@@ -857,7 +858,8 @@ std::optional<bool> ScheduledWidget::cornerButtonsDownShown() {
 
 bool ScheduledWidget::cornerButtonsUnreadMayBeShown() {
 	return _inner->loadedAtBottomKnown()
-		&& !_composeControls->isLockPresent();
+		&& !_composeControls->isLockPresent()
+		&& !_composeControls->isTTLButtonShown();
 }
 
 bool ScheduledWidget::cornerButtonsHas(CornerButtonType type) {

From cb4781360ae632819bf888cc18b2cdb42214da6a Mon Sep 17 00:00:00 2001
From: 23rd <23rd@vivaldi.net>
Date: Sun, 14 Jan 2024 18:50:15 +0300
Subject: [PATCH 54/73] Added first shown tooltip to ttl button from voice
 record bar.

---
 Telegram/Resources/langs/lang.strings         |  1 +
 Telegram/SourceFiles/core/core_settings.cpp   |  9 +++-
 Telegram/SourceFiles/core/core_settings.h     | 10 +++++
 .../history_view_voice_record_bar.cpp         | 44 +++++++++++++++----
 4 files changed, 55 insertions(+), 9 deletions(-)

diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index 1bbb82bd5..357838992 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -2492,6 +2492,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 "lng_record_listen_cancel_sure" = "Are you sure you want to discard your recorded voice message?";
 "lng_record_lock_discard" = "Discard";
 "lng_record_hold_tip" = "Please hold the mouse button pressed to record a voice message.";
+"lng_record_once_first_tooltip" = "Tap to set this message to **Play Once**.";
 "lng_record_once_active_tooltip" = "The recipients will be able to listen to it only once.";
 "lng_will_be_notified" = "Members will be notified when you post";
 "lng_wont_be_notified" = "Members will not be notified when you post";
diff --git a/Telegram/SourceFiles/core/core_settings.cpp b/Telegram/SourceFiles/core/core_settings.cpp
index ac4119d95..0ab441fe0 100644
--- a/Telegram/SourceFiles/core/core_settings.cpp
+++ b/Telegram/SourceFiles/core/core_settings.cpp
@@ -339,7 +339,8 @@ QByteArray Settings::serialize() const {
 			<< qint32(_ignoreBatterySaving.current() ? 1 : 0)
 			<< quint64(_macRoundIconDigest.value_or(0))
 			<< qint32(_storiesClickTooltipHidden.current() ? 1 : 0)
-			<< qint32(_recentEmojiSkip.size());
+			<< qint32(_recentEmojiSkip.size())
+			<< qint32(_ttlVoiceClickTooltipHidden.current() ? 1 : 0);
 		for (const auto &id : _recentEmojiSkip) {
 			stream << id;
 		}
@@ -453,6 +454,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
 	qint32 storiesClickTooltipHidden = _storiesClickTooltipHidden.current() ? 1 : 0;
 	base::flat_set<QString> recentEmojiSkip;
 	qint32 trayIconMonochrome = (_trayIconMonochrome.current() ? 1 : 0);
+	qint32 ttlVoiceClickTooltipHidden = _ttlVoiceClickTooltipHidden.current() ? 1 : 0;
 
 	stream >> themesAccentColors;
 	if (!stream.atEnd()) {
@@ -709,6 +711,9 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
 		// Let existing clients use the old value.
 		trayIconMonochrome = 0;
 	}
+	if (!stream.atEnd()) {
+		stream >> ttlVoiceClickTooltipHidden;
+	}
 	if (stream.status() != QDataStream::Ok) {
 		LOG(("App Error: "
 			"Bad data for Core::Settings::constructFromSerialized()"));
@@ -903,6 +908,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
 	_storiesClickTooltipHidden = (storiesClickTooltipHidden == 1);
 	_recentEmojiSkip = std::move(recentEmojiSkip);
 	_trayIconMonochrome = (trayIconMonochrome == 1);
+	_ttlVoiceClickTooltipHidden = (ttlVoiceClickTooltipHidden == 1);
 }
 
 QString Settings::getSoundPath(const QString &key) const {
@@ -1259,6 +1265,7 @@ void Settings::resetOnLastLogout() {
 	_systemDarkModeEnabled = false;
 	_hiddenGroupCallTooltips = 0;
 	_storiesClickTooltipHidden = false;
+	_ttlVoiceClickTooltipHidden = false;
 
 	_recentEmojiPreload.clear();
 	_recentEmoji.clear();
diff --git a/Telegram/SourceFiles/core/core_settings.h b/Telegram/SourceFiles/core/core_settings.h
index 0f7082293..5afab71f9 100644
--- a/Telegram/SourceFiles/core/core_settings.h
+++ b/Telegram/SourceFiles/core/core_settings.h
@@ -820,6 +820,15 @@ public:
 	void setStoriesClickTooltipHidden(bool value) {
 		_storiesClickTooltipHidden = value;
 	}
+	[[nodiscard]] bool ttlVoiceClickTooltipHidden() const {
+		return _ttlVoiceClickTooltipHidden.current();
+	}
+	[[nodiscard]] rpl::producer<bool> ttlVoiceClickTooltipHiddenValue() const {
+		return _ttlVoiceClickTooltipHidden.value();
+	}
+	void setTtlVoiceClickTooltipHidden(bool value) {
+		_ttlVoiceClickTooltipHidden = value;
+	}
 
 	[[nodiscard]] static bool ThirdColumnByDefault();
 	[[nodiscard]] static float64 DefaultDialogsWidthRatio();
@@ -945,6 +954,7 @@ private:
 	rpl::variable<bool> _ignoreBatterySaving = false;
 	std::optional<uint64> _macRoundIconDigest;
 	rpl::variable<bool> _storiesClickTooltipHidden = false;
+	rpl::variable<bool> _ttlVoiceClickTooltipHidden = false;
 
 	bool _tabbedReplacedWithInfo = false; // per-window
 	rpl::event_stream<bool> _tabbedReplacedWithInfoValue; // per-window
diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp
index f8bd585b2..d78bc4b41 100644
--- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp
+++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp
@@ -280,7 +280,6 @@ protected:
 private:
 	const style::RecordBar &_st;
 	const QRect _rippleRect;
-	const QString _text;
 
 	Ui::Animations::Simple _activeAnimation;
 
@@ -292,8 +291,7 @@ TTLButton::TTLButton(
 : RippleButton(parent, st.lock.ripple)
 , _st(st)
 , _rippleRect(Rect(Size(st::historyRecordLockTopShadow.width()))
-	- (st::historyRecordLockRippleMargin))
-, _text(u"1"_q) {
+	- (st::historyRecordLockRippleMargin)) {
 	resize(Size(st::historyRecordLockTopShadow.width()));
 
 	setClickedCallback([=] {
@@ -306,20 +304,27 @@ TTLButton::TTLButton(
 			st::historyRecordVoiceShowDuration);
 	});
 
-	{
+	Ui::RpWidget::shownValue() | rpl::filter(
+		rpl::mappers::_1
+	) | rpl::skip(1) | rpl::take(1) | rpl::start_with_next([=] {
+		auto text = rpl::conditional(
+			Core::App().settings().ttlVoiceClickTooltipHiddenValue(),
+			tr::lng_record_once_active_tooltip(
+				Ui::Text::RichLangValue),
+			tr::lng_record_once_first_tooltip(
+				Ui::Text::RichLangValue));
 		const auto tooltip = Ui::CreateChild<Ui::ImportantTooltip>(
 			parent.get(),
 			object_ptr<Ui::PaddingWrap<Ui::FlatLabel>>(
 				parent.get(),
 				Ui::MakeNiceTooltipLabel(
 					parent,
-					tr::lng_record_once_active_tooltip(
-						Ui::Text::RichLangValue),
+					std::move(text),
 					st::historyMessagesTTLLabel.minWidth,
 					st::ttlMediaImportantTooltipLabel),
 				st::defaultImportantTooltip.padding),
 			st::historyRecordTooltip);
-		geometryValue(
+		Ui::RpWidget::geometryValue(
 		) | rpl::start_with_next([=](const QRect &r) {
 			if (r.isEmpty()) {
 				return;
@@ -336,6 +341,15 @@ TTLButton::TTLButton(
 			});
 		}, tooltip->lifetime());
 		tooltip->show();
+		if (!Core::App().settings().ttlVoiceClickTooltipHidden()) {
+			clicks(
+			) | rpl::take(1) | rpl::start_with_next([=] {
+				Core::App().settings().setTtlVoiceClickTooltipHidden(true);
+			}, tooltip->lifetime());
+			tooltip->toggleAnimated(true);
+		} else {
+			tooltip->toggleFast(false);
+		}
 
 		clicks(
 		) | rpl::start_with_next([=] {
@@ -347,7 +361,19 @@ TTLButton::TTLButton(
 				tooltip->hideAfter(kTimeout);
 			}
 		}, tooltip->lifetime());
-	}
+
+		Ui::RpWidget::geometryValue(
+		) | rpl::map([=](const QRect &r) {
+			return (r.left() + r.width() > parentWidget()->width());
+		}) | rpl::distinct_until_changed(
+		) | rpl::start_with_next([=](bool toHide) {
+			const auto isFirstTooltip =
+				!Core::App().settings().ttlVoiceClickTooltipHidden();
+			if (isFirstTooltip || (!isFirstTooltip && toHide)) {
+				tooltip->toggleAnimated(!toHide);
+			}
+		}, tooltip->lifetime());
+	}, lifetime());
 
 	paintRequest(
 	) | rpl::start_with_next([=](const QRect &clip) {
@@ -1633,6 +1659,8 @@ void VoiceRecordBar::finish() {
 
 	_listen = nullptr;
 
+	[[maybe_unused]] const auto s = takeTTLState();
+
 	_sendActionUpdates.fire({ Api::SendProgressType::RecordVoice, -1 });
 }
 

From b462d7627fe4adfacefb0f0d71415e2c09b2a46e Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Tue, 16 Jan 2024 08:35:38 +0400
Subject: [PATCH 55/73] Fix channels in poll results restoring.

---
 .../info/polls/info_polls_results_inner_widget.cpp           | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/Telegram/SourceFiles/info/polls/info_polls_results_inner_widget.cpp b/Telegram/SourceFiles/info/polls/info_polls_results_inner_widget.cpp
index b1cefdeff..3d42cb8c0 100644
--- a/Telegram/SourceFiles/info/polls/info_polls_results_inner_widget.cpp
+++ b/Telegram/SourceFiles/info/polls/info_polls_results_inner_widget.cpp
@@ -372,10 +372,7 @@ void ListController::restoreState(std::unique_ptr<PeerListState> state) {
 
 std::unique_ptr<PeerListRow> ListController::createRestoredRow(
 		not_null<PeerData*> peer) {
-	if (const auto user = peer->asUser()) {
-		return createRow(user);
-	}
-	return nullptr;
+	return createRow(peer);
 }
 
 void ListController::rowClicked(not_null<PeerListRow*> row) {

From 104cf504ab4349c971f30a23e6ee4692abb1dc24 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Tue, 16 Jan 2024 10:34:49 +0400
Subject: [PATCH 56/73] Play ttl media horizontally where the message was.

---
 .../chat_helpers/ttl_media_layer_widget.cpp   | 135 ++++++++++++------
 .../history/view/history_view_message.cpp     |   4 +-
 .../view/media/history_view_document.cpp      |  10 +-
 3 files changed, 99 insertions(+), 50 deletions(-)

diff --git a/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp b/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp
index 120f89f9e..817cd4709 100644
--- a/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp
+++ b/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp
@@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/widgets/tooltip.h"
 #include "window/section_widget.h" // Window::ChatThemeValueFromPeer.
 #include "window/themes/window_theme.h"
+#include "window/window_controller.h"
 #include "window/window_session_controller.h"
 #include "styles/style_chat.h"
 #include "styles/style_chat_helpers.h"
@@ -44,6 +45,7 @@ public:
 	PreviewDelegate(
 		not_null<QWidget*> parent,
 		not_null<Ui::ChatStyle*> st,
+		rpl::producer<bool> chatWideValue,
 		Fn<void()> update);
 
 	bool elementAnimationsPaused() override;
@@ -54,15 +56,18 @@ public:
 private:
 	const not_null<QWidget*> _parent;
 	const std::unique_ptr<Ui::PathShiftGradient> _pathGradient;
+	rpl::variable<bool> _chatWide;
 
 };
 
 PreviewDelegate::PreviewDelegate(
 	not_null<QWidget*> parent,
 	not_null<Ui::ChatStyle*> st,
+	rpl::producer<bool> chatWideValue,
 	Fn<void()> update)
 : _parent(parent)
-, _pathGradient(HistoryView::MakePathShiftGradient(st, update)) {
+, _pathGradient(HistoryView::MakePathShiftGradient(st, update))
+, _chatWide(std::move(chatWideValue)) {
 }
 
 bool PreviewDelegate::elementAnimationsPaused() {
@@ -78,7 +83,7 @@ HistoryView::Context PreviewDelegate::elementContext() {
 }
 
 bool PreviewDelegate::elementIsChatWide() {
-	return true;
+	return _chatWide.current();
 }
 
 class PreviewWrap final : public Ui::RpWidget {
@@ -86,6 +91,8 @@ public:
 	PreviewWrap(
 		not_null<Ui::RpWidget*> parent,
 		not_null<HistoryItem*> item,
+		rpl::producer<QRect> viewportValue,
+		rpl::producer<bool> chatWideValue,
 		rpl::producer<std::shared_ptr<Ui::ChatTheme>> theme);
 	~PreviewWrap();
 
@@ -93,13 +100,17 @@ public:
 
 private:
 	void paintEvent(QPaintEvent *e) override;
-	[[nodiscard]] QRect elementRect() const;
 
 	const not_null<HistoryItem*> _item;
 	const std::unique_ptr<Ui::ChatStyle> _style;
 	const std::unique_ptr<PreviewDelegate> _delegate;
+	rpl::variable<QRect> _globalViewport;
+	rpl::variable<bool> _chatWide;
 	std::shared_ptr<Ui::ChatTheme> _theme;
 	std::unique_ptr<HistoryView::Element> _element;
+	QRect _viewport;
+	QRect _elementGeometry;
+	rpl::variable<QRect> _elementInner;
 	rpl::lifetime _elementLifetime;
 
 	struct {
@@ -114,6 +125,8 @@ private:
 PreviewWrap::PreviewWrap(
 	not_null<Ui::RpWidget*> parent,
 	not_null<HistoryItem*> item,
+	rpl::producer<QRect> viewportValue,
+	rpl::producer<bool> chatWideValue,
 	rpl::producer<std::shared_ptr<Ui::ChatTheme>> theme)
 : RpWidget(parent)
 , _item(item)
@@ -122,8 +135,9 @@ PreviewWrap::PreviewWrap(
 , _delegate(std::make_unique<PreviewDelegate>(
 	parent,
 	_style.get(),
-	[=] { update(elementRect()); })) {
-
+	std::move(chatWideValue),
+	[=] { update(_elementGeometry); }))
+, _globalViewport(std::move(viewportValue)) {
 	const auto isRound = _item
 		&& _item->media()
 		&& _item->media()->document()
@@ -140,7 +154,7 @@ PreviewWrap::PreviewWrap(
 	session->data().viewRepaintRequest(
 	) | rpl::start_with_next([=](not_null<const HistoryView::Element*> view) {
 		if (view == _element.get()) {
-			update(elementRect());
+			update(_elementGeometry);
 		}
 	}, lifetime());
 
@@ -157,11 +171,15 @@ PreviewWrap::PreviewWrap(
 		close->setClickedCallback(closeCallback);
 		close->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
 
-		sizeValue(
-		) | rpl::start_with_next([=](const QSize &s) {
+		rpl::combine(
+			sizeValue(),
+			_elementInner.value()
+		) | rpl::start_with_next([=](QSize size, QRect inner) {
 			close->moveToLeft(
-				(s.width() - close->width()) / 2,
-				s.height() - close->height() - st::ttlMediaButtonBottomSkip);
+				inner.x() + (inner.width() - close->width()) / 2,
+				(size.height()
+					- close->height()
+					- st::ttlMediaButtonBottomSkip));
 		}, close->lifetime());
 	}
 
@@ -170,11 +188,27 @@ PreviewWrap::PreviewWrap(
 
 	{
 		_element->initDimensions();
-		widthValue(
-		) | rpl::filter([=](int width) {
-			return width > st::msgMinWidth;
-		}) | rpl::start_with_next([=](int width) {
-			_element->resizeGetHeight(width);
+		rpl::combine(
+			sizeValue(),
+			_globalViewport.value()
+		) | rpl::start_with_next([=](QSize outer, QRect globalViewport) {
+			_viewport = globalViewport.isEmpty()
+				? rect()
+				: mapFromGlobal(globalViewport);
+			if (_viewport.width() < st::msgMinWidth) {
+				return;
+			}
+			_element->resizeGetHeight(_viewport.width());
+			_elementGeometry = QRect(
+				(_viewport.width() - _element->width()) / 2,
+				(_viewport.height() - _element->height()) / 2,
+				_element->width(),
+				_element->height()
+			).translated(_viewport.topLeft());
+			const auto media = _element->media();
+			_elementInner = _element->innerGeometry().translated(
+				_elementGeometry.topLeft());
+			update();
 		}, _elementLifetime);
 	}
 
@@ -203,22 +237,17 @@ PreviewWrap::PreviewWrap(
 				st::defaultImportantTooltip.padding),
 			st::dialogsStoriesTooltip);
 		tooltip->toggleFast(true);
-		sizeValue(
-		) | rpl::filter(
-			[](const QSize &s) { return !s.isNull(); }
-		) | rpl::take(1) | rpl::start_with_next([=](const QSize &s) {
-			if (s.isEmpty()) {
-				return;
-			}
-			auto area = elementRect();
-			area.setWidth(_element->media()
-				? _element->media()->width()
-				: _element->width());
-			tooltip->pointAt(area, RectPart::Top, [=](QSize size) {
+		_elementInner.value(
+		) | rpl::filter([](const QRect &inner) {
+			return !inner.isEmpty();
+		}) | rpl::start_with_next([=](const QRect &inner) {
+			tooltip->pointAt(inner, RectPart::Top, [=](QSize size) {
 				return QPoint{
-					(area.width() - size.width()) / 2,
-					(s.height() - size.height() * 2 - _element->height()) / 2
-						- st::defaultImportantTooltip.padding.top(),
+					inner.x() + (inner.width() - size.width()) / 2,
+					(inner.y()
+						- st::normalFont->height
+						- size.height()
+						- st::defaultImportantTooltip.padding.top()),
 				};
 			});
 		}, tooltip->lifetime());
@@ -232,14 +261,6 @@ PreviewWrap::PreviewWrap(
 	}, lifetime());
 }
 
-QRect PreviewWrap::elementRect() const {
-	return QRect(
-		(width() - _element->width()) / 2,
-		(height() - _element->height()) / 2,
-		_element->width(),
-		_element->height());
-}
-
 rpl::producer<> PreviewWrap::closeRequests() const {
 	return _closeRequests.events();
 }
@@ -250,13 +271,14 @@ PreviewWrap::~PreviewWrap() {
 }
 
 void PreviewWrap::paintEvent(QPaintEvent *e) {
-	if (!_element) {
+	if (!_element || _elementGeometry.isEmpty()) {
 		return;
 	}
 
 	auto p = QPainter(this);
-	const auto r = rect();
-
+	//p.fillRect(_viewport, QColor(255, 0, 0, 64));
+	//p.fillRect(_elementGeometry, QColor(0, 255, 0, 64));
+	//p.fillRect(_elementInner.current(), QColor(0, 0, 255, 64));
 	if (!_last.use) {
 		const auto size = _element->currentSize();
 		auto result = QImage(
@@ -276,12 +298,35 @@ void PreviewWrap::paintEvent(QPaintEvent *e) {
 		}
 		_last.frame = std::move(result);
 	}
-	p.translate(
-		(r.width() - _element->width()) / 2,
-		(r.height() - _element->height()) / 2);
+	p.translate(_elementGeometry.topLeft());
 	p.drawImage(0, 0, _last.frame);
 }
 
+rpl::producer<QRect> GlobalViewportForWindow(
+		not_null<Window::SessionController*> controller) {
+	const auto delegate = controller->window().floatPlayerDelegate();
+	return rpl::single(rpl::empty) | rpl::then(
+		delegate->floatPlayerAreaUpdates()
+	) | rpl::map([=] {
+		auto section = (Media::Player::FloatSectionDelegate*)nullptr;
+		delegate->floatPlayerEnumerateSections([&](
+				not_null<Media::Player::FloatSectionDelegate*> check,
+				Window::Column column) {
+			if ((column == Window::Column::First && !section)
+				|| column == Window::Column::Second) {
+				section = check;
+			}
+		});
+		if (section) {
+			const auto rect = section->floatPlayerAvailableRect();
+			if (rect.width() >= st::msgMinWidth) {
+				return rect;
+			}
+		}
+		return QRect();
+	});
+}
+
 } // namespace
 
 void ShowTTLMediaLayerWidget(
@@ -292,6 +337,8 @@ void ShowTTLMediaLayerWidget(
 	auto preview = base::make_unique_q<PreviewWrap>(
 		parent,
 		item,
+		GlobalViewportForWindow(controller),
+		controller->adaptive().chatWideValue(),
 		Window::ChatThemeValueFromPeer(
 			controller,
 			item->history()->peer));
diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp
index d7c9e2f16..93db9cd12 100644
--- a/Telegram/SourceFiles/history/view/history_view_message.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_message.cpp
@@ -1984,6 +1984,7 @@ bool Message::hasFromPhoto() const {
 	case Context::AdminLog:
 		return true;
 	case Context::History:
+	case Context::TTLViewer:
 	case Context::Pinned:
 	case Context::Replies:
 	case Context::SavedSublist: {
@@ -2005,7 +2006,6 @@ bool Message::hasFromPhoto() const {
 		return !item->out() && !item->history()->peer->isUser();
 	} break;
 	case Context::ContactPreview:
-	case Context::TTLViewer:
 		return false;
 	}
 	Unexpected("Context in Message::hasFromPhoto.");
@@ -3171,6 +3171,7 @@ bool Message::hasFromName() const {
 	case Context::AdminLog:
 		return true;
 	case Context::History:
+	case Context::TTLViewer:
 	case Context::Pinned:
 	case Context::Replies:
 	case Context::SavedSublist: {
@@ -3201,7 +3202,6 @@ bool Message::hasFromName() const {
 		return false;
 	} break;
 	case Context::ContactPreview:
-	case Context::TTLViewer:
 		return false;
 	}
 	Unexpected("Context in Message::hasFromName.");
diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_document.cpp
index 2c963c344..5c3632c33 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_document.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_document.cpp
@@ -243,10 +243,12 @@ void PaintWaveform(
 			p.fillRect(
 				QRectF(barLeft, barTop, leftWidth, barHeight),
 				active);
-			p.fillRect(
-				QRectF(activeWidth, barTop, rightWidth, barHeight),
-				inactive);
-		} else {
+			if (!ttl) {
+				p.fillRect(
+					QRectF(activeWidth, barTop, rightWidth, barHeight),
+					inactive);
+			}
+		} else if (!ttl || barLeft < activeWidth) {
 			const auto &color = (barLeft >= activeWidth) ? inactive : active;
 			p.fillRect(QRectF(barLeft, barTop, barWidth, barHeight), color);
 		}

From b31bb6dd3373f38232feb627c860372ba901bde2 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Tue, 16 Jan 2024 10:53:26 +0400
Subject: [PATCH 57/73] Fix sending single-time voice messages.

---
 .../controls/history_view_voice_record_bar.cpp     | 14 ++++++++++----
 .../view/controls/history_view_voice_record_bar.h  |  3 ++-
 2 files changed, 12 insertions(+), 5 deletions(-)

diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp
index d78bc4b41..3714bbf8f 100644
--- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp
+++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp
@@ -1638,10 +1638,12 @@ void VoiceRecordBar::stop(bool send) {
 	if (isHidden() && !send) {
 		return;
 	}
+	const auto ttlBeforeHide = peekTTLState();
 	auto disappearanceCallback = [=] {
 		hide();
 
-		stopRecording(send ? StopType::Send : StopType::Cancel);
+		const auto type = send ? StopType::Send : StopType::Cancel;
+		stopRecording(type, ttlBeforeHide);
 	};
 	_lockShowing = false;
 	visibilityAnimate(false, std::move(disappearanceCallback));
@@ -1671,7 +1673,7 @@ void VoiceRecordBar::hideFast() {
 	[[maybe_unused]] const auto s = takeTTLState();
 }
 
-void VoiceRecordBar::stopRecording(StopType type) {
+void VoiceRecordBar::stopRecording(StopType type, bool ttlBeforeHide) {
 	using namespace ::Media::Capture;
 	if (type == StopType::Cancel) {
 		instance()->stop(crl::guard(this, [=](Result &&data) {
@@ -1691,9 +1693,9 @@ void VoiceRecordBar::stopRecording(StopType type) {
 		const auto duration = Duration(data.samples);
 		if (type == StopType::Send) {
 			const auto options = Api::SendOptions{
-				.ttlSeconds = takeTTLState()
+				.ttlSeconds = (ttlBeforeHide
 					? std::numeric_limits<int>::max()
-					: 0
+					: 0),
 			};
 			_sendVoiceRequests.fire({
 				data.bytes,
@@ -1901,6 +1903,10 @@ void VoiceRecordBar::computeAndSetLockProgress(QPoint globalPos) {
 	_lock->requestPaintProgress(Progress(localPos.y(), higher - lower));
 }
 
+bool VoiceRecordBar::peekTTLState() const {
+	return !_ttlButton->isDisabled();
+}
+
 bool VoiceRecordBar::takeTTLState() const {
 	const auto hasTtl = !_ttlButton->isDisabled();
 	_ttlButton->clearState();
diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h
index 8b59ad5ea..964614597 100644
--- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h
+++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h
@@ -126,7 +126,7 @@ private:
 	[[nodiscard]] bool recordingAnimationCallback(crl::time now);
 
 	void stop(bool send);
-	void stopRecording(StopType type);
+	void stopRecording(StopType type, bool ttlBeforeHide = false);
 	void visibilityAnimate(bool show, Fn<void()> &&callback);
 
 	[[nodiscard]] bool showRecordButton() const;
@@ -149,6 +149,7 @@ private:
 
 	void computeAndSetLockProgress(QPoint globalPos);
 
+	[[nodiscard]] bool peekTTLState() const;
 	[[nodiscard]] bool takeTTLState() const;
 
 	const style::RecordBar &_st;

From 6b910e11e5ee7d9cafc1d83c185a2d6ceb267f64 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Tue, 16 Jan 2024 11:16:58 +0400
Subject: [PATCH 58/73] Update lib_webview submodle.

---
 Telegram/lib_webview | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Telegram/lib_webview b/Telegram/lib_webview
index a1c84d636..d7da1570f 160000
--- a/Telegram/lib_webview
+++ b/Telegram/lib_webview
@@ -1 +1 @@
-Subproject commit a1c84d636f7975781dece08939bbc24da8298c3c
+Subproject commit d7da1570f02485d9bdcd929cd2ce5845b52f5178

From 6f64fea0b129d077a1e9a87240ac53099167b201 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Tue, 16 Jan 2024 11:20:41 +0400
Subject: [PATCH 59/73] Fix build with Xcode.

---
 Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp | 2 +-
 Telegram/SourceFiles/dialogs/dialogs_widget.cpp       | 6 ++++++
 Telegram/SourceFiles/dialogs/dialogs_widget.h         | 7 +++++--
 3 files changed, 12 insertions(+), 3 deletions(-)

diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
index 05e54ec6b..14c9552bb 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
@@ -1781,7 +1781,7 @@ void InnerWidget::mousePressReleased(
 		}
 	}
 	if (auto activated = ClickHandler::unpressed()) {
-		ActivateClickHandler(window(), activated, { button });
+		ActivateClickHandler(window(), activated, ClickContext{ button });
 	}
 }
 
diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
index b41257487..b6f0d414b 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
@@ -2691,6 +2691,12 @@ bool Widget::setSearchInChat(
 	return true;
 }
 
+bool Widget::setSearchInChat(
+		Key chat,
+		PeerData *from) {
+	return setSearchInChat(chat, from, {});
+}
+
 void Widget::clearSearchCache() {
 	_searchCache.clear();
 	_singleMessageSearch.clear();
diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h
index 3c8373cb1..fc7264376 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_widget.h
+++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h
@@ -182,8 +182,11 @@ private:
 	[[nodiscard]] bool searchForTopicsRequired(const QString &query) const;
 	bool setSearchInChat(
 		Key chat,
-		PeerData *from = nullptr,
-		std::vector<Data::ReactionId> tags = {});
+		PeerData *from,
+		std::vector<Data::ReactionId> tags);
+	bool setSearchInChat(
+		Key chat,
+		PeerData *from = nullptr);
 	void showCalendar();
 	void showSearchFrom();
 	void showMainMenu();

From 93a734eecf3f73e2a18ee7caeb47ef01c5f890b4 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Tue, 16 Jan 2024 11:56:08 +0400
Subject: [PATCH 60/73] Prepare ttl-media release.

---
 Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp | 4 +++-
 Telegram/SourceFiles/history/history_item.cpp         | 3 ++-
 2 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
index 14c9552bb..cea479bb4 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
@@ -2986,7 +2986,9 @@ void InnerWidget::searchInChat(
 		if (peer->isSelf()) {
 			const auto reactions = &peer->owner().reactions();
 			const auto list = [=] {
-				return reactions->list(Data::Reactions::Type::MyTags);
+				// Disable reactions as tags for now.
+				//return reactions->list(Data::Reactions::Type::MyTags);
+				return std::vector<Data::Reaction>();
 			};
 			_searchTags = std::make_unique<SearchTags>(
 				&peer->owner(),
diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp
index 35a2dc7db..bf473a19b 100644
--- a/Telegram/SourceFiles/history/history_item.cpp
+++ b/Telegram/SourceFiles/history/history_item.cpp
@@ -2430,7 +2430,8 @@ const std::vector<Data::MessageReaction> &HistoryItem::reactions() const {
 }
 
 bool HistoryItem::reactionsAreTags() const {
-	return _flags & MessageFlag::ReactionsAreTags;
+	// Disable reactions as tags for now.
+	return false;// _flags & MessageFlag::ReactionsAreTags;
 }
 
 auto HistoryItem::recentReactions() const

From 06775b5623970e389cf37fd1b0e39b2deaf8ebd3 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Tue, 16 Jan 2024 11:56:28 +0400
Subject: [PATCH 61/73] Optimize ttl media overlay a bit.

---
 .../chat_helpers/ttl_media_layer_widget.cpp   | 58 +++++++------------
 .../view/media/history_view_document.cpp      |  9 ++-
 2 files changed, 28 insertions(+), 39 deletions(-)

diff --git a/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp b/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp
index 817cd4709..552b050e0 100644
--- a/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp
+++ b/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp
@@ -113,10 +113,7 @@ private:
 	rpl::variable<QRect> _elementInner;
 	rpl::lifetime _elementLifetime;
 
-	struct {
-		QImage frame;
-		bool use = false;
-	} _last;
+	QImage _lastFrameCache;
 
 	rpl::event_stream<> _closeRequests;
 
@@ -138,6 +135,14 @@ PreviewWrap::PreviewWrap(
 	std::move(chatWideValue),
 	[=] { update(_elementGeometry); }))
 , _globalViewport(std::move(viewportValue)) {
+	const auto closeCallback = [=] { _closeRequests.fire({}); };
+	HistoryView::TTLVoiceStops(
+		item->fullId()
+	) | rpl::start_with_next([=] {
+		_lastFrameCache = Ui::GrabWidgetToImage(this, _elementGeometry);
+		closeCallback();
+	}, lifetime());
+
 	const auto isRound = _item
 		&& _item->media()
 		&& _item->media()->document()
@@ -158,8 +163,6 @@ PreviewWrap::PreviewWrap(
 		}
 	}, lifetime());
 
-	const auto closeCallback = [=] { _closeRequests.fire({}); };
-
 	{
 		const auto close = Ui::CreateChild<Ui::RoundButton>(
 			this,
@@ -252,13 +255,6 @@ PreviewWrap::PreviewWrap(
 			});
 		}, tooltip->lifetime());
 	}
-
-	HistoryView::TTLVoiceStops(
-		item->fullId()
-	) | rpl::start_with_next([=] {
-		_last.use = true;
-		closeCallback();
-	}, lifetime());
 }
 
 rpl::producer<> PreviewWrap::closeRequests() const {
@@ -275,31 +271,19 @@ void PreviewWrap::paintEvent(QPaintEvent *e) {
 		return;
 	}
 
-	auto p = QPainter(this);
-	//p.fillRect(_viewport, QColor(255, 0, 0, 64));
-	//p.fillRect(_elementGeometry, QColor(0, 255, 0, 64));
-	//p.fillRect(_elementInner.current(), QColor(0, 0, 255, 64));
-	if (!_last.use) {
-		const auto size = _element->currentSize();
-		auto result = QImage(
-			size * style::DevicePixelRatio(),
-			QImage::Format_ARGB32_Premultiplied);
-		result.fill(Qt::transparent);
-		result.setDevicePixelRatio(style::DevicePixelRatio());
-		{
-			auto q = Painter(&result);
-			auto context = _theme->preparePaintContext(
-				_style.get(),
-				Rect(size),
-				Rect(size),
-				!window()->isActiveWindow());
-			context.outbg = _element->hasOutLayout();
-			_element->draw(q, context);
-		}
-		_last.frame = std::move(result);
-	}
+	auto p = Painter(this);
 	p.translate(_elementGeometry.topLeft());
-	p.drawImage(0, 0, _last.frame);
+	if (!_lastFrameCache.isNull()) {
+		p.drawImage(0, 0, _lastFrameCache);
+	} else {
+		auto context = _theme->preparePaintContext(
+			_style.get(),
+			Rect(_element->currentSize()),
+			Rect(_element->currentSize()),
+			!window()->isActiveWindow());
+		context.outbg = _element->hasOutLayout();
+		_element->draw(p, context);
+	}
 }
 
 rpl::producer<QRect> GlobalViewportForWindow(
diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_document.cpp
index 5c3632c33..3eb378a17 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_document.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_document.cpp
@@ -71,8 +71,13 @@ constexpr auto kAudioVoiceMsgUpdateView = crl::time(100);
 		st::historyFileInPause.width(),
 		st::historyFileInPause.height()));
 	const auto state = lifetime->make_state<State>();
-	state->start = Lottie::MakeIcon({
-		.name = u"voice_ttl_start"_q,
+	//state->start = Lottie::MakeIcon({
+	//	.name = u"voice_ttl_start"_q,
+	//	.color = &st::historyFileInIconFg,
+	//	.sizeOverride = iconSize,
+	//});
+	state->idle = Lottie::MakeIcon({
+		.name = u"voice_ttl_idle"_q,
 		.color = &st::historyFileInIconFg,
 		.sizeOverride = iconSize,
 	});

From 6033071e61ecb98818a0524eb71f9272b244d146 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Tue, 16 Jan 2024 12:53:23 +0400
Subject: [PATCH 62/73] Fix new taskbar "Quit Telegram" icon generation.

---
 Telegram/SourceFiles/platform/win/tray_win.cpp | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/Telegram/SourceFiles/platform/win/tray_win.cpp b/Telegram/SourceFiles/platform/win/tray_win.cpp
index 2a8e4615e..d2fa0af69 100644
--- a/Telegram/SourceFiles/platform/win/tray_win.cpp
+++ b/Telegram/SourceFiles/platform/win/tray_win.cpp
@@ -410,6 +410,8 @@ void WriteIco(const QString &path, std::vector<QImage> images) {
 		buffer.append(pngs[i]);
 	}
 
+	const auto dir = QFileInfo(path).dir();
+	dir.mkpath(dir.absolutePath());
 	auto f = QFile(path);
 	if (f.open(QIODevice::WriteOnly)) {
 		f.write(buffer);

From 3301d28615624463baa1243427503c22e76dfbe8 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Tue, 16 Jan 2024 12:53:50 +0400
Subject: [PATCH 63/73] Fix white glitch workaround on Windows.

---
 Telegram/lib_ui | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Telegram/lib_ui b/Telegram/lib_ui
index 08a19be80..564e354c1 160000
--- a/Telegram/lib_ui
+++ b/Telegram/lib_ui
@@ -1 +1 @@
-Subproject commit 08a19be802cef8ee801121906c5f0ce9d2fa65b0
+Subproject commit 564e354c1d4fabe906cb790babe6879e33faca54

From ac7958f33503d796e02ba5e364e5a04d762e73fa Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Tue, 16 Jan 2024 13:03:44 +0400
Subject: [PATCH 64/73] Remove ctrl+shift+N shortcuts by default.

Those are used in some input methods, including Farsi.

You can always add such bindings in tdata/shortcuts-custom.json for
the "account1", "account2", etc commands.

Fixes #27334.
---
 Telegram/SourceFiles/core/shortcuts.cpp | 18 +++++++++++++++---
 1 file changed, 15 insertions(+), 3 deletions(-)

diff --git a/Telegram/SourceFiles/core/shortcuts.cpp b/Telegram/SourceFiles/core/shortcuts.cpp
index ad4a79eb7..b2190393a 100644
--- a/Telegram/SourceFiles/core/shortcuts.cpp
+++ b/Telegram/SourceFiles/core/shortcuts.cpp
@@ -410,9 +410,9 @@ void Manager::fillDefaults() {
 		kShowAccount,
 		ranges::views::ints(1, ranges::unreachable));
 
-	for (const auto &[command, index] : accounts) {
-		set(u"%1+shift+%2"_q.arg(ctrl).arg(index), command);
-	}
+	//for (const auto &[command, index] : accounts) {
+	//	set(u"%1+shift+%2"_q.arg(ctrl).arg(index), command);
+	//}
 
 	set(u"%1+shift+down"_q.arg(ctrl), Command::FolderNext);
 	set(u"%1+shift+up"_q.arg(ctrl), Command::FolderPrevious);
@@ -458,6 +458,18 @@ void Manager::writeDefaultFile() {
 		}
 	}
 
+	// Commands without a default value.
+	for (const auto command : kShowAccount) {
+		const auto j = CommandNames.find(command);
+		if (j != CommandNames.end()) {
+			QJsonObject entry;
+			entry.insert(u"keys"_q, QJsonValue());
+			entry.insert(u"command"_q, j->second);
+			shortcuts.append(entry);
+		}
+	}
+
+
 	auto document = QJsonDocument();
 	document.setArray(shortcuts);
 	file.write(document.toJson(QJsonDocument::Indented));

From 2213bedc12c47952e6d5812d738c2ac07c69bb38 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Tue, 16 Jan 2024 13:14:36 +0400
Subject: [PATCH 65/73] Version 4.14.5.

- Allow sending one-time voice messages.
- Improve playing one-time voice and video messages.
- Remove Ctrl+Shift+[1-6] shortcuts by default,
some are used in input methods.
- Some bugs and glitches fixed.
---
 Telegram/Resources/uwp/AppX/AppxManifest.xml | 2 +-
 Telegram/Resources/winrc/Telegram.rc         | 8 ++++----
 Telegram/Resources/winrc/Updater.rc          | 8 ++++----
 Telegram/SourceFiles/core/version.h          | 4 ++--
 Telegram/build/version                       | 8 ++++----
 changelog.txt                                | 7 +++++++
 6 files changed, 22 insertions(+), 15 deletions(-)

diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml
index 8030e5cba..607a27620 100644
--- a/Telegram/Resources/uwp/AppX/AppxManifest.xml
+++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml
@@ -10,7 +10,7 @@
   <Identity Name="TelegramMessengerLLP.TelegramDesktop"
     ProcessorArchitecture="ARCHITECTURE"
     Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
-    Version="4.14.4.0" />
+    Version="4.14.5.0" />
   <Properties>
     <DisplayName>Telegram Desktop</DisplayName>
     <PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc
index 158616895..ffa88efad 100644
--- a/Telegram/Resources/winrc/Telegram.rc
+++ b/Telegram/Resources/winrc/Telegram.rc
@@ -44,8 +44,8 @@ IDI_ICON1               ICON                    "..\\art\\icon256.ico"
 //
 
 VS_VERSION_INFO VERSIONINFO
- FILEVERSION 4,14,4,0
- PRODUCTVERSION 4,14,4,0
+ FILEVERSION 4,14,5,0
+ PRODUCTVERSION 4,14,5,0
  FILEFLAGSMASK 0x3fL
 #ifdef _DEBUG
  FILEFLAGS 0x1L
@@ -62,10 +62,10 @@ BEGIN
         BEGIN
             VALUE "CompanyName", "Telegram FZ-LLC"
             VALUE "FileDescription", "Telegram Desktop"
-            VALUE "FileVersion", "4.14.4.0"
+            VALUE "FileVersion", "4.14.5.0"
             VALUE "LegalCopyright", "Copyright (C) 2014-2024"
             VALUE "ProductName", "Telegram Desktop"
-            VALUE "ProductVersion", "4.14.4.0"
+            VALUE "ProductVersion", "4.14.5.0"
         END
     END
     BLOCK "VarFileInfo"
diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc
index bfc6ab89c..3c128e7a5 100644
--- a/Telegram/Resources/winrc/Updater.rc
+++ b/Telegram/Resources/winrc/Updater.rc
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
 //
 
 VS_VERSION_INFO VERSIONINFO
- FILEVERSION 4,14,4,0
- PRODUCTVERSION 4,14,4,0
+ FILEVERSION 4,14,5,0
+ PRODUCTVERSION 4,14,5,0
  FILEFLAGSMASK 0x3fL
 #ifdef _DEBUG
  FILEFLAGS 0x1L
@@ -53,10 +53,10 @@ BEGIN
         BEGIN
             VALUE "CompanyName", "Telegram FZ-LLC"
             VALUE "FileDescription", "Telegram Desktop Updater"
-            VALUE "FileVersion", "4.14.4.0"
+            VALUE "FileVersion", "4.14.5.0"
             VALUE "LegalCopyright", "Copyright (C) 2014-2024"
             VALUE "ProductName", "Telegram Desktop"
-            VALUE "ProductVersion", "4.14.4.0"
+            VALUE "ProductVersion", "4.14.5.0"
         END
     END
     BLOCK "VarFileInfo"
diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h
index d6e18ed78..44cb36e25 100644
--- a/Telegram/SourceFiles/core/version.h
+++ b/Telegram/SourceFiles/core/version.h
@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
 constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
 constexpr auto AppName = "Telegram Desktop"_cs;
 constexpr auto AppFile = "Telegram"_cs;
-constexpr auto AppVersion = 4014004;
-constexpr auto AppVersionStr = "4.14.4";
+constexpr auto AppVersion = 4014005;
+constexpr auto AppVersionStr = "4.14.5";
 constexpr auto AppBetaVersion = false;
 constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
diff --git a/Telegram/build/version b/Telegram/build/version
index 7f191e54d..35cc3ece4 100644
--- a/Telegram/build/version
+++ b/Telegram/build/version
@@ -1,7 +1,7 @@
-AppVersion         4014004
+AppVersion         4014005
 AppVersionStrMajor 4.14
-AppVersionStrSmall 4.14.4
-AppVersionStr      4.14.4
+AppVersionStrSmall 4.14.5
+AppVersionStr      4.14.5
 BetaChannel        0
 AlphaVersion       0
-AppVersionOriginal 4.14.4
+AppVersionOriginal 4.14.5
diff --git a/changelog.txt b/changelog.txt
index 06857dfac..6c316d6e1 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,3 +1,10 @@
+4.14.5 (15.01.24)
+
+- Allow sending one-time voice messages.
+- Improve playing one-time voice and video messages.
+- Remove Ctrl+Shift+[1-6] shortcuts by default, some are used in input methods.
+- Some bugs and glitches fixed.
+
 4.14.4 (08.01.24)
 
 - Switch between logged in accounts using Ctrl+Shift+[1-6] shortcuts.

From ed027c23d0bafccfdb33c4283c43e4dc6e0700f8 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Tue, 16 Jan 2024 20:29:37 +0400
Subject: [PATCH 66/73] Version 4.14.5: Fix build with GCC.

---
 .../SourceFiles/chat_helpers/ttl_media_layer_widget.cpp     | 1 -
 Telegram/SourceFiles/core/shortcuts.cpp                     | 6 +++---
 Telegram/SourceFiles/dialogs/dialogs_widget.cpp             | 2 --
 Telegram/SourceFiles/history/history_inner_widget.cpp       | 2 --
 .../SourceFiles/history/view/media/history_view_gif.cpp     | 4 ----
 .../history/view/reactions/history_view_reactions.cpp       | 1 -
 6 files changed, 3 insertions(+), 13 deletions(-)

diff --git a/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp b/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp
index 552b050e0..fae8f0c2d 100644
--- a/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp
+++ b/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp
@@ -208,7 +208,6 @@ PreviewWrap::PreviewWrap(
 				_element->width(),
 				_element->height()
 			).translated(_viewport.topLeft());
-			const auto media = _element->media();
 			_elementInner = _element->innerGeometry().translated(
 				_elementGeometry.topLeft());
 			update();
diff --git a/Telegram/SourceFiles/core/shortcuts.cpp b/Telegram/SourceFiles/core/shortcuts.cpp
index b2190393a..545c9b55a 100644
--- a/Telegram/SourceFiles/core/shortcuts.cpp
+++ b/Telegram/SourceFiles/core/shortcuts.cpp
@@ -406,9 +406,9 @@ void Manager::fillDefaults() {
 		set(u"%1+%2"_q.arg(ctrl).arg(index), command);
 	}
 
-	auto &&accounts = ranges::views::zip(
-		kShowAccount,
-		ranges::views::ints(1, ranges::unreachable));
+	//auto &&accounts = ranges::views::zip(
+	//	kShowAccount,
+	//	ranges::views::ints(1, ranges::unreachable));
 
 	//for (const auto &[command, index] : accounts) {
 	//	set(u"%1+shift+%2"_q.arg(ctrl).arg(index), command);
diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
index b6f0d414b..9f2c9290c 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
@@ -1935,7 +1935,6 @@ void Widget::searchMessages(QString query, Key inChat) {
 	const auto inChatChanged = [&] {
 		const auto inPeer = inChat.peer();
 		const auto inTopic = inChat.topic();
-		const auto inSublist = inChat.sublist();
 		if (!inTopic
 			&& _openedForum
 			&& inPeer == _openedForum->channel()
@@ -2615,7 +2614,6 @@ bool Widget::setSearchInChat(
 	}
 	const auto peer = chat.peer();
 	const auto topic = chat.topic();
-	const auto sublist = chat.sublist();
 	const auto forum = peer ? peer->forum() : nullptr;
 	if (chat.folder() || (forum && !topic)) {
 		chat = Key();
diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp
index 9cf3ab373..17fb3257b 100644
--- a/Telegram/SourceFiles/history/history_inner_widget.cpp
+++ b/Telegram/SourceFiles/history/history_inner_widget.cpp
@@ -981,8 +981,6 @@ Ui::ChatPaintContext HistoryInner::preparePaintContext(
 		const QRect &clip) const {
 	const auto visibleAreaPositionGlobal = mapToGlobal(
 		QPoint(0, _visibleAreaTop));
-	const auto visibleAreaPositionLocal = mapFromGlobal(
-		visibleAreaPositionGlobal);
 	return _controller->preparePaintContext({
 		.theme = _theme.get(),
 		.clip = clip,
diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp
index ca29639ec..03164193b 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp
@@ -71,9 +71,6 @@ int gifMaxStatusWidth(DocumentData *document) {
 
 [[nodiscard]] HistoryView::TtlRoundPaintCallback CreateTtlPaintCallback(
 		Fn<void()> update) {
-	const auto iconSize = Size(std::min(
-		st::historyFileInPause.width(),
-		st::historyFileInPause.height()));
 	const auto centerMargins = Margins(st::historyFileInPause.width() * 3);
 
 	const auto renderer = std::make_shared<QSvgRenderer>(
@@ -144,7 +141,6 @@ Gif::Gif(
 	? std::make_unique<MediaSpoiler>()
 	: nullptr)
 , _downloadSize(Ui::FormatSizeText(_data->size)) {
-	auto hasDefaultDocumentLinks = false;
 	if (_data->isVideoMessage() && _parent->data()->media()->ttlSeconds()) {
 		if (_spoiler) {
 			_drawTtl = CreateTtlPaintCallback([=] { repaint(); });
diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp
index 292774899..39ae8087a 100644
--- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp
+++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp
@@ -408,7 +408,6 @@ void InlineList::paint(
 					: st->msgServiceBg())->c;
 			}
 
-			const auto radius = geometry.height() / 2.;
 			const auto fill = geometry.marginsAdded({
 				flipped ? bubbleSkip : 0,
 				0,

From 6686fe110d586f30a8668dc6e4a1861860a1bde3 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Tue, 16 Jan 2024 20:29:58 +0400
Subject: [PATCH 67/73] Fix release script working from Linux.

---
 Telegram/build/release.py | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/Telegram/build/release.py b/Telegram/build/release.py
index d7bee5077..4bcf93fe9 100644
--- a/Telegram/build/release.py
+++ b/Telegram/build/release.py
@@ -88,7 +88,8 @@ def appendSubmodules(appendTo, root, rootRevision):
       return False
     if not appendSubmodules(tmppath, subroot, revision):
       return False
-    if not invoke('gtar --concatenate --file=' + appendTo + '.tar ' + tmppath + '.tar'):
+    tar = 'tar' if sys.platform == 'linux' else 'gtar'
+    if not invoke(tar + ' --concatenate --file=' + appendTo + '.tar ' + tmppath + '.tar'):
       os.remove(appendTo + '.tar')
       os.remove(tmppath + '.tar')
       return False
@@ -163,7 +164,13 @@ if access_token == '':
   sys.exit(1)
 
 print('Version: ' + version_full)
-local_folder = expanduser("~") + '/Projects/backup/tdesktop/' + version_major + '/' + version_full
+local_base = expanduser("~") + '/Projects/backup/tdesktop'
+if not os.path.isdir(local_base):
+    local_base = '/mnt/c/Telegram/Projects/backup/tdesktop'
+    if not os.path.isdir(local_base):
+        print('Backup path not found: ' + local_base)
+        sys.exit(1)
+local_folder = local_base + '/' + version_major + '/' + version_full
 
 if stable == 1:
   if os.path.isdir(local_folder + '.beta'):

From 83fc19e14371beaad865371e7c0e79ea7e7a6374 Mon Sep 17 00:00:00 2001
From: 23rd <23rd@vivaldi.net>
Date: Tue, 16 Jan 2024 16:12:46 +0300
Subject: [PATCH 68/73] Changed behavior in voice record bar to create ttl
 button on demand.

---
 .../history_view_voice_record_bar.cpp         | 25 +++++++++++++------
 .../controls/history_view_voice_record_bar.h  |  2 +-
 2 files changed, 19 insertions(+), 8 deletions(-)

diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp
index 3714bbf8f..04c649c29 100644
--- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp
+++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp
@@ -292,9 +292,10 @@ TTLButton::TTLButton(
 , _st(st)
 , _rippleRect(Rect(Size(st::historyRecordLockTopShadow.width()))
 	- (st::historyRecordLockRippleMargin)) {
-	resize(Size(st::historyRecordLockTopShadow.width()));
+	QWidget::resize(Size(st::historyRecordLockTopShadow.width()));
+	Ui::AbstractButton::setDisabled(true);
 
-	setClickedCallback([=] {
+	Ui::AbstractButton::setClickedCallback([=] {
 		Ui::AbstractButton::setDisabled(!Ui::AbstractButton::isDisabled());
 		const auto isActive = !Ui::AbstractButton::isDisabled();
 		_activeAnimation.start(
@@ -306,7 +307,7 @@ TTLButton::TTLButton(
 
 	Ui::RpWidget::shownValue() | rpl::filter(
 		rpl::mappers::_1
-	) | rpl::skip(1) | rpl::take(1) | rpl::start_with_next([=] {
+	) | rpl::take(1) | rpl::start_with_next([=] {
 		auto text = rpl::conditional(
 			Core::App().settings().ttlVoiceClickTooltipHiddenValue(),
 			tr::lng_record_once_active_tooltip(
@@ -400,7 +401,7 @@ TTLButton::TTLButton(
 
 void TTLButton::clearState() {
 	Ui::AbstractButton::setDisabled(true);
-	update();
+	QWidget::update();
 	Ui::RpWidget::hide();
 }
 
@@ -1168,7 +1169,6 @@ VoiceRecordBar::VoiceRecordBar(
 , _show(std::move(descriptor.show))
 , _send(std::move(descriptor.send))
 , _lock(std::make_unique<RecordLock>(_outerContainer, _st.lock))
-, _ttlButton(std::make_unique<TTLButton>(_outerContainer, _st))
 , _level(std::make_unique<VoiceRecordButton>(_outerContainer, _st))
 , _cancel(std::make_unique<CancelButton>(this, _st, descriptor.recorderHeight))
 , _startTimer([=] { startRecording(); })
@@ -1247,6 +1247,9 @@ void VoiceRecordBar::updateLockGeometry() {
 void VoiceRecordBar::updateTTLGeometry(
 		TTLAnimationType type,
 		float64 progress) {
+	if (!_ttlButton) {
+		return;
+	}
 	const auto parent = parentWidget();
 	const auto me = Ui::MapFrom(_outerContainer, parent, geometry());
 	const auto anyTop = me.y() - st::historyRecordLockPosition.y();
@@ -1406,6 +1409,11 @@ void VoiceRecordBar::init() {
 	_lock->locks(
 	) | rpl::start_with_next([=] {
 		if (_hasTTLFilter && _hasTTLFilter()) {
+			if (!_ttlButton) {
+				_ttlButton = std::make_unique<TTLButton>(
+					_outerContainer,
+					_st);
+			}
 			_ttlButton->show();
 		}
 		updateTTLGeometry(TTLAnimationType::RightTopStatic, 0);
@@ -1869,7 +1877,7 @@ bool VoiceRecordBar::isRecordingByAnotherBar() const {
 }
 
 bool VoiceRecordBar::isTTLButtonShown() const {
-	return !_ttlButton->isHidden();
+	return _ttlButton && !_ttlButton->isHidden();
 }
 
 bool VoiceRecordBar::hasDuration() const {
@@ -1904,10 +1912,13 @@ void VoiceRecordBar::computeAndSetLockProgress(QPoint globalPos) {
 }
 
 bool VoiceRecordBar::peekTTLState() const {
-	return !_ttlButton->isDisabled();
+	return _ttlButton && !_ttlButton->isDisabled();
 }
 
 bool VoiceRecordBar::takeTTLState() const {
+	if (!_ttlButton) {
+		return false;
+	}
 	const auto hasTtl = !_ttlButton->isDisabled();
 	_ttlButton->clearState();
 	return hasTtl;
diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h
index 964614597..f1a58465e 100644
--- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h
+++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h
@@ -157,9 +157,9 @@ private:
 	const std::shared_ptr<ChatHelpers::Show> _show;
 	const std::shared_ptr<Ui::SendButton> _send;
 	const std::unique_ptr<RecordLock> _lock;
-	const std::unique_ptr<Ui::AbstractButton> _ttlButton;
 	const std::unique_ptr<VoiceRecordButton> _level;
 	const std::unique_ptr<CancelButton> _cancel;
+	std::unique_ptr<Ui::AbstractButton> _ttlButton;
 	std::unique_ptr<ListenWrap> _listen;
 
 	base::Timer _startTimer;

From c4526943483abbee6e43e269c401184decf6e8f4 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Tue, 16 Jan 2024 16:54:25 +0400
Subject: [PATCH 69/73] Pause infinite radial timer if anim::Disabled.

---
 Telegram/lib_ui | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Telegram/lib_ui b/Telegram/lib_ui
index 564e354c1..cb1a041ca 160000
--- a/Telegram/lib_ui
+++ b/Telegram/lib_ui
@@ -1 +1 @@
-Subproject commit 564e354c1d4fabe906cb790babe6879e33faca54
+Subproject commit cb1a041ca363e815cbb9f63804c02b2d02d9818d

From d3fdfe4b29e689de0e9269a385d61f1979c2f800 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Tue, 16 Jan 2024 21:09:45 +0400
Subject: [PATCH 70/73] Fix crash in themes editor.

Fixes #27366.
---
 .../SourceFiles/window/themes/window_theme_editor_block.cpp    | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/Telegram/SourceFiles/window/themes/window_theme_editor_block.cpp b/Telegram/SourceFiles/window/themes/window_theme_editor_block.cpp
index bd1e5c311..9c0c85cc3 100644
--- a/Telegram/SourceFiles/window/themes/window_theme_editor_block.cpp
+++ b/Telegram/SourceFiles/window/themes/window_theme_editor_block.cpp
@@ -331,9 +331,8 @@ void EditorBlock::activateRow(const Row &row) {
 			const auto state = editor->lifetime().make_state<State>();
 
 			const auto save = crl::guard(this, [=] {
-				saveEditing(editor->color());
 				state->cancelLifetime.destroy();
-				box->closeBox();
+				saveEditing(editor->color());
 			});
 			box->boxClosing(
 			) | rpl::start_with_next(crl::guard(this, [=] {

From 605b255e327946e0be94bad58acda80d2763cd6c Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Tue, 16 Jan 2024 21:12:06 +0400
Subject: [PATCH 71/73] Fix media viewer non-full-screen-ness.

---
 Telegram/lib_ui | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Telegram/lib_ui b/Telegram/lib_ui
index cb1a041ca..30b22ace0 160000
--- a/Telegram/lib_ui
+++ b/Telegram/lib_ui
@@ -1 +1 @@
-Subproject commit cb1a041ca363e815cbb9f63804c02b2d02d9818d
+Subproject commit 30b22ace0b67f72e2ab913fccf56842b863effad

From 5033b9ef0dde7d763ab955babcf9e51c913e3029 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Tue, 16 Jan 2024 21:43:19 +0400
Subject: [PATCH 72/73] Fix crash in ttl video, track editions.

---
 .../chat_helpers/ttl_media_layer_widget.cpp   | 97 +++++++++++++------
 1 file changed, 70 insertions(+), 27 deletions(-)

diff --git a/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp b/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp
index fae8f0c2d..f2445a166 100644
--- a/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp
+++ b/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp
@@ -100,6 +100,9 @@ public:
 
 private:
 	void paintEvent(QPaintEvent *e) override;
+	void createView();
+	[[nodiscard]] bool goodItem() const;
+	void clear();
 
 	const not_null<HistoryItem*> _item;
 	const std::unique_ptr<Ui::ChatStyle> _style;
@@ -162,6 +165,30 @@ PreviewWrap::PreviewWrap(
 			update(_elementGeometry);
 		}
 	}, lifetime());
+	session->data().itemViewRefreshRequest(
+	) | rpl::start_with_next([=](not_null<HistoryItem*> item) {
+		if (item == _item) {
+			if (goodItem()) {
+				createView();
+				update();
+			} else {
+				clear();
+				_closeRequests.fire({});
+			}
+		}
+	}, lifetime());
+	session->data().itemDataChanges(
+	) | rpl::start_with_next([=](not_null<HistoryItem*> item) {
+		if (item == _item) {
+			_element->itemDataChanged();
+		}
+	}, lifetime());
+	session->data().itemRemoved(
+	) | rpl::start_with_next([=](not_null<const HistoryItem*> item) {
+		if (item == _item) {
+			_closeRequests.fire({});
+		}
+	}, lifetime());
 
 	{
 		const auto close = Ui::CreateChild<Ui::RoundButton>(
@@ -187,32 +214,7 @@ PreviewWrap::PreviewWrap(
 	}
 
 	QWidget::setAttribute(Qt::WA_OpaquePaintEvent, false);
-	_element = _item->createView(_delegate.get());
-
-	{
-		_element->initDimensions();
-		rpl::combine(
-			sizeValue(),
-			_globalViewport.value()
-		) | rpl::start_with_next([=](QSize outer, QRect globalViewport) {
-			_viewport = globalViewport.isEmpty()
-				? rect()
-				: mapFromGlobal(globalViewport);
-			if (_viewport.width() < st::msgMinWidth) {
-				return;
-			}
-			_element->resizeGetHeight(_viewport.width());
-			_elementGeometry = QRect(
-				(_viewport.width() - _element->width()) / 2,
-				(_viewport.height() - _element->height()) / 2,
-				_element->width(),
-				_element->height()
-			).translated(_viewport.topLeft());
-			_elementInner = _element->innerGeometry().translated(
-				_elementGeometry.topLeft());
-			update();
-		}, _elementLifetime);
-	}
+	createView();
 
 	{
 		auto text = item->out()
@@ -260,11 +262,52 @@ rpl::producer<> PreviewWrap::closeRequests() const {
 	return _closeRequests.events();
 }
 
-PreviewWrap::~PreviewWrap() {
+bool PreviewWrap::goodItem() const {
+	const auto media = _item->media();
+	if (!media || !media->ttlSeconds()) {
+		return false;
+	}
+	const auto document = media->document();
+	return document
+		&& (document->isVoiceMessage() || document->isVideoMessage());
+}
+
+void PreviewWrap::createView() {
+	clear();
+	_element = _item->createView(_delegate.get());
+	_element->initDimensions();
+	rpl::combine(
+		sizeValue(),
+		_globalViewport.value()
+	) | rpl::start_with_next([=](QSize outer, QRect globalViewport) {
+		_viewport = globalViewport.isEmpty()
+			? rect()
+			: mapFromGlobal(globalViewport);
+		if (_viewport.width() < st::msgMinWidth) {
+			return;
+		}
+		_element->resizeGetHeight(_viewport.width());
+		_elementGeometry = QRect(
+			(_viewport.width() - _element->width()) / 2,
+			(_viewport.height() - _element->height()) / 2,
+			_element->width(),
+			_element->height()
+		).translated(_viewport.topLeft());
+		_elementInner = _element->innerGeometry().translated(
+			_elementGeometry.topLeft());
+		update();
+	}, _elementLifetime);
+}
+
+void PreviewWrap::clear() {
 	_elementLifetime.destroy();
 	_element = nullptr;
 }
 
+PreviewWrap::~PreviewWrap() {
+	clear();
+}
+
 void PreviewWrap::paintEvent(QPaintEvent *e) {
 	if (!_element || _elementGeometry.isEmpty()) {
 		return;

From c364383cf0cfd6949baa113069aa91b92f1a37fd Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Tue, 16 Jan 2024 21:44:56 +0400
Subject: [PATCH 73/73] Version 4.14.6.

- Fix one-time audio tooltip showing in wrong places.
- Fix media viewer showing above taskbar on Windows.
- Fix crash in one-time video message playback.
---
 Telegram/Resources/uwp/AppX/AppxManifest.xml | 2 +-
 Telegram/Resources/winrc/Telegram.rc         | 8 ++++----
 Telegram/Resources/winrc/Updater.rc          | 8 ++++----
 Telegram/SourceFiles/core/version.h          | 4 ++--
 Telegram/build/version                       | 8 ++++----
 changelog.txt                                | 6 ++++++
 6 files changed, 21 insertions(+), 15 deletions(-)

diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml
index 607a27620..732d62eb2 100644
--- a/Telegram/Resources/uwp/AppX/AppxManifest.xml
+++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml
@@ -10,7 +10,7 @@
   <Identity Name="TelegramMessengerLLP.TelegramDesktop"
     ProcessorArchitecture="ARCHITECTURE"
     Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
-    Version="4.14.5.0" />
+    Version="4.14.6.0" />
   <Properties>
     <DisplayName>Telegram Desktop</DisplayName>
     <PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc
index ffa88efad..8d807a2a5 100644
--- a/Telegram/Resources/winrc/Telegram.rc
+++ b/Telegram/Resources/winrc/Telegram.rc
@@ -44,8 +44,8 @@ IDI_ICON1               ICON                    "..\\art\\icon256.ico"
 //
 
 VS_VERSION_INFO VERSIONINFO
- FILEVERSION 4,14,5,0
- PRODUCTVERSION 4,14,5,0
+ FILEVERSION 4,14,6,0
+ PRODUCTVERSION 4,14,6,0
  FILEFLAGSMASK 0x3fL
 #ifdef _DEBUG
  FILEFLAGS 0x1L
@@ -62,10 +62,10 @@ BEGIN
         BEGIN
             VALUE "CompanyName", "Telegram FZ-LLC"
             VALUE "FileDescription", "Telegram Desktop"
-            VALUE "FileVersion", "4.14.5.0"
+            VALUE "FileVersion", "4.14.6.0"
             VALUE "LegalCopyright", "Copyright (C) 2014-2024"
             VALUE "ProductName", "Telegram Desktop"
-            VALUE "ProductVersion", "4.14.5.0"
+            VALUE "ProductVersion", "4.14.6.0"
         END
     END
     BLOCK "VarFileInfo"
diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc
index 3c128e7a5..b4d675b3f 100644
--- a/Telegram/Resources/winrc/Updater.rc
+++ b/Telegram/Resources/winrc/Updater.rc
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
 //
 
 VS_VERSION_INFO VERSIONINFO
- FILEVERSION 4,14,5,0
- PRODUCTVERSION 4,14,5,0
+ FILEVERSION 4,14,6,0
+ PRODUCTVERSION 4,14,6,0
  FILEFLAGSMASK 0x3fL
 #ifdef _DEBUG
  FILEFLAGS 0x1L
@@ -53,10 +53,10 @@ BEGIN
         BEGIN
             VALUE "CompanyName", "Telegram FZ-LLC"
             VALUE "FileDescription", "Telegram Desktop Updater"
-            VALUE "FileVersion", "4.14.5.0"
+            VALUE "FileVersion", "4.14.6.0"
             VALUE "LegalCopyright", "Copyright (C) 2014-2024"
             VALUE "ProductName", "Telegram Desktop"
-            VALUE "ProductVersion", "4.14.5.0"
+            VALUE "ProductVersion", "4.14.6.0"
         END
     END
     BLOCK "VarFileInfo"
diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h
index 44cb36e25..dfebfcca0 100644
--- a/Telegram/SourceFiles/core/version.h
+++ b/Telegram/SourceFiles/core/version.h
@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
 constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
 constexpr auto AppName = "Telegram Desktop"_cs;
 constexpr auto AppFile = "Telegram"_cs;
-constexpr auto AppVersion = 4014005;
-constexpr auto AppVersionStr = "4.14.5";
+constexpr auto AppVersion = 4014006;
+constexpr auto AppVersionStr = "4.14.6";
 constexpr auto AppBetaVersion = false;
 constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
diff --git a/Telegram/build/version b/Telegram/build/version
index 35cc3ece4..1e9cf236b 100644
--- a/Telegram/build/version
+++ b/Telegram/build/version
@@ -1,7 +1,7 @@
-AppVersion         4014005
+AppVersion         4014006
 AppVersionStrMajor 4.14
-AppVersionStrSmall 4.14.5
-AppVersionStr      4.14.5
+AppVersionStrSmall 4.14.6
+AppVersionStr      4.14.6
 BetaChannel        0
 AlphaVersion       0
-AppVersionOriginal 4.14.5
+AppVersionOriginal 4.14.6
diff --git a/changelog.txt b/changelog.txt
index 6c316d6e1..996980a6c 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,3 +1,9 @@
+4.14.6 (15.01.24)
+
+- Fix one-time audio tooltip showing in wrong places.
+- Fix media viewer showing above taskbar on Windows.
+- Fix crash in one-time video message playback.
+
 4.14.5 (15.01.24)
 
 - Allow sending one-time voice messages.