From 610e0e79139ab01aabd67c8a81ef2213c57c3605 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Tue, 22 Aug 2023 19:13:21 +0200
Subject: [PATCH 01/29] Start default notification settings.

---
 Telegram/CMakeLists.txt                       |   2 +
 Telegram/Resources/langs/lang.strings         |  29 ++-
 .../boxes/peers/edit_peer_permissions_box.cpp |   5 +-
 Telegram/SourceFiles/settings/settings.style  |   8 +
 .../settings/settings_notifications.cpp       | 203 +++++++++++-----
 .../settings/settings_notifications.h         |   4 +
 .../settings/settings_notifications_type.cpp  | 225 ++++++++++++++++++
 .../settings/settings_notifications_type.h    |  64 +++++
 8 files changed, 475 insertions(+), 65 deletions(-)
 create mode 100644 Telegram/SourceFiles/settings/settings_notifications_type.cpp
 create mode 100644 Telegram/SourceFiles/settings/settings_notifications_type.h

diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt
index f15f85198..8de1d1e8b 100644
--- a/Telegram/CMakeLists.txt
+++ b/Telegram/CMakeLists.txt
@@ -1261,6 +1261,8 @@ PRIVATE
     settings/settings_main.h
     settings/settings_notifications.cpp
     settings/settings_notifications.h
+    settings/settings_notifications_type.cpp
+    settings/settings_notifications_type.h
     settings/settings_power_saving.cpp
     settings/settings_power_saving.h
     settings/settings_premium.cpp
diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index 31f384f0a..c01e2f6d8 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -451,6 +451,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 "lng_settings_show_from" = "Show notifications from";
 "lng_settings_notify_all" = "All accounts";
 "lng_settings_notify_all_about" = "Turn this off if you want to receive notifications only from the account you are currently using.";
+"lng_settings_notify_global" = "Global settings";
 "lng_settings_notify_title" = "Notifications for chats";
 "lng_settings_desktop_notify" = "Desktop notifications";
 "lng_settings_native_title" = "Native notifications";
@@ -458,8 +459,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 "lng_settings_use_native_notifications" = "Use native notifications";
 "lng_settings_notifications_position" = "Location on the screen";
 "lng_settings_notifications_count" = "Notifications count";
-"lng_settings_sound_notify" = "Play sound";
-"lng_settings_sound_notify_off" = "Off";
+"lng_settings_sound_allowed" = "Allow sound";
 "lng_settings_alert_windows" = "Flash the taskbar icon";
 "lng_settings_alert_mac" = "Bounce the dock icon";
 "lng_settings_alert_linux" = "Draw attention to the window";
@@ -480,6 +480,31 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 "lng_notification_hide_all" = "Hide all";
 "lng_notification_sample" = "This is a sample notification";
 "lng_notification_reminder" = "Reminder";
+"lng_notification_private_chats" = "Private chats";
+"lng_notification_groups" = "Groups";
+"lng_notification_channels" = "Channels";
+"lng_notification_click_to_change" = "Click here to change";
+"lng_notification_on" = "On, {exceptions}";
+"lng_notification_off" = "Off, {exceptions}";
+"lng_notification_exceptions#one" = "{count} exception";
+"lng_notification_exceptions#other" = "{count} exceptions";
+"lng_notification_exceptions_title" = "Exceptions";
+"lng_notification_title_private_chats" = "Notifications for private chats";
+"lng_notification_about_private_chats#one" = "Please note that **{count} chat** is listed as an exception and won't be affected by this change.";
+"lng_notification_about_private_chats#other" = "Please note that **{count} chats** are listed as exceptions and won't be affected by this change.";
+"lng_notification_title_groups" = "Notifications for groups";
+"lng_notification_about_groups#one" = "Please note that **{count} group** is listed as an exception and won't be affected by this change.";
+"lng_notification_about_groups#other" = "Please note that **{count} groups** are listed as exceptions and won't be affected by this change.";
+"lng_notification_title_channels" = "Notifications for channels";
+"lng_notification_about_channels#one" = "Please note that **{count} channel** is listed as an exception and won't be affected by this change.";
+"lng_notification_about_channels#other" = "Please note that **{count} channels** are listed as exceptions and won't be affected by this change.";
+"lng_notification_enable" = "Enable notifications";
+"lng_notification_sound" = "Sound";
+"lng_notification_tone" = "Notification tone";
+"lng_notification_exceptions_muted" = "Muted";
+"lng_notification_exceptions_unmuted" = "Unmuted";
+"lng_notification_exceptions_add" = "Add an exception";
+"lng_notification_exceptions_clear" = "Delete all exceptions";
 
 "lng_reaction_text" = "{reaction} to your «{text}»";
 "lng_reaction_notext" = "{reaction} to your message";
diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp
index 4c33b38fc..97a86be3b 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp
+++ b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp
@@ -319,16 +319,15 @@ not_null<Ui::RpWidget*> AddInnerToggle(
 		button->geometryValue(
 		) | rpl::start_with_next([=](const QRect &r) {
 			const auto w = st::rightsButtonToggleWidth;
-			constexpr auto kLineWidth = int(1);
 			toggleButton->setGeometry(
 				r.x() + r.width() - w,
 				r.y(),
 				w,
 				r.height());
 			separator->setGeometry(
-				toggleButton->x() - kLineWidth,
+				toggleButton->x() - st::lineWidth,
 				r.y() + (r.height() - separatorHeight) / 2,
-				kLineWidth,
+				st::lineWidth,
 				separatorHeight);
 		}, toggleButton->lifetime());
 
diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style
index 4f53c9a33..15bf57077 100644
--- a/Telegram/SourceFiles/settings/settings.style
+++ b/Telegram/SourceFiles/settings/settings.style
@@ -528,6 +528,14 @@ settingsBlockedList: PeerList(peerListBox) {
 	padding: margins(0px, 0px, 0px, membersMarginBottom);
 }
 
+settingsNotificationType: SettingsButton(settingsButton) {
+	height: 40px;
+	padding: margins(60px, 4px, 22px, 4px);
+}
+settingsNotificationTypeDetails: FlatLabel(defaultFlatLabel) {
+	textFg: windowSubTextFg;
+}
+
 requestPeerRestriction: FlatLabel(defaultFlatLabel) {
 	minWidth: 240px;
 	textFg: membersAboutLimitFg;
diff --git a/Telegram/SourceFiles/settings/settings_notifications.cpp b/Telegram/SourceFiles/settings/settings_notifications.cpp
index 87abcbc01..cbe3f6c8a 100644
--- a/Telegram/SourceFiles/settings/settings_notifications.cpp
+++ b/Telegram/SourceFiles/settings/settings_notifications.cpp
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "settings/settings_notifications.h"
 
 #include "settings/settings_common.h"
+#include "settings/settings_notifications_type.h"
 #include "ui/controls/chat_service_checkbox.h"
 #include "ui/effects/animations.h"
 #include "ui/wrap/vertical_layout.h"
@@ -140,6 +141,106 @@ private:
 
 };
 
+void AddTypeButton(
+		not_null<Ui::VerticalLayout*> container,
+		not_null<Window::SessionController*> controller,
+		Data::DefaultNotify type,
+		Fn<void(Type)> showOther) {
+	using Type = Data::DefaultNotify;
+	auto label = [&] {
+		switch (type) {
+		case Type::User: return tr::lng_notification_private_chats();
+		case Type::Group: return tr::lng_notification_groups();
+		case Type::Broadcast: return tr::lng_notification_channels();
+		}
+		Unexpected("Type value in AddTypeButton.");
+	}();
+	const auto icon = [&] {
+		switch (type) {
+		case Type::User: return &st::menuIconProfile;
+		case Type::Group: return &st::menuIconGroups;
+		case Type::Broadcast: return &st::menuIconChannel;
+		}
+		Unexpected("Type value in AddTypeButton.");
+	}();
+	const auto button = AddButton(
+		container,
+		std::move(label),
+		st::settingsNotificationType,
+		{ icon });
+	button->setClickedCallback([=] {
+		showOther(NotificationsTypeId(type));
+	});
+
+	const auto &st = st::settingsNotificationType;
+	const auto details = Ui::CreateChild<Ui::FlatLabel>(
+		button.get(),
+		tr::lng_notification_click_to_change(),
+		st::settingsNotificationTypeDetails);
+	details->show();
+	details->moveToLeft(
+		st.padding.left(),
+		st.padding.top() + st.height - details->height());
+	details->setAttribute(Qt::WA_TransparentForMouseEvents);
+
+	const auto session = &controller->session();
+	const auto toggleButton = Ui::CreateChild<Ui::SettingsButton>(
+		container.get(),
+		nullptr,
+		st);
+	const auto checkView = toggleButton->lifetime().make_state<Ui::ToggleView>(
+		st.toggle,
+		NotificationsEnabledForType(session, type),
+		[=] { toggleButton->update(); });
+
+	const auto separator = Ui::CreateChild<Ui::RpWidget>(container.get());
+	separator->paintRequest(
+	) | rpl::start_with_next([=, bg = st.textBgOver] {
+		auto p = QPainter(separator);
+		p.fillRect(separator->rect(), bg);
+	}, separator->lifetime());
+	const auto separatorHeight = st.height - 2 * st.toggle.border;
+	button->geometryValue(
+	) | rpl::start_with_next([=](const QRect &r) {
+		const auto w = st::rightsButtonToggleWidth;
+		toggleButton->setGeometry(
+			r.x() + r.width() - w,
+			r.y(),
+			w,
+			r.height());
+		separator->setGeometry(
+			toggleButton->x() - st::lineWidth,
+			r.y() + (r.height() - separatorHeight) / 2,
+			st::lineWidth,
+			separatorHeight);
+	}, toggleButton->lifetime());
+
+	const auto checkWidget = Ui::CreateChild<Ui::RpWidget>(toggleButton);
+	checkWidget->resize(checkView->getSize());
+	checkWidget->paintRequest(
+	) | rpl::start_with_next([=] {
+		auto p = QPainter(checkWidget);
+		checkView->paint(p, 0, 0, checkWidget->width());
+	}, checkWidget->lifetime());
+	toggleButton->sizeValue(
+	) | rpl::start_with_next([=](const QSize &s) {
+		checkWidget->moveToRight(
+			st.toggleSkip,
+			(s.height() - checkWidget->height()) / 2);
+	}, toggleButton->lifetime());
+
+	toggleButton->clicks(
+	) | rpl::start_with_next([=] {
+		const auto enabled = !checkView->checked();
+		const auto settings = &session->data().notifySettings();
+		checkView->setChecked(enabled, anim::type::normal);
+		settings->defaultUpdate(type, Data::MuteValue{
+			.unmute = enabled,
+			.forever = !enabled,
+		});
+	}, toggleButton->lifetime());
+}
+
 NotificationsCount::NotificationsCount(
 	QWidget *parent,
 	not_null<Window::SessionController*> controller)
@@ -788,15 +889,16 @@ void SetupMultiAccountNotifications(
 
 void SetupNotificationsContent(
 		not_null<Window::SessionController*> controller,
-		not_null<Ui::VerticalLayout*> container) {
+		not_null<Ui::VerticalLayout*> container,
+		Fn<void(Type)> showOther) {
 	using namespace rpl::mappers;
 
-	AddSkip(container);
+	AddSkip(container, st::settingsPrivacySkip);
 
 	using NotifyView = Core::Settings::NotifyView;
 	SetupMultiAccountNotifications(controller, container);
 
-	AddSubsectionTitle(container, tr::lng_settings_notify_title());
+	AddSubsectionTitle(container, tr::lng_settings_notify_global());
 
 	const auto session = &controller->session();
 	const auto checkbox = [&](
@@ -842,41 +944,15 @@ void SetupNotificationsContent(
 		flashbounceToggles->events_starting_with(
 			settings.flashBounceNotify()));
 
-	const auto soundLabel = container->lifetime(
-	).make_state<rpl::event_stream<QString>>();
-	const auto soundValue = [=] {
-		const auto owner = &controller->session().data();
-		const auto &settings = owner->notifySettings().defaultSettings(
-			Data::DefaultNotify::User);
-		return !Core::App().settings().soundNotify()
-			? Data::NotifySound{ .none = true }
-			: settings.sound().value_or(Data::NotifySound());
+	const auto soundAllowed = container->lifetime(
+	).make_state<rpl::event_stream<bool>>();
+	const auto allowed = [=] {
+		return Core::App().settings().soundNotify();
 	};
-	const auto label = [=] {
-		const auto now = soundValue();
-		const auto owner = &controller->session().data();
-		return now.none
-			? tr::lng_settings_sound_notify_off(tr::now)
-			: !now.id
-			? tr::lng_ringtones_box_default(tr::now)
-			: ExtractRingtoneName(owner->document(now.id));
-	};
-	controller->session().data().notifySettings().defaultUpdates(
-		Data::DefaultNotify::User
-	) | rpl::start_with_next([=] {
-		soundLabel->fire(label());
-	}, container->lifetime());
-	controller->session().api().ringtones().listUpdates(
-	) | rpl::start_with_next([=] {
-		soundLabel->fire(label());
-	}, container->lifetime());
-
-	const auto sound = AddButtonWithLabel(
-		container,
-		tr::lng_settings_sound_notify(),
-		soundLabel->events_starting_with(label()),
-		st::settingsButton,
-		{ &st::menuIconSoundOn });
+	const auto sound = addCheckbox(
+		tr::lng_settings_sound_allowed(),
+		{ &st::menuIconUnmute },
+		soundAllowed->events_starting_with(allowed()));
 
 	AddSkip(container);
 
@@ -896,6 +972,17 @@ void SetupNotificationsContent(
 	previewDivider->toggle(!settings.desktopNotify(), anim::type::instant);
 
 	AddSkip(container, st::notifyPreviewBottomSkip);
+	AddSubsectionTitle(container, tr::lng_settings_notify_title());
+	const auto addType = [&](Data::DefaultNotify type) {
+		AddTypeButton(container, controller, type, showOther);
+	};
+	addType(Data::DefaultNotify::User);
+	addType(Data::DefaultNotify::Group);
+	addType(Data::DefaultNotify::Broadcast);
+
+	AddSkip(container, st::settingsCheckboxesSkip);
+	AddDivider(container);
+	AddSkip(container, st::settingsCheckboxesSkip);
 	AddSubsectionTitle(container, tr::lng_settings_events_title());
 
 	auto joinSilent = rpl::single(
@@ -1016,6 +1103,14 @@ void SetupNotificationsContent(
 		changed(Change::DesktopEnabled);
 	}, desktop->lifetime());
 
+	sound->toggledChanges(
+	) | rpl::filter([](bool checked) {
+		return (checked != Core::App().settings().soundNotify());
+	}) | rpl::start_with_next([=](bool checked) {
+		Core::App().settings().setSoundNotify(checked);
+		changed(Change::SoundEnabled);
+	}, sound->lifetime());
+
 	name->checkedChanges(
 	) | rpl::map([=](bool checked) {
 		if (!checked) {
@@ -1048,25 +1143,6 @@ void SetupNotificationsContent(
 		changed(Change::ViewParams);
 	}, preview->lifetime());
 
-	sound->setClickedCallback([=] {
-		controller->show(Box(RingtonesBox, session, soundValue(), [=](
-				Data::NotifySound sound) {
-			Core::App().settings().setSoundNotify(!sound.none);
-			if (!sound.none) {
-				using Type = Data::DefaultNotify;
-				const auto owner = &controller->session().data();
-				auto &settings = owner->notifySettings();
-				const auto updateType = [&](Type type) {
-					settings.defaultUpdate(type, {}, {}, sound);
-				};
-				updateType(Type::User);
-				updateType(Type::Group);
-				updateType(Type::Broadcast);
-			}
-			changed(Change::SoundEnabled);
-		}));
-	});
-
 	flashbounce->toggledChanges(
 	) | rpl::filter([](bool checked) {
 		return (checked != Core::App().settings().flashBounceNotify());
@@ -1104,7 +1180,7 @@ void SetupNotificationsContent(
 		} else if (change == Change::ViewParams) {
 			//
 		} else if (change == Change::SoundEnabled) {
-			soundLabel->fire(label());
+			soundAllowed->fire(allowed());
 		} else if (change == Change::FlashBounceEnabled) {
 			flashbounceToggles->fire(
 				Core::App().settings().flashBounceNotify());
@@ -1131,8 +1207,9 @@ void SetupNotificationsContent(
 
 void SetupNotifications(
 		not_null<Window::SessionController*> controller,
-		not_null<Ui::VerticalLayout*> container) {
-	SetupNotificationsContent(controller, container);
+		not_null<Ui::VerticalLayout*> container,
+		Fn<void(Type)> showOther) {
+	SetupNotificationsContent(controller, container, std::move(showOther));
 }
 
 } // namespace
@@ -1148,11 +1225,17 @@ rpl::producer<QString> Notifications::title() {
 	return tr::lng_settings_section_notify();
 }
 
+rpl::producer<Type> Notifications::sectionShowOther() {
+	return _showOther.events();
+}
+
 void Notifications::setupContent(
 		not_null<Window::SessionController*> controller) {
 	const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
 
-	SetupNotifications(controller, content);
+	SetupNotifications(controller, content, [=](Type type) {
+		_showOther.fire_copy(type);
+	});
 
 	Ui::ResizeFitChild(this, content);
 }
diff --git a/Telegram/SourceFiles/settings/settings_notifications.h b/Telegram/SourceFiles/settings/settings_notifications.h
index d895e20f2..d75318230 100644
--- a/Telegram/SourceFiles/settings/settings_notifications.h
+++ b/Telegram/SourceFiles/settings/settings_notifications.h
@@ -19,9 +19,13 @@ public:
 
 	[[nodiscard]] rpl::producer<QString> title() override;
 
+	rpl::producer<Type> sectionShowOther() override;
+
 private:
 	void setupContent(not_null<Window::SessionController*> controller);
 
+	rpl::event_stream<Type> _showOther;
+
 };
 
 } // namespace Settings
diff --git a/Telegram/SourceFiles/settings/settings_notifications_type.cpp b/Telegram/SourceFiles/settings/settings_notifications_type.cpp
new file mode 100644
index 000000000..29699a398
--- /dev/null
+++ b/Telegram/SourceFiles/settings/settings_notifications_type.cpp
@@ -0,0 +1,225 @@
+/*
+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 "settings/settings_notifications_type.h"
+
+#include "api/api_ringtones.h"
+#include "apiwrap.h"
+#include "base/unixtime.h"
+#include "boxes/ringtones_box.h"
+#include "data/notify/data_notify_settings.h"
+#include "data/data_session.h"
+#include "lang/lang_keys.h"
+#include "main/main_session.h"
+#include "ui/widgets/buttons.h"
+#include "ui/wrap/slide_wrap.h"
+#include "ui/wrap/vertical_layout.h"
+#include "window/window_session_controller.h"
+#include "styles/style_menu_icons.h"
+#include "styles/style_settings.h"
+
+namespace Settings {
+namespace {
+
+using Notify = Data::DefaultNotify;
+
+template <Notify kType>
+[[nodiscard]] Type Id() {
+	return &NotificationsTypeMetaImplementation<kType>::Meta;
+}
+
+[[nodiscard]] rpl::producer<QString> Title(Notify type) {
+	switch (type) {
+	case Notify::User: return tr::lng_notification_title_private_chats();
+	case Notify::Group: return tr::lng_notification_title_groups();
+	case Notify::Broadcast: return tr::lng_notification_title_channels();
+	}
+	Unexpected("Type in Title.");
+}
+
+void SetupChecks(
+		not_null<Ui::VerticalLayout*> container,
+		not_null<Window::SessionController*> controller,
+		Notify type) {
+	AddSubsectionTitle(container, Title(type));
+
+	const auto session = &controller->session();
+	const auto settings = &session->data().notifySettings();
+
+	const auto enabled = container->add(
+		CreateButton(
+			container,
+			tr::lng_notification_enable(),
+			st::settingsButton,
+			{ &st::menuIconNotifications }));
+	enabled->toggleOn(NotificationsEnabledForTypeValue(session, type));
+
+	const auto soundWrap = container->add(
+		object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
+			container,
+			object_ptr<Ui::VerticalLayout>(container)));
+	soundWrap->toggleOn(enabled->toggledValue());
+	soundWrap->finishAnimating();
+
+	const auto soundInner = soundWrap->entity();
+	const auto soundValue = [=] {
+		const auto sound = settings->defaultSettings(type).sound();
+		return !sound || !sound->none;
+	};
+	const auto sound = soundInner->add(
+		CreateButton(
+			soundInner,
+			tr::lng_notification_sound(),
+			st::settingsButton,
+			{ &st::menuIconUnmute }));
+	sound->toggleOn(rpl::single(
+		soundValue()
+	) | rpl::then(settings->defaultUpdates(
+		type
+	) | rpl::map([=] { return soundValue(); })));
+
+	const auto toneWrap = soundInner->add(
+		object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
+			container,
+			object_ptr<Ui::VerticalLayout>(container)));
+	toneWrap->toggleOn(sound->toggledValue());
+	toneWrap->finishAnimating();
+
+	const auto toneInner = toneWrap->entity();
+	const auto toneLabel = toneInner->lifetime(
+	).make_state<rpl::event_stream<QString>>();
+	const auto toneValue = [=] {
+		const auto sound = settings->defaultSettings(type).sound();
+		return sound.value_or(Data::NotifySound());
+	};
+	const auto label = [=] {
+		const auto now = toneValue();
+		return !now.id
+			? tr::lng_ringtones_box_default(tr::now)
+			: ExtractRingtoneName(session->data().document(now.id));
+	};
+	settings->defaultUpdates(
+		Data::DefaultNotify::User
+	) | rpl::start_with_next([=] {
+		toneLabel->fire(label());
+	}, toneInner->lifetime());
+	session->api().ringtones().listUpdates(
+	) | rpl::start_with_next([=] {
+		toneLabel->fire(label());
+	}, toneInner->lifetime());
+
+	const auto tone = AddButtonWithLabel(
+		toneInner,
+		tr::lng_notification_tone(),
+		toneLabel->events_starting_with(label()),
+		st::settingsButton,
+		{ &st::menuIconSoundOn });
+
+	enabled->toggledValue(
+	) | rpl::filter([=](bool value) {
+		return (value != NotificationsEnabledForType(session, type));
+	}) | rpl::start_with_next([=](bool value) {
+		settings->defaultUpdate(type, Data::MuteValue{
+			.unmute = value,
+			.forever = !value,
+		});
+	}, sound->lifetime());
+
+	sound->toggledValue(
+	) | rpl::filter([=](bool enabled) {
+		const auto sound = settings->defaultSettings(type).sound();
+		return (!sound || !sound->none) != enabled;
+	}) | rpl::start_with_next([=](bool enabled) {
+		const auto value = Data::NotifySound{ .none = !enabled };
+		settings->defaultUpdate(type, {}, {}, value);
+	}, sound->lifetime());
+
+	tone->setClickedCallback([=] {
+		controller->show(Box(RingtonesBox, session, toneValue(), [=](
+				Data::NotifySound sound) {
+			settings->defaultUpdate(type, {}, {}, sound);
+		}));
+	});
+}
+
+void SetupExceptions(
+	not_null<Ui::VerticalLayout*> container,
+	not_null<Window::SessionController*> controller,
+	Notify type) {
+}
+
+} // namespace
+
+Type NotificationsTypeId(Notify type) {
+	switch (type) {
+	case Notify::User: return Id<Notify::User>();
+	case Notify::Group: return Id<Notify::Group>();
+	case Notify::Broadcast: return Id<Notify::Broadcast>();
+	}
+	Unexpected("Type in NotificationTypeId.");
+}
+
+NotificationsType::NotificationsType(
+	QWidget *parent,
+	not_null<Window::SessionController*> controller,
+	Notify type)
+: AbstractSection(parent)
+, _type(type) {
+	setupContent(controller);
+}
+
+rpl::producer<QString> NotificationsType::title() {
+	switch (_type) {
+	case Notify::User: return tr::lng_notification_private_chats();
+	case Notify::Group: return tr::lng_notification_groups();
+	case Notify::Broadcast: return tr::lng_notification_channels();
+	}
+	Unexpected("Type in NotificationsType.");
+}
+
+Type NotificationsType::id() const {
+	return NotificationsTypeId(_type);
+}
+
+void NotificationsType::setupContent(
+		not_null<Window::SessionController*> controller) {
+	const auto container = Ui::CreateChild<Ui::VerticalLayout>(this);
+
+	AddSkip(container, st::settingsPrivacySkip);
+	SetupChecks(container, controller, _type);
+
+	AddSkip(container);
+	AddDivider(container);
+	AddSkip(container);
+
+	SetupExceptions(container, controller, _type);
+
+	Ui::ResizeFitChild(this, container);
+}
+
+bool NotificationsEnabledForType(
+		not_null<Main::Session*> session,
+		Notify type) {
+	const auto settings = &session->data().notifySettings();
+	const auto until = settings->defaultSettings(type).muteUntil();
+	return until && (*until <= base::unixtime::now());
+}
+
+rpl::producer<bool> NotificationsEnabledForTypeValue(
+		not_null<Main::Session*> session,
+		Data::DefaultNotify type) {
+	const auto settings = &session->data().notifySettings();
+	return rpl::single(
+		rpl::empty
+	) | rpl::then(
+		settings->defaultUpdates(type)
+	) | rpl::map([=] {
+		return NotificationsEnabledForType(session, type);
+	});
+}
+
+} // namespace Settings
diff --git a/Telegram/SourceFiles/settings/settings_notifications_type.h b/Telegram/SourceFiles/settings/settings_notifications_type.h
new file mode 100644
index 000000000..4cb04c7a2
--- /dev/null
+++ b/Telegram/SourceFiles/settings/settings_notifications_type.h
@@ -0,0 +1,64 @@
+/*
+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 "settings/settings_common.h"
+#include "data/notify/data_notify_settings.h"
+
+namespace Data {
+enum class DefaultNotify;
+} // namespace Data
+
+namespace Settings {
+
+class NotificationsType;
+
+template <Data::DefaultNotify kType>
+struct NotificationsTypeMetaImplementation : SectionMeta {
+	object_ptr<AbstractSection> create(
+		not_null<QWidget*> parent,
+		not_null<Window::SessionController*> controller
+	) const final override {
+		return object_ptr<NotificationsType>(parent, controller, kType);
+	}
+
+	[[nodiscard]] static not_null<SectionMeta*> Meta() {
+		static NotificationsTypeMetaImplementation result;
+		return &result;
+	}
+};
+
+[[nodiscard]] Type NotificationsTypeId(Data::DefaultNotify type);
+
+class NotificationsType : public AbstractSection {
+public:
+	NotificationsType(
+		QWidget *parent,
+		not_null<Window::SessionController*> controller,
+		Data::DefaultNotify type);
+
+	[[nodiscard]] rpl::producer<QString> title() override;
+
+	[[nodiscard]] Type id() const final override;
+
+private:
+	void setupContent(not_null<Window::SessionController*> controller);
+
+	Data::DefaultNotify _type;
+
+};
+
+[[nodiscard]] bool NotificationsEnabledForType(
+	not_null<Main::Session*> session,
+	Data::DefaultNotify type);
+
+[[nodiscard]] rpl::producer<bool> NotificationsEnabledForTypeValue(
+	not_null<Main::Session*> session,
+	Data::DefaultNotify type);
+
+} // namespace Settings

From 518f0e22cdf5d2076b80f83e3f431cc030b79a96 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Wed, 23 Aug 2023 11:23:28 +0200
Subject: [PATCH 02/29] Improve small blocklist layout, fix heightMin.

---
 Telegram/SourceFiles/settings/settings.style  |  3 +-
 .../settings/settings_blocked_peers.cpp       | 30 +++++++++++++++++--
 Telegram/lib_ui                               |  2 +-
 3 files changed, 31 insertions(+), 4 deletions(-)

diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style
index 15bf57077..45173b569 100644
--- a/Telegram/SourceFiles/settings/settings.style
+++ b/Telegram/SourceFiles/settings/settings.style
@@ -523,10 +523,11 @@ settingsPremiumLock: icon{{ "emoji/premium_lock", windowActiveTextFg, point(0px,
 settingsPremiumLockSkip: 3px;
 
 settingsBlockedListSubtitleAddPadding: margins(0px, 1px, 0px, -4px);
-settingsBlockedListIconPadding: margins(0px, 34px, 0px, 5px);
+settingsBlockedListIconPadding: margins(0px, 24px, 0px, 5px);
 settingsBlockedList: PeerList(peerListBox) {
 	padding: margins(0px, 0px, 0px, membersMarginBottom);
 }
+settingsBlockedHeightMin: 240px;
 
 settingsNotificationType: SettingsButton(settingsButton) {
 	height: 40px;
diff --git a/Telegram/SourceFiles/settings/settings_blocked_peers.cpp b/Telegram/SourceFiles/settings/settings_blocked_peers.cpp
index 45f2376df..f90bd9ac8 100644
--- a/Telegram/SourceFiles/settings/settings_blocked_peers.cpp
+++ b/Telegram/SourceFiles/settings/settings_blocked_peers.cpp
@@ -47,7 +47,10 @@ Blocked::Blocked(
 					tr::lng_contacts_loading(),
 					st::changePhoneDescription),
 				std::move(padding)));
-		Ui::ResizeFitChild(this, _loading.get());
+		Ui::ResizeFitChild(
+			this,
+			_loading.get(),
+			st::settingsBlockedHeightMin);
 	}
 
 	_controller->session().api().blockedPeers().slice(
@@ -201,7 +204,30 @@ void Blocked::setupContent() {
 		AddSkip(content, st::settingsBlockedListIconPadding.top());
 	}
 
-	Ui::ResizeFitChild(this, _container);
+	// We want minimal height to be the same no matter if subtitle
+	// is visible or not, so minimal height isn't a constant here.
+//	Ui::ResizeFitChild(this, _container, st::settingsBlockedHeightMin);
+
+	widthValue(
+	) | rpl::start_with_next([=](int width) {
+		_container->resizeToWidth(width);
+	}, _container->lifetime());
+
+	rpl::combine(
+		_container->heightValue(),
+		_emptinessChanges.events_starting_with(true)
+	) | rpl::start_with_next([=](int height, bool empty) {
+		const auto subtitled = !empty || (_countBlocked.current() > 0);
+		const auto total = st::settingsBlockedHeightMin;
+		const auto padding = st::settingsSubsectionTitlePadding
+			+ st::settingsBlockedListSubtitleAddPadding;
+		const auto subtitle = st::settingsSectionSkip
+			+ padding.top()
+			+ st::settingsSubsectionTitle.style.font->height
+			+ padding.bottom();
+		const auto min = total - (subtitled ? subtitle : 0);
+		resize(width(), std::max(height, min));
+	}, _container->lifetime());
 }
 
 void Blocked::checkTotal(int total) {
diff --git a/Telegram/lib_ui b/Telegram/lib_ui
index a3c52c2cf..552db4b24 160000
--- a/Telegram/lib_ui
+++ b/Telegram/lib_ui
@@ -1 +1 @@
-Subproject commit a3c52c2cfe7ee5c3382ac0579eb96ec6962b7f47
+Subproject commit 552db4b24f4542dcb6d19302d7e29ff00349b156

From b80f5f970605ed0ed714dd7a7722ef2dfb8fd56d Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Wed, 23 Aug 2023 13:20:41 +0200
Subject: [PATCH 03/29] Manage notifications exceptions in Settings.

---
 Telegram/Resources/langs/lang.strings         |   5 +
 Telegram/SourceFiles/apiwrap.cpp              |  11 +-
 Telegram/SourceFiles/boxes/peer_list_box.cpp  |  22 +
 Telegram/SourceFiles/boxes/peer_list_box.h    |   2 +
 .../boxes/peer_list_controllers.cpp           |  19 -
 .../SourceFiles/boxes/peer_list_controllers.h |   1 -
 .../data/notify/data_notify_settings.cpp      | 203 ++++++---
 .../data/notify/data_notify_settings.h        |  62 ++-
 .../data/notify/data_peer_notify_settings.cpp |   9 +
 .../data/notify/data_peer_notify_settings.h   |   1 +
 .../settings/settings_blocked_peers.cpp       |   2 +-
 .../settings/settings_notifications.cpp       |  27 +-
 .../settings/settings_notifications_type.cpp  | 399 +++++++++++++++++-
 Telegram/SourceFiles/ui/menu_icons.style      |   1 +
 14 files changed, 646 insertions(+), 118 deletions(-)

diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index c01e2f6d8..a045ba1f0 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -498,6 +498,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 "lng_notification_title_channels" = "Notifications for channels";
 "lng_notification_about_channels#one" = "Please note that **{count} channel** is listed as an exception and won't be affected by this change.";
 "lng_notification_about_channels#other" = "Please note that **{count} channels** are listed as exceptions and won't be affected by this change.";
+"lng_notification_exceptions_view" = "View exceptions";
 "lng_notification_enable" = "Enable notifications";
 "lng_notification_sound" = "Sound";
 "lng_notification_tone" = "Notification tone";
@@ -505,6 +506,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 "lng_notification_exceptions_unmuted" = "Unmuted";
 "lng_notification_exceptions_add" = "Add an exception";
 "lng_notification_exceptions_clear" = "Delete all exceptions";
+"lng_notification_exceptions_clear_sure" = "Are you sure you want to delete all exceptions?";
+"lng_notification_exceptions_clear_button" = "Delete";
+"lng_notification_exceptions_remove" = "Remove";
+"lng_notification_context_remove" = "Remove exception";
 
 "lng_reaction_text" = "{reaction} to your «{text}»";
 "lng_reaction_notext" = "{reaction} to your message";
diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp
index e71e03953..aebc9b809 100644
--- a/Telegram/SourceFiles/apiwrap.cpp
+++ b/Telegram/SourceFiles/apiwrap.cpp
@@ -1887,17 +1887,8 @@ void ApiWrap::sendNotifySettingsUpdates() {
 	}
 	const auto &settings = session().data().notifySettings();
 	for (const auto type : base::take(_updateNotifyDefaults)) {
-		const auto input = [&] {
-			switch (type) {
-			case Data::DefaultNotify::User: return MTP_inputNotifyUsers();
-			case Data::DefaultNotify::Group: return MTP_inputNotifyChats();
-			case Data::DefaultNotify::Broadcast:
-				return MTP_inputNotifyBroadcasts();
-			}
-			Unexpected("Default notify type in sendNotifySettingsUpdates");
-		}();
 		request(MTPaccount_UpdateNotifySettings(
-			input,
+			Data::DefaultNotifyToMTP(type),
 			settings.defaultSettings(type).serialize()
 		)).afterDelay(kSmallDelayMs).send();
 	}
diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp
index 6f27553d0..407eeb185 100644
--- a/Telegram/SourceFiles/boxes/peer_list_box.cpp
+++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "boxes/peer_list_box.h"
 
+#include "history/history.h" // chatListNameSortKey.
 #include "main/session/session_show.h"
 #include "main/main_session.h"
 #include "mainwidget.h"
@@ -396,6 +397,27 @@ void PeerListController::setSearchNoResultsText(const QString &text) {
 	}
 }
 
+void PeerListController::sortByName() {
+	auto keys = base::flat_map<PeerListRowId, QString>();
+	keys.reserve(delegate()->peerListFullRowsCount());
+	const auto key = [&](const PeerListRow &row) {
+		const auto id = row.id();
+		const auto i = keys.find(id);
+		if (i != end(keys)) {
+			return i->second;
+		}
+		const auto peer = row.peer();
+		const auto history = peer->owner().history(peer);
+		return keys.emplace(
+			id,
+			history->chatListNameSortKey()).first->second;
+	};
+	const auto predicate = [&](const PeerListRow &a, const PeerListRow &b) {
+		return (key(a).compare(key(b)) < 0);
+	};
+	delegate()->peerListSortRows(predicate);
+}
+
 base::unique_qptr<Ui::PopupMenu> PeerListController::rowContextMenu(
 		QWidget *parent,
 		not_null<PeerListRow*> row) {
diff --git a/Telegram/SourceFiles/boxes/peer_list_box.h b/Telegram/SourceFiles/boxes/peer_list_box.h
index ac8ab554b..837bb6e2c 100644
--- a/Telegram/SourceFiles/boxes/peer_list_box.h
+++ b/Telegram/SourceFiles/boxes/peer_list_box.h
@@ -560,6 +560,8 @@ protected:
 		delegate()->peerListSetSearchNoResults(std::move(noResults));
 	}
 
+	void sortByName();
+
 private:
 	PeerListDelegate *_delegate = nullptr;
 	std::unique_ptr<PeerListSearchController> _searchController = nullptr;
diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp
index 335531fc9..874e61e23 100644
--- a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp
+++ b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp
@@ -594,25 +594,6 @@ void ContactsBoxController::sort() {
 	}
 }
 
-void ContactsBoxController::sortByName() {
-	auto keys = base::flat_map<PeerListRowId, QString>();
-	keys.reserve(delegate()->peerListFullRowsCount());
-	const auto key = [&](const PeerListRow &row) {
-		const auto id = row.id();
-		const auto i = keys.find(id);
-		if (i != end(keys)) {
-			return i->second;
-		}
-		const auto peer = row.peer();
-		const auto history = peer->owner().history(peer);
-		return keys.emplace(id, history->chatListNameSortKey()).first->second;
-	};
-	const auto predicate = [&](const PeerListRow &a, const PeerListRow &b) {
-		return (key(a).compare(key(b)) < 0);
-	};
-	delegate()->peerListSortRows(predicate);
-}
-
 void ContactsBoxController::sortByOnline() {
 	const auto now = base::unixtime::now();
 	const auto key = [&](const PeerListRow &row) {
diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.h b/Telegram/SourceFiles/boxes/peer_list_controllers.h
index 0ba108f01..35b4e0f15 100644
--- a/Telegram/SourceFiles/boxes/peer_list_controllers.h
+++ b/Telegram/SourceFiles/boxes/peer_list_controllers.h
@@ -192,7 +192,6 @@ protected:
 
 private:
 	void sort();
-	void sortByName();
 	void sortByOnline();
 	void rebuildRows();
 	void checkForEmptyRows();
diff --git a/Telegram/SourceFiles/data/notify/data_notify_settings.cpp b/Telegram/SourceFiles/data/notify/data_notify_settings.cpp
index 1675d1e92..22fc6e26f 100644
--- a/Telegram/SourceFiles/data/notify/data_notify_settings.cpp
+++ b/Telegram/SourceFiles/data/notify/data_notify_settings.cpp
@@ -42,11 +42,39 @@ constexpr auto kMaxNotifyCheckDelay = 24 * 3600 * crl::time(1000);
 	return (result > 0);
 }
 
+[[nodiscard]] bool SkipAddException(not_null<PeerData*> peer) {
+	if (const auto user = peer->asUser()) {
+		return user->isInaccessible();
+	} else if (const auto chat = peer->asChat()) {
+		return chat->isDeactivated() || chat->isForbidden();
+	} else if (const auto channel = peer->asChannel()) {
+		return channel->isForbidden();
+	}
+	return false;
+}
+
 } // namespace
 
+DefaultNotify DefaultNotifyType(not_null<const PeerData*> peer) {
+	return peer->isUser()
+		? DefaultNotify::User
+		: (peer->isChat() || peer->isMegagroup())
+		? DefaultNotify::Group
+		: DefaultNotify::Broadcast;
+}
+
+MTPInputNotifyPeer DefaultNotifyToMTP(DefaultNotify type) {
+	switch (type) {
+	case DefaultNotify::User: return MTP_inputNotifyUsers();
+	case DefaultNotify::Group: return MTP_inputNotifyChats();
+	case DefaultNotify::Broadcast: return MTP_inputNotifyBroadcasts();
+	}
+	Unexpected("Default notify type in sendNotifySettingsUpdates");
+}
+
 NotifySettings::NotifySettings(not_null<Session*> owner)
-	: _owner(owner)
-	, _unmuteByFinishedTimer([=] { unmuteByFinished(); }) {
+: _owner(owner)
+, _unmuteByFinishedTimer([=] { unmuteByFinished(); }) {
 }
 
 void NotifySettings::request(not_null<PeerData*> peer) {
@@ -63,7 +91,7 @@ void NotifySettings::request(not_null<PeerData*> peer) {
 	}
 }
 
-void NotifySettings::request(not_null<Data::Thread*> thread) {
+void NotifySettings::request(not_null<Thread*> thread) {
 	if (const auto topic = thread->asTopic()) {
 		if (topic->notify().settingsUnknown()) {
 			topic->session().api().requestNotifySettings(
@@ -145,6 +173,7 @@ void NotifySettings::apply(
 		not_null<PeerData*> peer,
 		const MTPPeerNotifySettings &settings) {
 	if (peer->notify().change(settings)) {
+		updateException(peer);
 		updateLocal(peer);
 		Core::App().notifications().checkDelayed();
 	}
@@ -162,7 +191,7 @@ void NotifySettings::apply(
 }
 
 void NotifySettings::apply(
-		not_null<Data::ForumTopic*> topic,
+		not_null<ForumTopic*> topic,
 		const MTPPeerNotifySettings &settings) {
 	if (topic->notify().change(settings)) {
 		updateLocal(topic);
@@ -171,8 +200,8 @@ void NotifySettings::apply(
 }
 
 void NotifySettings::update(
-		not_null<Data::Thread*> thread,
-		Data::MuteValue muteForSeconds,
+		not_null<Thread*> thread,
+		MuteValue muteForSeconds,
 		std::optional<bool> silentPosts,
 		std::optional<NotifySound> sound,
 		std::optional<bool> storiesMuted) {
@@ -181,34 +210,29 @@ void NotifySettings::update(
 			silentPosts,
 			sound,
 			storiesMuted)) {
+		if (const auto history = thread->asHistory()) {
+			updateException(history->peer);
+		}
 		updateLocal(thread);
 		thread->session().api().updateNotifySettingsDelayed(thread);
 	}
 }
 
-void NotifySettings::resetToDefault(not_null<Data::Thread*> thread) {
-	const auto empty = MTP_peerNotifySettings(
-		MTP_flags(0),
-		MTPBool(),
-		MTPBool(),
-		MTPint(),
-		MTPNotificationSound(),
-		MTPNotificationSound(),
-		MTPNotificationSound(),
-		MTPBool(),
-		MTPBool(),
-		MTPNotificationSound(),
-		MTPNotificationSound(),
-		MTPNotificationSound());
-	if (thread->notify().change(empty)) {
+void NotifySettings::resetToDefault(not_null<Thread*> thread) {
+	// Duplicated in clearExceptions(type) and resetToDefault(peer).
+	if (thread->notify().resetToDefault()) {
+		if (const auto history = thread->asHistory()) {
+			updateException(history->peer);
+		}
 		updateLocal(thread);
 		thread->session().api().updateNotifySettingsDelayed(thread);
+		Core::App().notifications().checkDelayed();
 	}
 }
 
 void NotifySettings::update(
 		not_null<PeerData*> peer,
-		Data::MuteValue muteForSeconds,
+		MuteValue muteForSeconds,
 		std::optional<bool> silentPosts,
 		std::optional<NotifySound> sound,
 		std::optional<bool> storiesMuted) {
@@ -217,33 +241,24 @@ void NotifySettings::update(
 			silentPosts,
 			sound,
 			storiesMuted)) {
+		updateException(peer);
 		updateLocal(peer);
 		peer->session().api().updateNotifySettingsDelayed(peer);
 	}
 }
 
 void NotifySettings::resetToDefault(not_null<PeerData*> peer) {
-	const auto empty = MTP_peerNotifySettings(
-		MTP_flags(0),
-		MTPBool(),
-		MTPBool(),
-		MTPint(),
-		MTPNotificationSound(),
-		MTPNotificationSound(),
-		MTPNotificationSound(),
-		MTPBool(),
-		MTPBool(),
-		MTPNotificationSound(),
-		MTPNotificationSound(),
-		MTPNotificationSound());
-	if (peer->notify().change(empty)) {
+	// Duplicated in clearExceptions(type) and resetToDefault(thread).
+	if (peer->notify().resetToDefault()) {
+		updateException(peer);
 		updateLocal(peer);
 		peer->session().api().updateNotifySettingsDelayed(peer);
+		Core::App().notifications().checkDelayed();
 	}
 }
 
-void NotifySettings::forumParentMuteUpdated(not_null<Data::Forum*> forum) {
-	forum->enumerateTopics([&](not_null<Data::ForumTopic*> topic) {
+void NotifySettings::forumParentMuteUpdated(not_null<Forum*> forum) {
+	forum->enumerateTopics([&](not_null<ForumTopic*> topic) {
 		if (!topic->notify().settingsUnknown()) {
 			updateLocal(topic);
 		}
@@ -266,11 +281,7 @@ auto NotifySettings::defaultValue(DefaultNotify type) const
 
 const PeerNotifySettings &NotifySettings::defaultSettings(
 		not_null<const PeerData*> peer) const {
-	return defaultSettings(peer->isUser()
-		? DefaultNotify::User
-		: (peer->isChat() || peer->isMegagroup())
-		? DefaultNotify::Group
-		: DefaultNotify::Broadcast);
+	return defaultSettings(DefaultNotifyType(peer));
 }
 
 const PeerNotifySettings &NotifySettings::defaultSettings(
@@ -280,7 +291,7 @@ const PeerNotifySettings &NotifySettings::defaultSettings(
 
 void NotifySettings::defaultUpdate(
 		DefaultNotify type,
-		Data::MuteValue muteForSeconds,
+		MuteValue muteForSeconds,
 		std::optional<bool> silentPosts,
 		std::optional<NotifySound> sound,
 		std::optional<bool> storiesMuted) {
@@ -291,7 +302,7 @@ void NotifySettings::defaultUpdate(
 	}
 }
 
-void NotifySettings::updateLocal(not_null<Data::Thread*> thread) {
+void NotifySettings::updateLocal(not_null<Thread*> thread) {
 	const auto topic = thread->asTopic();
 	if (!topic) {
 		return updateLocal(thread->peer());
@@ -351,7 +362,7 @@ void NotifySettings::cacheSound(not_null<DocumentData*> document) {
 	const auto view = document->createMediaView();
 	_ringtones.views.emplace(document->id, view);
 	document->forceToCache(true);
-	document->save(Data::FileOriginRingtones(), QString());
+	document->save(FileOriginRingtones(), QString());
 }
 
 void NotifySettings::cacheSound(const std::optional<NotifySound> &sound) {
@@ -459,7 +470,7 @@ void NotifySettings::unmuteByFinished() {
 }
 
 bool NotifySettings::isMuted(
-		not_null<const Data::Thread*> thread,
+		not_null<const Thread*> thread,
 		crl::time *changesIn) const {
 	const auto topic = thread->asTopic();
 	const auto until = topic ? topic->notify().muteUntil() : std::nullopt;
@@ -468,27 +479,24 @@ bool NotifySettings::isMuted(
 		: isMuted(thread->peer(), changesIn);
 }
 
-bool NotifySettings::isMuted(not_null<const Data::Thread*> thread) const {
+bool NotifySettings::isMuted(not_null<const Thread*> thread) const {
 	return isMuted(thread, nullptr);
 }
 
-NotifySound NotifySettings::sound(
-		not_null<const Data::Thread*> thread) const {
+NotifySound NotifySettings::sound(not_null<const Thread*> thread) const {
 	const auto topic = thread->asTopic();
 	const auto sound = topic ? topic->notify().sound() : std::nullopt;
 	return sound ? *sound : this->sound(thread->peer());
 }
 
-bool NotifySettings::muteUnknown(
-		not_null<const Data::Thread*> thread) const {
+bool NotifySettings::muteUnknown(not_null<const Thread*> thread) const {
 	const auto topic = thread->asTopic();
 	return (topic && topic->notify().settingsUnknown())
 		|| ((!topic || !topic->notify().muteUntil().has_value())
 			&& muteUnknown(thread->peer()));
 }
 
-bool NotifySettings::soundUnknown(
-		not_null<const Data::Thread*> thread) const {
+bool NotifySettings::soundUnknown(not_null<const Thread*> thread) const {
 	const auto topic = thread->asTopic();
 	return (topic && topic->notify().settingsUnknown())
 		|| ((!topic || !topic->notify().sound().has_value())
@@ -543,8 +551,7 @@ bool NotifySettings::silentPostsUnknown(
 			&& defaultSettings(peer).settingsUnknown());
 }
 
-bool NotifySettings::soundUnknown(
-		not_null<const PeerData*> peer) const {
+bool NotifySettings::soundUnknown(not_null<const PeerData*> peer) const {
 	return peer->notify().settingsUnknown()
 		|| (!peer->notify().sound().has_value()
 			&& defaultSettings(peer).settingsUnknown());
@@ -556,8 +563,7 @@ bool NotifySettings::settingsUnknown(not_null<const PeerData*> peer) const {
 		|| soundUnknown(peer);
 }
 
-bool NotifySettings::settingsUnknown(
-		not_null<const Data::Thread*> thread) const {
+bool NotifySettings::settingsUnknown(not_null<const Thread*> thread) const {
 	const auto topic = thread->asTopic();
 	return muteUnknown(thread)
 		|| soundUnknown(thread)
@@ -577,4 +583,85 @@ rpl::producer<> NotifySettings::defaultUpdates(
 		: DefaultNotify::Broadcast);
 }
 
+void NotifySettings::loadExceptions() {
+	for (auto i = 0; i != kDefaultNotifyTypes; ++i) {
+		if (_exceptionsRequestId[i]) {
+			continue;
+		}
+		const auto type = static_cast<DefaultNotify>(i);
+		const auto api = &_owner->session().api();
+		const auto requestId = api->request(MTPaccount_GetNotifyExceptions(
+			MTP_flags(MTPaccount_GetNotifyExceptions::Flag::f_peer),
+			DefaultNotifyToMTP(type)
+		)).done([=](const MTPUpdates &result) {
+			api->applyUpdates(result);
+		}).send();
+		_exceptionsRequestId[i] = requestId;
+	}
+}
+
+void NotifySettings::updateException(not_null<PeerData*> peer) {
+	const auto type = DefaultNotifyType(peer);
+	const auto index = static_cast<int>(type);
+	const auto exception = peer->notify().muteUntil().has_value();
+	if (!exception) {
+		if (_exceptions[index].remove(peer)) {
+			exceptionsUpdated(type);
+		}
+	} else if (SkipAddException(peer)) {
+		return;
+	} else if (_exceptions[index].emplace(peer).second) {
+		exceptionsUpdated(type);
+	}
+}
+
+void NotifySettings::exceptionsUpdated(DefaultNotify type) {
+	if (!ranges::contains(_exceptionsUpdatesScheduled, true)) {
+		crl::on_main(&_owner->session(), [=] {
+			const auto scheduled = base::take(_exceptionsUpdatesScheduled);
+			for (auto i = 0; i != kDefaultNotifyTypes; ++i) {
+				if (scheduled[i]) {
+					_exceptionsUpdates.fire(static_cast<DefaultNotify>(i));
+				}
+			}
+		});
+	}
+	_exceptionsUpdatesScheduled[static_cast<int>(type)] = true;
+	_exceptionsUpdatesRealtime.fire_copy(type);
+}
+
+rpl::producer<DefaultNotify> NotifySettings::exceptionsUpdates() const {
+	return _exceptionsUpdates.events();
+}
+
+auto NotifySettings::exceptionsUpdatesRealtime() const
+-> rpl::producer<DefaultNotify> {
+	return _exceptionsUpdatesRealtime.events();
+}
+
+const base::flat_set<not_null<PeerData*>> &NotifySettings::exceptions(
+		DefaultNotify type) const {
+	const auto index = static_cast<int>(type);
+	Assert(index >= 0 && index < kDefaultNotifyTypes);
+
+	return _exceptions[index];
+}
+
+void NotifySettings::clearExceptions(DefaultNotify type) {
+	const auto index = static_cast<int>(type);
+	const auto list = base::take(_exceptions[index]);
+	if (list.empty()) {
+		return;
+	}
+	for (const auto &peer : list) {
+		// Duplicated in resetToDefault(peer / thread).
+		if (peer->notify().resetToDefault()) {
+			updateLocal(peer);
+			peer->session().api().updateNotifySettingsDelayed(peer);
+		}
+	}
+	Core::App().notifications().checkDelayed();
+	exceptionsUpdated(type);
+}
+
 } // namespace Data
diff --git a/Telegram/SourceFiles/data/notify/data_notify_settings.h b/Telegram/SourceFiles/data/notify/data_notify_settings.h
index 6e51b87ac..b1919a319 100644
--- a/Telegram/SourceFiles/data/notify/data_notify_settings.h
+++ b/Telegram/SourceFiles/data/notify/data_notify_settings.h
@@ -26,13 +26,17 @@ enum class DefaultNotify {
 	Group,
 	Broadcast,
 };
+[[nodiscard]] DefaultNotify DefaultNotifyType(
+	not_null<const PeerData*> peer);
+
+[[nodiscard]] MTPInputNotifyPeer DefaultNotifyToMTP(DefaultNotify type);
 
 class NotifySettings final {
 public:
 	NotifySettings(not_null<Session*> owner);
 
 	void request(not_null<PeerData*> peer);
-	void request(not_null<Data::Thread*> thread);
+	void request(not_null<Thread*> thread);
 
 	void apply(
 		const MTPNotifyPeer &notifyPeer,
@@ -50,25 +54,25 @@ public:
 		MsgId topicRootId,
 		const MTPPeerNotifySettings &settings);
 	void apply(
-		not_null<Data::ForumTopic*> topic,
+		not_null<ForumTopic*> topic,
 		const MTPPeerNotifySettings &settings);
 
 	void update(
-		not_null<Data::Thread*> thread,
-		Data::MuteValue muteForSeconds,
+		not_null<Thread*> thread,
+		MuteValue muteForSeconds,
 		std::optional<bool> silentPosts = std::nullopt,
 		std::optional<NotifySound> sound = std::nullopt,
 		std::optional<bool> storiesMuted = std::nullopt);
-	void resetToDefault(not_null<Data::Thread*> thread);
+	void resetToDefault(not_null<Thread*> thread);
 	void update(
 		not_null<PeerData*> peer,
-		Data::MuteValue muteForSeconds,
+		MuteValue muteForSeconds,
 		std::optional<bool> silentPosts = std::nullopt,
 		std::optional<NotifySound> sound = std::nullopt,
 		std::optional<bool> storiesMuted = std::nullopt);
 	void resetToDefault(not_null<PeerData*> peer);
 
-	void forumParentMuteUpdated(not_null<Data::Forum*> forum);
+	void forumParentMuteUpdated(not_null<Forum*> forum);
 
 	void cacheSound(DocumentId id);
 	void cacheSound(not_null<DocumentData*> document);
@@ -84,18 +88,15 @@ public:
 
 	void defaultUpdate(
 		DefaultNotify type,
-		Data::MuteValue muteForSeconds,
+		MuteValue muteForSeconds,
 		std::optional<bool> silentPosts = std::nullopt,
 		std::optional<NotifySound> sound = std::nullopt,
 		std::optional<bool> storiesMuted = std::nullopt);
 
-	[[nodiscard]] bool isMuted(not_null<const Data::Thread*> thread) const;
-	[[nodiscard]] NotifySound sound(
-		not_null<const Data::Thread*> thread) const;
-	[[nodiscard]] bool muteUnknown(
-		not_null<const Data::Thread*> thread) const;
-	[[nodiscard]] bool soundUnknown(
-		not_null<const Data::Thread*> thread) const;
+	[[nodiscard]] bool isMuted(not_null<const Thread*> thread) const;
+	[[nodiscard]] NotifySound sound(not_null<const Thread*> thread) const;
+	[[nodiscard]] bool muteUnknown(not_null<const Thread*> thread) const;
+	[[nodiscard]] bool soundUnknown(not_null<const Thread*> thread) const;
 
 	[[nodiscard]] bool isMuted(not_null<const PeerData*> peer) const;
 	[[nodiscard]] bool silentPosts(not_null<const PeerData*> peer) const;
@@ -105,7 +106,17 @@ public:
 		not_null<const PeerData*> peer) const;
 	[[nodiscard]] bool soundUnknown(not_null<const PeerData*> peer) const;
 
+	void loadExceptions();
+	[[nodiscard]] rpl::producer<DefaultNotify> exceptionsUpdates() const;
+	[[nodiscard]] auto exceptionsUpdatesRealtime() const
+		-> rpl::producer<DefaultNotify>;
+	[[nodiscard]] const base::flat_set<not_null<PeerData*>> &exceptions(
+		DefaultNotify type) const;
+	void clearExceptions(DefaultNotify type);
+
 private:
+	static constexpr auto kDefaultNotifyTypes = 3;
+
 	struct DefaultValue {
 		PeerNotifySettings settings;
 		rpl::event_stream<> updates;
@@ -114,7 +125,7 @@ private:
 	void cacheSound(const std::optional<NotifySound> &sound);
 
 	[[nodiscard]] bool isMuted(
-		not_null<const Data::Thread*> thread,
+		not_null<const Thread*> thread,
 		crl::time *changesIn) const;
 	[[nodiscard]] bool isMuted(
 		not_null<const PeerData*> peer,
@@ -126,21 +137,22 @@ private:
 		not_null<const PeerData*> peer) const;
 	[[nodiscard]] bool settingsUnknown(not_null<const PeerData*> peer) const;
 	[[nodiscard]] bool settingsUnknown(
-		not_null<const Data::Thread*> thread) const;
+		not_null<const Thread*> thread) const;
 
 	void unmuteByFinished();
 	void unmuteByFinishedDelayed(crl::time delay);
-	void updateLocal(not_null<Data::Thread*> thread);
+	void updateLocal(not_null<Thread*> thread);
 	void updateLocal(not_null<PeerData*> peer);
 	void updateLocal(DefaultNotify type);
 
+	void updateException(not_null<PeerData*> peer);
+	void exceptionsUpdated(DefaultNotify type);
+
 	const not_null<Session*> _owner;
 
 	DefaultValue _defaultValues[3];
 	std::unordered_set<not_null<const PeerData*>> _mutedPeers;
-	std::unordered_map<
-		not_null<Data::ForumTopic*>,
-		rpl::lifetime> _mutedTopics;
+	std::unordered_map<not_null<ForumTopic*>, rpl::lifetime> _mutedTopics;
 	base::Timer _unmuteByFinishedTimer;
 
 	struct {
@@ -151,6 +163,14 @@ private:
 		rpl::lifetime pendingLifetime;
 	} _ringtones;
 
+	rpl::event_stream<DefaultNotify> _exceptionsUpdates;
+	rpl::event_stream<DefaultNotify> _exceptionsUpdatesRealtime;
+	std::array<
+		base::flat_set<not_null<PeerData*>>,
+		kDefaultNotifyTypes> _exceptions;
+	std::array<mtpRequestId, kDefaultNotifyTypes> _exceptionsRequestId = {};
+	std::array<bool, kDefaultNotifyTypes> _exceptionsUpdatesScheduled = {};
+
 };
 
 } // namespace Data
diff --git a/Telegram/SourceFiles/data/notify/data_peer_notify_settings.cpp b/Telegram/SourceFiles/data/notify/data_peer_notify_settings.cpp
index 4b756a611..48d198d6e 100644
--- a/Telegram/SourceFiles/data/notify/data_peer_notify_settings.cpp
+++ b/Telegram/SourceFiles/data/notify/data_peer_notify_settings.cpp
@@ -256,6 +256,15 @@ bool PeerNotifySettings::change(
 		SerializeSound(std::nullopt))); // stories_sound
 }
 
+bool PeerNotifySettings::resetToDefault() {
+	if (_known && !_value) {
+		return false;
+	}
+	_known = true;
+	_value = nullptr;
+	return true;
+}
+
 std::optional<TimeId> PeerNotifySettings::muteUntil() const {
 	return _value
 		? _value->muteUntil()
diff --git a/Telegram/SourceFiles/data/notify/data_peer_notify_settings.h b/Telegram/SourceFiles/data/notify/data_peer_notify_settings.h
index 76a8ecfd9..b5de9e119 100644
--- a/Telegram/SourceFiles/data/notify/data_peer_notify_settings.h
+++ b/Telegram/SourceFiles/data/notify/data_peer_notify_settings.h
@@ -46,6 +46,7 @@ public:
 		std::optional<bool> silentPosts,
 		std::optional<NotifySound> sound,
 		std::optional<bool> storiesMuted);
+	bool resetToDefault();
 
 	bool settingsUnknown() const;
 	std::optional<TimeId> muteUntil() const;
diff --git a/Telegram/SourceFiles/settings/settings_blocked_peers.cpp b/Telegram/SourceFiles/settings/settings_blocked_peers.cpp
index f90bd9ac8..e21666d81 100644
--- a/Telegram/SourceFiles/settings/settings_blocked_peers.cpp
+++ b/Telegram/SourceFiles/settings/settings_blocked_peers.cpp
@@ -80,7 +80,7 @@ QPointer<Ui::RpWidget> Blocked::createPinnedToTop(not_null<QWidget*> parent) {
 		content,
 		tr::lng_blocked_list_add(),
 		st::settingsButtonActive,
-		{ &st::menuIconBlockSettings, IconType::Round, &st::transparent }
+		{ &st::menuIconBlockSettings }
 	)->addClickHandler([=] {
 		BlockedBoxController::BlockNewPeer(_controller);
 	});
diff --git a/Telegram/SourceFiles/settings/settings_notifications.cpp b/Telegram/SourceFiles/settings/settings_notifications.cpp
index cbe3f6c8a..9bf5f35d4 100644
--- a/Telegram/SourceFiles/settings/settings_notifications.cpp
+++ b/Telegram/SourceFiles/settings/settings_notifications.cpp
@@ -172,10 +172,30 @@ void AddTypeButton(
 		showOther(NotificationsTypeId(type));
 	});
 
+	const auto session = &controller->session();
+	const auto settings = &session->data().notifySettings();
 	const auto &st = st::settingsNotificationType;
+	auto status = rpl::combine(
+		NotificationsEnabledForTypeValue(session, type),
+		rpl::single(
+			type
+		) | rpl::then(settings->exceptionsUpdates(
+		) | rpl::filter(rpl::mappers::_1 == type))
+	) | rpl::map([=](bool enabled, const auto &) {
+		const auto count = int(settings->exceptions(type).size());
+		return !count
+			? tr::lng_notification_click_to_change()
+			: (enabled
+				? tr::lng_notification_on
+				: tr::lng_notification_off)(
+					lt_exceptions,
+					tr::lng_notification_exceptions(
+						lt_count,
+						rpl::single(float64(count))));
+	}) | rpl::flatten_latest();
 	const auto details = Ui::CreateChild<Ui::FlatLabel>(
 		button.get(),
-		tr::lng_notification_click_to_change(),
+		std::move(status),
 		st::settingsNotificationTypeDetails);
 	details->show();
 	details->moveToLeft(
@@ -183,12 +203,11 @@ void AddTypeButton(
 		st.padding.top() + st.height - details->height());
 	details->setAttribute(Qt::WA_TransparentForMouseEvents);
 
-	const auto session = &controller->session();
 	const auto toggleButton = Ui::CreateChild<Ui::SettingsButton>(
 		container.get(),
 		nullptr,
 		st);
-	const auto checkView = toggleButton->lifetime().make_state<Ui::ToggleView>(
+	const auto checkView = button->lifetime().make_state<Ui::ToggleView>(
 		st.toggle,
 		NotificationsEnabledForType(session, type),
 		[=] { toggleButton->update(); });
@@ -971,6 +990,8 @@ void SetupNotificationsContent(
 	previewWrap->toggle(settings.desktopNotify(), anim::type::instant);
 	previewDivider->toggle(!settings.desktopNotify(), anim::type::instant);
 
+	controller->session().data().notifySettings().loadExceptions();
+
 	AddSkip(container, st::notifyPreviewBottomSkip);
 	AddSubsectionTitle(container, tr::lng_settings_notify_title());
 	const auto addType = [&](Data::DefaultNotify type) {
diff --git a/Telegram/SourceFiles/settings/settings_notifications_type.cpp b/Telegram/SourceFiles/settings/settings_notifications_type.cpp
index 29699a398..999ff9165 100644
--- a/Telegram/SourceFiles/settings/settings_notifications_type.cpp
+++ b/Telegram/SourceFiles/settings/settings_notifications_type.cpp
@@ -11,14 +11,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "apiwrap.h"
 #include "base/unixtime.h"
 #include "boxes/ringtones_box.h"
+#include "boxes/peer_list_box.h"
+#include "boxes/peer_list_controllers.h"
 #include "data/notify/data_notify_settings.h"
+#include "data/data_changes.h"
+#include "data/data_peer.h"
 #include "data/data_session.h"
+#include "history/history.h"
 #include "lang/lang_keys.h"
 #include "main/main_session.h"
+#include "menu/menu_mute.h"
+#include "ui/boxes/confirm_box.h"
 #include "ui/widgets/buttons.h"
+#include "ui/widgets/popup_menu.h"
 #include "ui/wrap/slide_wrap.h"
 #include "ui/wrap/vertical_layout.h"
 #include "window/window_session_controller.h"
+#include "styles/style_layers.h"
 #include "styles/style_menu_icons.h"
 #include "styles/style_settings.h"
 
@@ -27,6 +36,321 @@ namespace {
 
 using Notify = Data::DefaultNotify;
 
+class AddExceptionBoxController final
+	: public ChatsListBoxController
+	, public base::has_weak_ptr {
+public:
+	AddExceptionBoxController(
+		not_null<Main::Session*> session,
+		Notify type,
+		Fn<void(not_null<PeerData*>)> done);
+
+	Main::Session &session() const override;
+	void rowClicked(not_null<PeerListRow*> row) override;
+	base::unique_qptr<Ui::PopupMenu> rowContextMenu(
+		QWidget *parent,
+		not_null<PeerListRow*> row) override;
+
+private:
+	void prepareViewHook() override;
+	std::unique_ptr<Row> createRow(not_null<History*> history) override;
+
+	const not_null<Main::Session*> _session;
+	const Notify _type;
+	const Fn<void(not_null<PeerData*>)> _done;
+
+	base::unique_qptr<Ui::PopupMenu> _menu;
+	PeerData *_lastClickedPeer = nullptr;
+
+	rpl::lifetime _lifetime;
+
+};
+
+class ExceptionsController final : public PeerListController {
+public:
+	ExceptionsController(
+		not_null<Window::SessionController*> window,
+		Notify type);
+
+	Main::Session &session() const override;
+	void prepare() override;
+	void rowClicked(not_null<PeerListRow*> row) override;
+	base::unique_qptr<Ui::PopupMenu> rowContextMenu(
+		QWidget *parent,
+		not_null<PeerListRow*> row) override;
+	void rowRightActionClicked(not_null<PeerListRow*> row) override;
+	void loadMoreRows() override;
+
+	void bringToTop(not_null<PeerData*> peer);
+
+	[[nodiscard]] rpl::producer<int> countValue() const;
+
+private:
+	void refreshRows();
+	bool appendRow(not_null<PeerData*> peer);
+	std::unique_ptr<PeerListRow> createRow(not_null<PeerData*> peer) const;
+	void refreshStatus(not_null<PeerListRow*> row) const;
+
+	void sort();
+
+	const not_null<Window::SessionController*> _window;
+	const Notify _type;
+
+	base::unique_qptr<Ui::PopupMenu> _menu;
+
+	base::flat_map<not_null<PeerData*>, int> _topOrdered;
+	int _topOrder = 0;
+
+	rpl::variable<int> _count;
+
+	rpl::lifetime _lifetime;
+
+};
+
+AddExceptionBoxController::AddExceptionBoxController(
+	not_null<Main::Session*> session,
+	Notify type,
+	Fn<void(not_null<PeerData*>)> done)
+: ChatsListBoxController(session)
+, _session(session)
+, _type(type)
+, _done(std::move(done)) {
+}
+
+Main::Session &AddExceptionBoxController::session() const {
+	return *_session;
+}
+
+void AddExceptionBoxController::prepareViewHook() {
+	delegate()->peerListSetTitle(tr::lng_notification_exceptions_add());
+
+	_session->changes().peerUpdates(
+		Data::PeerUpdate::Flag::Notifications
+	) | rpl::filter([=](const Data::PeerUpdate &update) {
+		return update.peer == _lastClickedPeer;
+	}) | rpl::start_with_next([=] {
+		if (const auto onstack = _done) {
+			onstack(_lastClickedPeer);
+		}
+	}, _lifetime);
+}
+
+void AddExceptionBoxController::rowClicked(not_null<PeerListRow*> row) {
+	delegate()->peerListShowRowMenu(row, true);
+}
+
+base::unique_qptr<Ui::PopupMenu> AddExceptionBoxController::rowContextMenu(
+		QWidget *parent,
+		not_null<PeerListRow*> row) {
+	const auto peer = row->peer();
+	auto result = base::make_unique_q<Ui::PopupMenu>(
+		parent,
+		st::popupMenuWithIcons);
+
+	MuteMenu::FillMuteMenu(
+		result.get(),
+		peer->owner().history(peer),
+		delegate()->peerListUiShow());
+
+	// First clear _menu value, so that we don't check row positions yet.
+	base::take(_menu);
+
+	// Here unique_qptr is used like a shared pointer, where
+	// not the last destroyed pointer destroys the object, but the first.
+	_menu = base::unique_qptr<Ui::PopupMenu>(result.get());
+	_menu->setDestroyedCallback(crl::guard(this, [=] {
+		_lastClickedPeer = nullptr;
+	}));
+	_lastClickedPeer = peer;
+
+	return result;
+}
+
+auto AddExceptionBoxController::createRow(not_null<History*> history)
+-> std::unique_ptr<AddExceptionBoxController::Row> {
+	if (Data::DefaultNotifyType(history->peer) != _type
+		|| history->peer->isSelf()
+		|| history->peer->isRepliesChat()) {
+		return nullptr;
+	}
+	return std::make_unique<Row>(history);
+}
+
+ExceptionsController::ExceptionsController(
+	not_null<Window::SessionController*> window,
+	Notify type)
+: _window(window)
+, _type(type) {
+}
+
+Main::Session &ExceptionsController::session() const {
+	return _window->session();
+}
+
+void ExceptionsController::prepare() {
+	refreshRows();
+
+	session().data().notifySettings().exceptionsUpdates(
+	) | rpl::filter(rpl::mappers::_1 == _type) | rpl::start_with_next([=] {
+		refreshRows();
+	}, lifetime());
+
+	session().changes().peerUpdates(
+		Data::PeerUpdate::Flag::Notifications
+	) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
+		const auto peer = update.peer;
+		if (const auto row = delegate()->peerListFindRow(peer->id.value)) {
+			if (peer->notify().muteUntil().has_value()) {
+				refreshStatus(row);
+			} else {
+				delegate()->peerListRemoveRow(row);
+				delegate()->peerListRefreshRows();
+				_count = delegate()->peerListFullRowsCount();
+			}
+		}
+	}, _lifetime);
+}
+
+void ExceptionsController::loadMoreRows() {
+}
+
+void ExceptionsController::bringToTop(not_null<PeerData*> peer) {
+	_topOrdered[peer] = ++_topOrder;
+	if (delegate()->peerListFindRow(peer->id.value)) {
+		sort();
+	}
+}
+
+rpl::producer<int> ExceptionsController::countValue() const {
+	return _count.value();
+}
+
+void ExceptionsController::rowClicked(not_null<PeerListRow*> row) {
+	delegate()->peerListShowRowMenu(row, true);
+}
+
+void ExceptionsController::rowRightActionClicked(
+		not_null<PeerListRow*> row) {
+	session().data().notifySettings().resetToDefault(row->peer());
+}
+
+void ExceptionsController::refreshRows() {
+	auto seen = base::flat_set<not_null<PeerData*>>();
+	const auto &list = session().data().notifySettings().exceptions(_type);
+	auto removed = false, added = false;
+	auto already = delegate()->peerListFullRowsCount();
+	seen.reserve(std::min(int(list.size()), already));
+	for (auto i = 0; i != already;) {
+		const auto row = delegate()->peerListRowAt(i);
+		if (list.contains(row->peer())) {
+			seen.emplace(row->peer());
+			++i;
+		} else {
+			delegate()->peerListRemoveRow(row);
+			--already;
+			removed = true;
+		}
+	}
+	for (const auto &peer : list) {
+		if (!seen.contains(peer)) {
+			appendRow(peer);
+			added = true;
+		}
+	}
+	if (added || removed) {
+		if (added) {
+			sort();
+		}
+		delegate()->peerListRefreshRows();
+		_count = delegate()->peerListFullRowsCount();
+	}
+}
+
+base::unique_qptr<Ui::PopupMenu> ExceptionsController::rowContextMenu(
+		QWidget *parent,
+		not_null<PeerListRow*> row) {
+	const auto peer = row->peer();
+	auto result = base::make_unique_q<Ui::PopupMenu>(
+		parent,
+		st::popupMenuWithIcons);
+
+	result->addAction(
+		(peer->isUser()
+			? tr::lng_context_view_profile
+			: peer->isBroadcast()
+			? tr::lng_context_view_channel
+			: tr::lng_context_view_group)(tr::now),
+		crl::guard(_window, [window = _window.get(), peer] {
+			window->showPeerInfo(peer);
+		}),
+		(peer->isUser() ? &st::menuIconProfile : &st::menuIconInfo));
+	result->addSeparator();
+
+	MuteMenu::FillMuteMenu(
+		result.get(),
+		peer->owner().history(peer),
+		_window->uiShow());
+
+	// First clear _menu value, so that we don't check row positions yet.
+	base::take(_menu);
+
+	// Here unique_qptr is used like a shared pointer, where
+	// not the last destroyed pointer destroys the object, but the first.
+	_menu = base::unique_qptr<Ui::PopupMenu>(result.get());
+
+	return result;
+}
+
+bool ExceptionsController::appendRow(not_null<PeerData*> peer) {
+	delegate()->peerListAppendRow(createRow(peer));
+	return true;
+}
+
+std::unique_ptr<PeerListRow> ExceptionsController::createRow(
+		not_null<PeerData*> peer) const {
+	auto row = std::make_unique<PeerListRowWithLink>(peer);
+	row->setActionLink(tr::lng_notification_exceptions_remove(tr::now));
+	refreshStatus(row.get());
+	return row;
+}
+
+void ExceptionsController::refreshStatus(not_null<PeerListRow*> row) const {
+	const auto peer = row->peer();
+	const auto status = peer->owner().notifySettings().isMuted(peer)
+		? tr::lng_notification_exceptions_muted(tr::now)
+		: tr::lng_notification_exceptions_unmuted(tr::now);
+	row->setCustomStatus(status);
+}
+
+void ExceptionsController::sort() {
+	auto keys = base::flat_map<PeerListRowId, QString>();
+	keys.reserve(delegate()->peerListFullRowsCount());
+	const auto length = QString::number(_topOrder).size();
+	const auto key = [&](const PeerListRow &row) {
+		const auto id = row.id();
+		const auto i = keys.find(id);
+		if (i != end(keys)) {
+			return i->second;
+		}
+		const auto peer = row.peer();
+		const auto top = _topOrdered.find(peer);
+		if (top != end(_topOrdered)) {
+			const auto order = _topOrder - top->second;
+			return keys.emplace(
+				id,
+				u"0%1"_q.arg(order, length, 10, QChar('0'))).first->second;
+		}
+		const auto history = peer->owner().history(peer);
+		return keys.emplace(
+			id,
+			'1' + history->chatListNameSortKey()).first->second;
+	};
+	const auto predicate = [&](const PeerListRow &a, const PeerListRow &b) {
+		return (key(a).compare(key(b)) < 0);
+	};
+	delegate()->peerListSortRows(predicate);
+}
+
 template <Notify kType>
 [[nodiscard]] Type Id() {
 	return &NotificationsTypeMetaImplementation<kType>::Meta;
@@ -103,7 +427,7 @@ void SetupChecks(
 			: ExtractRingtoneName(session->data().document(now.id));
 	};
 	settings->defaultUpdates(
-		Data::DefaultNotify::User
+		Notify::User
 	) | rpl::start_with_next([=] {
 		toneLabel->fire(label());
 	}, toneInner->lifetime());
@@ -147,9 +471,74 @@ void SetupChecks(
 }
 
 void SetupExceptions(
-	not_null<Ui::VerticalLayout*> container,
-	not_null<Window::SessionController*> controller,
-	Notify type) {
+		not_null<Ui::VerticalLayout*> container,
+		not_null<Window::SessionController*> window,
+		Notify type) {
+	const auto add = AddButton(
+		container,
+		tr::lng_notification_exceptions_add(),
+		st::settingsButtonActive,
+		{ &st::menuIconInviteSettings });
+
+	auto controller = std::make_unique<ExceptionsController>(window, type);
+	controller->setStyleOverrides(&st::settingsBlockedList);
+	const auto content = container->add(
+		object_ptr<PeerListContent>(container, controller.get()));
+
+	struct State {
+		std::unique_ptr<ExceptionsController> controller;
+		std::unique_ptr<PeerListContentDelegateSimple> delegate;
+	};
+	const auto state = content->lifetime().make_state<State>();
+	state->controller = std::move(controller);
+	state->delegate = std::make_unique<PeerListContentDelegateSimple>();
+
+	state->delegate->setContent(content);
+	state->controller->setDelegate(state->delegate.get());
+
+	add->setClickedCallback([=] {
+		const auto box = std::make_shared<QPointer<Ui::BoxContent>>();
+		const auto done = [=](not_null<PeerData*> peer) {
+			state->controller->bringToTop(peer);
+			if (*box) {
+				(*box)->closeBox();
+			}
+		};
+		auto controller = std::make_unique<AddExceptionBoxController>(
+			&window->session(),
+			type,
+			crl::guard(content, done));
+		auto initBox = [=](not_null<PeerListBox*> box) {
+			box->addButton(tr::lng_cancel(), [box] { box->closeBox(); });
+		};
+		*box = window->show(
+			Box<PeerListBox>(std::move(controller), std::move(initBox)));
+	});
+
+	const auto wrap = container->add(
+		object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
+			container,
+			CreateButton(
+				container,
+				tr::lng_notification_exceptions_clear(),
+				st::settingsAttentionButtonWithIcon,
+				{ &st::menuIconDeleteAttention })));
+	wrap->entity()->setClickedCallback([=] {
+		const auto clear = [=](Fn<void()> close) {
+			window->session().data().notifySettings().clearExceptions(type);
+			close();
+		};
+		window->show(Ui::MakeConfirmBox({
+			.text = tr::lng_notification_exceptions_clear_sure(),
+			.confirmed = clear,
+			.confirmText = tr::lng_notification_exceptions_clear_button(),
+			.confirmStyle = &st::attentionBoxButton,
+			.title = tr::lng_notification_exceptions_clear(),
+		}));
+	});
+	wrap->toggleOn(
+		state->controller->countValue() | rpl::map(rpl::mappers::_1 > 1),
+		anim::type::instant);
 }
 
 } // namespace
@@ -211,7 +600,7 @@ bool NotificationsEnabledForType(
 
 rpl::producer<bool> NotificationsEnabledForTypeValue(
 		not_null<Main::Session*> session,
-		Data::DefaultNotify type) {
+		Notify type) {
 	const auto settings = &session->data().notifySettings();
 	return rpl::single(
 		rpl::empty
diff --git a/Telegram/SourceFiles/ui/menu_icons.style b/Telegram/SourceFiles/ui/menu_icons.style
index bfb508773..715c6a2fb 100644
--- a/Telegram/SourceFiles/ui/menu_icons.style
+++ b/Telegram/SourceFiles/ui/menu_icons.style
@@ -173,6 +173,7 @@ menuIconReportAttention: icon {{ "menu/report", menuIconAttentionColor }};
 menuIconRestoreAttention: icon {{ "menu/restore", menuIconAttentionColor }};
 
 menuIconBlockSettings: icon {{ "menu/block", windowBgActive }};
+menuIconInviteSettings: icon {{ "menu/invite", windowBgActive }};
 
 playerSpeedSlow: icon {{ "player/speed/audiospeed_menu_0.5", menuIconColor }};
 playerSpeedSlowActive: icon {{ "player/speed/audiospeed_menu_0.5", mediaPlayerActiveFg }};

From 827e755552b58051cadc885f4dc534b05f0df49a Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Wed, 23 Aug 2023 19:29:56 +0200
Subject: [PATCH 04/29] Allow customizing default notifications.

---
 .../data/notify/data_notify_settings.cpp      |   7 +
 .../data/notify/data_notify_settings.h        |   1 +
 Telegram/SourceFiles/menu/menu_mute.cpp       | 196 +++++++++++-------
 Telegram/SourceFiles/menu/menu_mute.h         |  43 +++-
 .../settings/settings_notifications_type.cpp  |  21 +-
 5 files changed, 195 insertions(+), 73 deletions(-)

diff --git a/Telegram/SourceFiles/data/notify/data_notify_settings.cpp b/Telegram/SourceFiles/data/notify/data_notify_settings.cpp
index 22fc6e26f..c522b8aa5 100644
--- a/Telegram/SourceFiles/data/notify/data_notify_settings.cpp
+++ b/Telegram/SourceFiles/data/notify/data_notify_settings.cpp
@@ -289,6 +289,13 @@ const PeerNotifySettings &NotifySettings::defaultSettings(
 	return defaultValue(type).settings;
 }
 
+bool NotifySettings::isMuted(DefaultNotify type) const {
+	if (const auto until = defaultSettings(type).muteUntil()) {
+		return MutedFromUntil(*until, nullptr);
+	}
+	return true;
+}
+
 void NotifySettings::defaultUpdate(
 		DefaultNotify type,
 		MuteValue muteForSeconds,
diff --git a/Telegram/SourceFiles/data/notify/data_notify_settings.h b/Telegram/SourceFiles/data/notify/data_notify_settings.h
index b1919a319..b1930f45b 100644
--- a/Telegram/SourceFiles/data/notify/data_notify_settings.h
+++ b/Telegram/SourceFiles/data/notify/data_notify_settings.h
@@ -85,6 +85,7 @@ public:
 
 	[[nodiscard]] const PeerNotifySettings &defaultSettings(
 		DefaultNotify type) const;
+	[[nodiscard]] bool isMuted(DefaultNotify type) const;
 
 	void defaultUpdate(
 		DefaultNotify type,
diff --git a/Telegram/SourceFiles/menu/menu_mute.cpp b/Telegram/SourceFiles/menu/menu_mute.cpp
index 444747584..ac48e2740 100644
--- a/Telegram/SourceFiles/menu/menu_mute.cpp
+++ b/Telegram/SourceFiles/menu/menu_mute.cpp
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_session.h"
 #include "data/data_thread.h"
 #include "data/notify/data_notify_settings.h"
+#include "data/notify/data_peer_notify_settings.h"
 #include "info/profile/info_profile_values.h"
 #include "lang/lang_keys.h"
 #include "main/main_session.h"
@@ -31,10 +32,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "styles/style_menu_icons.h"
 
 namespace MuteMenu {
-
 namespace {
 
 constexpr auto kMuteDurSecondsDefault = crl::time(8) * 3600;
+constexpr auto kMuteForeverValue = std::numeric_limits<TimeId>::max();
 
 class IconWithText final : public Ui::Menu::Action {
 public:
@@ -70,7 +71,7 @@ public:
 	MuteItem(
 		not_null<RpWidget*> parent,
 		const style::Menu &st,
-		not_null<Data::Thread*> thread);
+		Descriptor descriptor);
 
 protected:
 	void paintEvent(QPaintEvent *e) override;
@@ -79,31 +80,30 @@ private:
 	const QPoint _itemIconPosition;
 	Ui::Animations::Simple _animation;
 	bool _isMuted = false;
+	bool _inited;
 
 };
 
 MuteItem::MuteItem(
 	not_null<RpWidget*> parent,
 	const style::Menu &st,
-	not_null<Data::Thread*> thread)
+	Descriptor descriptor)
 : Ui::Menu::Action(
 	parent,
 	st,
 	Ui::CreateChild<QAction>(parent.get()),
 	nullptr,
 	nullptr)
-, _itemIconPosition(st.itemIconPosition)
-, _isMuted(thread->owner().notifySettings().isMuted(thread)) {
-	Info::Profile::NotificationsEnabledValue(
-		thread
-	) | rpl::start_with_next([=](bool isUnmuted) {
-		const auto isMuted = !isUnmuted;
+, _itemIconPosition(st.itemIconPosition) {
+	descriptor.isMutedValue(
+	) | rpl::start_with_next([=](bool isMuted) {
 		action()->setText(isMuted
 			? tr::lng_mute_menu_duration_unmute(tr::now)
 			: tr::lng_mute_menu_duration_forever(tr::now));
-		if (isMuted == _isMuted) {
+		if (_inited && isMuted == _isMuted) {
 			return;
 		}
+		_inited = true;
 		_isMuted = isMuted;
 		_animation.start(
 			[=] { update(); },
@@ -112,13 +112,8 @@ MuteItem::MuteItem(
 			st::defaultPopupMenu.showDuration);
 	}, lifetime());
 
-	const auto weak = base::make_weak(thread);
 	setClickedCallback([=] {
-		if (const auto strong = weak.get()) {
-			strong->owner().notifySettings().update(
-				strong,
-				{ .unmute = _isMuted, .forever = !_isMuted });
-		}
+		descriptor.updateMutePeriod(_isMuted ? 0 : kMuteForeverValue);
 	});
 }
 
@@ -140,7 +135,7 @@ void MuteItem::paintEvent(QPaintEvent *e) {
 	icon.paint(p, _itemIconPosition, width(), color);
 }
 
-void MuteBox(not_null<Ui::GenericBox*> box, not_null<Data::Thread*> thread) {
+void MuteBox(not_null<Ui::GenericBox*> box, Descriptor descriptor) {
 	struct State {
 		int lastSeconds = 0;
 	};
@@ -161,14 +156,9 @@ void MuteBox(not_null<Ui::GenericBox*> box, not_null<Data::Thread*> thread) {
 			: tr::lng_mute_menu_mute();
 	}) | rpl::flatten_latest();
 
-	const auto weak = base::make_weak(thread);
 	Ui::ConfirmBox(box, {
 		.confirmed = [=] {
-			if (const auto strong = weak.get()) {
-				strong->owner().notifySettings().update(
-					strong,
-					{ .period = state->lastSeconds });
-			}
+			descriptor.updateMutePeriod(state->lastSeconds);
 			box->getDelegate()->hideLayer();
 		},
 		.confirmText = std::move(confirmText),
@@ -178,7 +168,7 @@ void MuteBox(not_null<Ui::GenericBox*> box, not_null<Data::Thread*> thread) {
 
 void PickMuteBox(
 		not_null<Ui::GenericBox*> box,
-		not_null<Data::Thread*> thread) {
+		Descriptor descriptor) {
 	struct State {
 		base::unique_qptr<Ui::PopupMenu> menu;
 	};
@@ -191,17 +181,12 @@ void PickMuteBox(
 
 	const auto pickerCallback = TimePickerBox(box, seconds, phrases, 0);
 
-	const auto weak = base::make_weak(thread);
 	Ui::ConfirmBox(box, {
 		.confirmed = [=] {
 			const auto muteFor = pickerCallback();
-			if (const auto strong = weak.get()) {
-				strong->owner().notifySettings().update(
-					strong,
-					{ .period = muteFor });
-				strong->session().settings().addMutePeriod(muteFor);
-				strong->session().saveSettings();
-			}
+			descriptor.updateMutePeriod(muteFor);
+			descriptor.session->settings().addMutePeriod(muteFor);
+			descriptor.session->saveSettings();
 			box->closeBox();
 		},
 		.confirmText = tr::lng_mute_menu_mute(),
@@ -220,11 +205,7 @@ void PickMuteBox(
 			st::popupMenuWithIcons);
 		state->menu->addAction(
 			tr::lng_manage_messages_ttl_after_custom(tr::now),
-			[=] {
-				if (const auto strong = weak.get()) {
-					box->getDelegate()->show(Box(MuteBox, strong));
-				}
-			},
+			[=] { box->getDelegate()->show(Box(MuteBox, descriptor)); },
 			&st::menuIconCustomize);
 		state->menu->setDestroyedCallback(crl::guard(top, [=] {
 			top->setForceRippled(false);
@@ -236,46 +217,124 @@ void PickMuteBox(
 
 } // namespace
 
+Descriptor ThreadDescriptor(not_null<Data::Thread*> thread) {
+	const auto weak = base::make_weak(thread);
+	const auto isMutedValue = [=]() -> rpl::producer<bool> {
+		if (const auto strong = weak.get()) {
+			return Info::Profile::NotificationsEnabledValue(
+				strong
+			) | rpl::map(!rpl::mappers::_1);
+		}
+		return rpl::single(false);
+	};
+	const auto currentSound = [=] {
+		const auto strong = weak.get();
+		return strong
+			? strong->owner().notifySettings().sound(strong)
+			: std::optional<Data::NotifySound>();
+	};
+	const auto updateSound = crl::guard(weak, [=](Data::NotifySound sound) {
+		thread->owner().notifySettings().update(thread, {}, {}, sound);
+	});
+	const auto updateMutePeriod = crl::guard(weak, [=](TimeId mute) {
+		const auto settings = &thread->owner().notifySettings();
+		if (!mute) {
+			settings->update(thread, { .unmute = true });
+		} else if (mute == kMuteForeverValue) {
+			settings->update(thread, { .forever = true });
+		} else {
+			settings->update(thread, { .period = mute });
+		}
+	});
+	return {
+		.session = &thread->session(),
+		.isMutedValue = isMutedValue,
+		.currentSound = currentSound,
+		.updateSound = updateSound,
+		.updateMutePeriod = updateMutePeriod,
+	};
+}
+
+Descriptor DefaultDescriptor(
+		not_null<Main::Session*> session,
+		Data::DefaultNotify type) {
+	const auto settings = &session->data().notifySettings();
+	const auto isMutedValue = [=]() -> rpl::producer<bool> {
+		return rpl::single(
+			rpl::empty
+		) | rpl::then(
+			settings->defaultUpdates(type)
+		) | rpl::map([=] {
+			return settings->isMuted(type);
+		});
+	};
+	const auto currentSound = [=] {
+		return settings->defaultSettings(type).sound();
+	};
+	const auto updateSound = [=](Data::NotifySound sound) {
+		settings->defaultUpdate(type, {}, {}, sound);
+	};
+	const auto updateMutePeriod = [=](TimeId mute) {
+		if (!mute) {
+			settings->defaultUpdate(type, { .unmute = true });
+		} else if (mute == kMuteForeverValue) {
+			settings->defaultUpdate(type, { .forever = true });
+		} else {
+			settings->defaultUpdate(type, { .period = mute });
+		}
+	};
+	return {
+		.session = session,
+		.isMutedValue = isMutedValue,
+		.currentSound = currentSound,
+		.updateSound = updateSound,
+		.updateMutePeriod = updateMutePeriod,
+	};
+}
+
 void FillMuteMenu(
 		not_null<Ui::PopupMenu*> menu,
-		not_null<Data::Thread*> thread,
+		Descriptor descriptor,
 		std::shared_ptr<Ui::Show> show) {
-	const auto weak = base::make_weak(thread);
-	const auto with = [=](Fn<void(not_null<Data::Thread*> thread)> handler) {
-		return [=] {
-			if (const auto strong = weak.get()) {
-				handler(strong);
-			}
-		};
+	const auto session = descriptor.session;
+	const auto soundSelect = [=] {
+		if (const auto currentSound = descriptor.currentSound()) {
+			show->showBox(Box(
+				RingtonesBox,
+				session,
+				*currentSound,
+				descriptor.updateSound));
+		}
 	};
-
 	menu->addAction(
 		tr::lng_mute_menu_sound_select(tr::now),
-		with([=](not_null<Data::Thread*> thread) {
-			show->showBox(Box(ThreadRingtonesBox, thread));
-		}),
+		soundSelect,
 		&st::menuIconSoundSelect);
 
-	const auto notifySettings = &thread->owner().notifySettings();
-	const auto soundIsNone = notifySettings->sound(thread).none;
+	const auto notifySettings = &session->data().notifySettings();
+	const auto soundIsNone = descriptor.currentSound().value_or(
+		Data::NotifySound()
+	).none;
+	const auto toggleSound = [=] {
+		if (auto sound = descriptor.currentSound()) {
+			sound->none = !soundIsNone;
+			descriptor.updateSound(*sound);
+		}
+	};
 	menu->addAction(
-		soundIsNone
+		(soundIsNone
 			? tr::lng_mute_menu_sound_on(tr::now)
-			: tr::lng_mute_menu_sound_off(tr::now),
-		with([=](not_null<Data::Thread*> thread) {
-			auto sound = notifySettings->sound(thread);
-			sound.none = !sound.none;
-			notifySettings->update(thread, {}, {}, sound);
-		}),
+			: tr::lng_mute_menu_sound_off(tr::now)),
+		toggleSound,
 		soundIsNone ? &st::menuIconSoundOn : &st::menuIconSoundOff);
 
 	const auto &st = menu->st().menu;
 	const auto iconTextPosition = st.itemIconPosition
 		+ st::menuIconMuteForAnyTextPosition;
-	for (const auto muteFor : thread->session().settings().mutePeriods()) {
-		const auto callback = with([=](not_null<Data::Thread*> thread) {
-			notifySettings->update(thread, { .period = muteFor });
-		});
+	for (const auto muteFor : session->settings().mutePeriods()) {
+		const auto callback = [=, update = descriptor.updateMutePeriod] {
+			update(muteFor);
+		};
 
 		auto item = base::make_unique_q<IconWithText>(
 			menu,
@@ -295,20 +354,17 @@ void FillMuteMenu(
 
 	menu->addAction(
 		tr::lng_mute_menu_duration(tr::now),
-		with([=](not_null<Data::Thread*> thread) {
-			DEBUG_LOG(("Mute Info: PickMuteBox called."));
-			show->showBox(Box(PickMuteBox, thread));
-		}),
+		[=] { show->showBox(Box(PickMuteBox, descriptor)); },
 		&st::menuIconMuteFor);
 
 	menu->addAction(
-		base::make_unique_q<MuteItem>(menu, menu->st().menu, thread));
+		base::make_unique_q<MuteItem>(menu, menu->st().menu, descriptor));
 }
 
 void SetupMuteMenu(
 		not_null<Ui::RpWidget*> parent,
 		rpl::producer<> triggers,
-		Fn<Data::Thread*()> makeThread,
+		Fn<std::optional<Descriptor>()> makeDescriptor,
 		std::shared_ptr<Ui::Show> show) {
 	struct State {
 		base::unique_qptr<Ui::PopupMenu> menu;
@@ -319,11 +375,11 @@ void SetupMuteMenu(
 	) | rpl::start_with_next([=] {
 		if (state->menu) {
 			return;
-		} else if (const auto thread = makeThread()) {
+		} else if (const auto descriptor = makeDescriptor()) {
 			state->menu = base::make_unique_q<Ui::PopupMenu>(
 				parent,
 				st::popupMenuWithIcons);
-			FillMuteMenu(state->menu.get(), thread, show);
+			FillMuteMenu(state->menu.get(), *descriptor, show);
 			state->menu->popup(QCursor::pos());
 		}
 	}, parent->lifetime());
diff --git a/Telegram/SourceFiles/menu/menu_mute.h b/Telegram/SourceFiles/menu/menu_mute.h
index 045cd4d11..a6d818668 100644
--- a/Telegram/SourceFiles/menu/menu_mute.h
+++ b/Telegram/SourceFiles/menu/menu_mute.h
@@ -9,8 +9,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 namespace Data {
 class Thread;
+struct NotifySound;
+enum class DefaultNotify;
 } // namespace Data
 
+namespace Main {
+class Session;
+} // namespace Main
+
 namespace Ui {
 class PopupMenu;
 class RpWidget;
@@ -19,15 +25,48 @@ class Show;
 
 namespace MuteMenu {
 
+struct Descriptor {
+	not_null<Main::Session*> session;
+	Fn<rpl::producer<bool>()> isMutedValue;
+	Fn<std::optional<Data::NotifySound>()> currentSound;
+	Fn<void(Data::NotifySound)> updateSound;
+	Fn<void(TimeId)> updateMutePeriod;
+};
+
+[[nodiscard]] Descriptor ThreadDescriptor(not_null<Data::Thread*> thread);
+[[nodiscard]] Descriptor DefaultDescriptor(
+	not_null<Main::Session*> session,
+	Data::DefaultNotify type);
+
 void FillMuteMenu(
 	not_null<Ui::PopupMenu*> menu,
-	not_null<Data::Thread*> thread,
+	Descriptor descriptor,
 	std::shared_ptr<Ui::Show> show);
 
 void SetupMuteMenu(
 	not_null<Ui::RpWidget*> parent,
 	rpl::producer<> triggers,
-	Fn<Data::Thread*()> makeThread,
+	Fn<std::optional<Descriptor>()> makeDescriptor,
 	std::shared_ptr<Ui::Show> show);
 
+inline void FillMuteMenu(
+		not_null<Ui::PopupMenu*> menu,
+		not_null<Data::Thread*> thread,
+		std::shared_ptr<Ui::Show> show) {
+	FillMuteMenu(menu, ThreadDescriptor(thread), std::move(show));
+}
+
+inline void SetupMuteMenu(
+		not_null<Ui::RpWidget*> parent,
+		rpl::producer<> triggers,
+		Fn<Data::Thread*()> makeThread,
+		std::shared_ptr<Ui::Show> show) {
+	SetupMuteMenu(parent, std::move(triggers), [=] {
+		const auto thread = makeThread();
+		return thread
+			? ThreadDescriptor(thread)
+			: std::optional<Descriptor>();
+	}, std::move(show));
+}
+
 } // namespace MuteMenu
diff --git a/Telegram/SourceFiles/settings/settings_notifications_type.cpp b/Telegram/SourceFiles/settings/settings_notifications_type.cpp
index 999ff9165..5f394face 100644
--- a/Telegram/SourceFiles/settings/settings_notifications_type.cpp
+++ b/Telegram/SourceFiles/settings/settings_notifications_type.cpp
@@ -380,7 +380,26 @@ void SetupChecks(
 			tr::lng_notification_enable(),
 			st::settingsButton,
 			{ &st::menuIconNotifications }));
-	enabled->toggleOn(NotificationsEnabledForTypeValue(session, type));
+	enabled->toggleOn(
+		NotificationsEnabledForTypeValue(session, type),
+		true);
+
+	enabled->setAcceptBoth();
+	MuteMenu::SetupMuteMenu(
+		enabled,
+		enabled->clicks(
+		) | rpl::filter([=](Qt::MouseButton button) {
+			if (button == Qt::RightButton) {
+				return true;
+			} else if (settings->isMuted(type)) {
+				settings->defaultUpdate(type, { .unmute = true });
+				return false;
+			} else {
+				return true;
+			}
+		}) | rpl::to_empty,
+		[=] { return MuteMenu::DefaultDescriptor(session, type); },
+		controller->uiShow());
 
 	const auto soundWrap = container->add(
 		object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(

From 1148a2e144f95a13fff3d7245d4a7bc2cf7fa78a Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Wed, 23 Aug 2023 19:43:35 +0200
Subject: [PATCH 05/29] Show information about default notifications toggle.

---
 .../settings/settings_notifications.cpp       | 43 +++++++++++++++++--
 1 file changed, 40 insertions(+), 3 deletions(-)

diff --git a/Telegram/SourceFiles/settings/settings_notifications.cpp b/Telegram/SourceFiles/settings/settings_notifications.cpp
index 9bf5f35d4..17e95b41a 100644
--- a/Telegram/SourceFiles/settings/settings_notifications.cpp
+++ b/Telegram/SourceFiles/settings/settings_notifications.cpp
@@ -9,8 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 #include "settings/settings_common.h"
 #include "settings/settings_notifications_type.h"
+#include "ui/boxes/confirm_box.h"
 #include "ui/controls/chat_service_checkbox.h"
 #include "ui/effects/animations.h"
+#include "ui/text/text_utilities.h"
 #include "ui/wrap/vertical_layout.h"
 #include "ui/wrap/slide_wrap.h"
 #include "ui/widgets/box_content_divider.h"
@@ -248,15 +250,50 @@ void AddTypeButton(
 			(s.height() - checkWidget->height()) / 2);
 	}, toggleButton->lifetime());
 
-	toggleButton->clicks(
-	) | rpl::start_with_next([=] {
+	const auto toggle = crl::guard(toggleButton, [=] {
 		const auto enabled = !checkView->checked();
-		const auto settings = &session->data().notifySettings();
 		checkView->setChecked(enabled, anim::type::normal);
 		settings->defaultUpdate(type, Data::MuteValue{
 			.unmute = enabled,
 			.forever = !enabled,
 		});
+	});
+	toggleButton->clicks(
+	) | rpl::start_with_next([=] {
+		const auto count = int(settings->exceptions(type).size());
+		if (!count) {
+			toggle();
+		} else {
+			controller->show(Box([=](not_null<Ui::GenericBox*> box) {
+				const auto phrase = [&] {
+					switch (type) {
+					case Type::User:
+						return tr::lng_notification_about_private_chats;
+					case Type::Group:
+						return tr::lng_notification_about_groups;
+					case Type::Broadcast:
+						return tr::lng_notification_about_channels;
+					}
+					Unexpected("Type in AddTypeButton.");
+				}();
+				Ui::ConfirmBox(box, {
+					.text = phrase(
+						lt_count,
+						rpl::single(float64(count)),
+						Ui::Text::RichLangValue),
+					.confirmed = [=](auto close) { toggle(); close(); },
+					.confirmText = tr::lng_box_ok(),
+					.title = tr::lng_notification_exceptions_title(),
+					.inform = true,
+				});
+				box->addLeftButton(
+					tr::lng_notification_exceptions_view(),
+					[=] {
+						box->closeBox();
+						showOther(NotificationsTypeId(type));
+					});
+			}));
+		}
 	}, toggleButton->lifetime());
 }
 

From 4755be4acef52f7b9c0f0955d154a1093536c2f8 Mon Sep 17 00:00:00 2001
From: Ilya Fedin <fedin-ilja2010@ya.ru>
Date: Thu, 24 Aug 2023 08:20:01 +0400
Subject: [PATCH 06/29] Add missing returns to skip taskbar Linux abstraction

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

diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp
index f6d48b3da..7db2d34e7 100644
--- a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp
+++ b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp
@@ -139,11 +139,13 @@ void XCBSetDesktopFileName(QWindow *window) {
 void SkipTaskbar(QWindow *window, bool skip) {
 	if (const auto integration = WaylandIntegration::Instance()) {
 		integration->skipTaskbar(window, skip);
+		return;
 	}
 
 #ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
 	if (IsX11()) {
 		XCBSkipTaskbar(window, skip);
+		return;
 	}
 #endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
 }

From 396635fa1d098838cf71fc800c246ee00939d7f7 Mon Sep 17 00:00:00 2001
From: Ilya Fedin <fedin-ilja2010@ya.ru>
Date: Fri, 25 Aug 2023 03:28:08 +0400
Subject: [PATCH 07/29] Make use of the new window-less
 base::Platform::XDP::ParentWindowID

---
 .../linux/linux_xdp_open_with_dialog.cpp      | 19 ++-----------------
 .../platform/linux/specific_linux.cpp         | 14 +-------------
 Telegram/lib_base                             |  2 +-
 3 files changed, 4 insertions(+), 31 deletions(-)

diff --git a/Telegram/SourceFiles/platform/linux/linux_xdp_open_with_dialog.cpp b/Telegram/SourceFiles/platform/linux/linux_xdp_open_with_dialog.cpp
index 9efcb0b68..a425ff59f 100644
--- a/Telegram/SourceFiles/platform/linux/linux_xdp_open_with_dialog.cpp
+++ b/Telegram/SourceFiles/platform/linux/linux_xdp_open_with_dialog.cpp
@@ -10,8 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "base/platform/base_platform_info.h"
 #include "base/platform/linux/base_linux_xdp_utilities.h"
 #include "base/platform/linux/base_linux_wayland_integration.h"
-#include "core/application.h"
-#include "window/window_controller.h"
 #include "base/random.h"
 
 #include <fcntl.h>
@@ -60,26 +58,13 @@ bool ShowXDPOpenWithDialog(const QString &filepath) {
 
 		const auto fdGuard = gsl::finally([&] { ::close(fd); });
 
-		const auto parentWindowId = [&]() -> Glib::ustring {
-			const auto activeWindow = Core::App().activeWindow();
-			if (!activeWindow) {
-				return {};
-			}
-
-			return base::Platform::XDP::ParentWindowID(
-				activeWindow->widget()->windowHandle());
-		}();
-
 		const auto handleToken = Glib::ustring("tdesktop")
 			+ std::to_string(base::RandomValue<uint>());
 
 		const auto activationToken = []() -> Glib::ustring {
 			using base::Platform::WaylandIntegration;
 			if (const auto integration = WaylandIntegration::Instance()) {
-				if (const auto token = integration->activationToken()
-					; !token.isNull()) {
-					return token.toStdString();
-				}
+				return integration->activationToken().toStdString();
 			}
 			return {};
 		}();
@@ -124,7 +109,7 @@ bool ShowXDPOpenWithDialog(const QString &filepath) {
 			kXDPOpenURIInterface,
 			"OpenFile",
 			Glib::create_variant(std::tuple{
-				parentWindowId,
+				base::Platform::XDP::ParentWindowID(),
 				Glib::DBusHandle(),
 				std::map<Glib::ustring, Glib::VariantBase>{
 					{
diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp
index 620b146e2..a4885388d 100644
--- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp
+++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp
@@ -17,10 +17,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "mainwindow.h"
 #include "storage/localstorage.h"
 #include "core/launcher.h"
-#include "core/application.h"
 #include "core/core_settings.h"
 #include "core/update_checker.h"
-#include "window/window_controller.h"
 #include "webview/platform/linux/webview_linux_webkitgtk.h"
 
 #ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
@@ -65,16 +63,6 @@ bool PortalAutostart(bool start, bool silent) {
 		const auto connection = Gio::DBus::Connection::get_sync(
 			Gio::DBus::BusType::SESSION);
 
-		const auto parentWindowId = [&]() -> Glib::ustring {
-			const auto activeWindow = Core::App().activeWindow();
-			if (!activeWindow) {
-				return {};
-			}
-
-			return base::Platform::XDP::ParentWindowID(
-				activeWindow->widget()->windowHandle());
-		}();
-
 		const auto handleToken = Glib::ustring("tdesktop")
 			+ std::to_string(base::RandomValue<uint>());
 
@@ -152,7 +140,7 @@ bool PortalAutostart(bool start, bool silent) {
 			"org.freedesktop.portal.Background",
 			"RequestBackground",
 			Glib::create_variant(std::tuple{
-				parentWindowId,
+				base::Platform::XDP::ParentWindowID(),
 				options,
 			}),
 			base::Platform::XDP::kService);
diff --git a/Telegram/lib_base b/Telegram/lib_base
index 0919789bf..35beda840 160000
--- a/Telegram/lib_base
+++ b/Telegram/lib_base
@@ -1 +1 @@
-Subproject commit 0919789bf555c651e32bdbcabc397dbacb6a2545
+Subproject commit 35beda8406844808a27deec9ef718c3f2a345a05

From a479fcd55caa8967a6327ef9b6cfaf425e8698fc Mon Sep 17 00:00:00 2001
From: Ilya Fedin <fedin-ilja2010@ya.ru>
Date: Fri, 25 Aug 2023 11:25:18 +0400
Subject: [PATCH 08/29] Update cmake_helpers

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

diff --git a/cmake b/cmake
index bd556e2a1..9fd1c7580 160000
--- a/cmake
+++ b/cmake
@@ -1 +1 @@
-Subproject commit bd556e2a16d34ad876054fb3ab25e83481af652a
+Subproject commit 9fd1c758053f88c2a432e61991ac6d7fbc9af263

From 77c2e12ebc8cbbeed878056ccf660425b83e944b Mon Sep 17 00:00:00 2001
From: Ilya Fedin <fedin-ilja2010@ya.ru>
Date: Sat, 26 Aug 2023 00:53:51 +0400
Subject: [PATCH 09/29] Use non-throwing directory_iterator

---
 Telegram/SourceFiles/core/utils.h | 9 +++------
 1 file changed, 3 insertions(+), 6 deletions(-)

diff --git a/Telegram/SourceFiles/core/utils.h b/Telegram/SourceFiles/core/utils.h
index c8ab4422d..325f6ccc4 100644
--- a/Telegram/SourceFiles/core/utils.h
+++ b/Telegram/SourceFiles/core/utils.h
@@ -45,12 +45,9 @@ inline QString IconName() {
 
 inline bool CanReadDirectory(const QString &path) {
 #ifndef Q_OS_MAC // directory_iterator since 10.15
-	try {
-		std::filesystem::directory_iterator(path.toStdString());
-		return true;
-	} catch (...) {
-		return false;
-	}
+	std::error_code error;
+	std::filesystem::directory_iterator(path.toStdString(), error);
+	return !error;
 #else
 	Unexpected("Not implemented.");
 #endif

From d0eb7ec52274ce739d33eaa501d503aa199a03f9 Mon Sep 17 00:00:00 2001
From: Ilya Fedin <fedin-ilja2010@ya.ru>
Date: Sun, 27 Aug 2023 01:35:39 +0400
Subject: [PATCH 10/29] Fix window extents terminology

Extents is a synonym of size but we're setting not window size.
---
 .../history_view_reactions_selector.cpp       | 64 +++++++++----------
 .../history_view_reactions_selector.h         |  2 +-
 .../media/stories/media_stories_reactions.cpp | 10 +--
 Telegram/lib_ui                               |  2 +-
 4 files changed, 39 insertions(+), 39 deletions(-)

diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp
index e24577962..c07fc9269 100644
--- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp
+++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp
@@ -241,7 +241,7 @@ int Selector::countWidth(int desiredWidth, int maxWidth) {
 	return std::max(2 * _skipx + _columns * _size, desiredWidth);
 }
 
-QMargins Selector::extentsForShadow() const {
+QMargins Selector::marginsForShadow() const {
 	const auto line = st::lineWidth;
 	return useTransparency()
 		? st::reactionCornerShadow
@@ -264,26 +264,26 @@ void Selector::setSpecialExpandTopSkip(int skip) {
 }
 
 void Selector::initGeometry(int innerTop) {
-	const auto extents = extentsForShadow();
+	const auto margins = marginsForShadow();
 	const auto parent = parentWidget()->rect();
 	const auto innerWidth = 2 * _skipx + _columns * _size;
 	const auto innerHeight = st::reactStripHeight;
 	const auto width = _useTransparency
-		? (innerWidth + extents.left() + extents.right())
+		? (innerWidth + margins.left() + margins.right())
 		: parent.width();
-	const auto height = innerHeight + extents.top() + extents.bottom();
+	const auto height = innerHeight + margins.top() + margins.bottom();
 	const auto left = style::RightToLeft() ? 0 : (parent.width() - width);
 	_collapsedTopSkip = _useTransparency
 		? (extendTopForCategories() + _specialExpandTopSkip)
 		: 0;
-	const auto top = innerTop - extents.top() - _collapsedTopSkip;
-	const auto add = _st.icons.stripBubble.height() - extents.bottom();
+	const auto top = innerTop - margins.top() - _collapsedTopSkip;
+	const auto add = _st.icons.stripBubble.height() - margins.bottom();
 	_outer = QRect(0, _collapsedTopSkip, width, height);
 	_outerWithBubble = _outer.marginsAdded({ 0, 0, 0, add });
 	setGeometry(_outerWithBubble.marginsAdded(
 		{ 0, _collapsedTopSkip, 0, 0 }
 	).translated(left, top));
-	_inner = _outer.marginsRemoved(extents);
+	_inner = _outer.marginsRemoved(margins);
 
 	if (!_strip) {
 		expand();
@@ -343,9 +343,9 @@ void Selector::paintAppearing(QPainter &p) {
 	}
 	_paintBuffer.fill(_st.bg->c);
 	auto q = QPainter(&_paintBuffer);
-	const auto extents = extentsForShadow();
+	const auto margins = marginsForShadow();
 	const auto appearedWidth = countAppearedWidth(_appearProgress);
-	const auto fullWidth = _inner.x() + appearedWidth + extents.right();
+	const auto fullWidth = _inner.x() + appearedWidth + margins.right();
 	const auto size = QSize(fullWidth, _outer.height());
 
 	q.translate(_inner.topLeft() - QPoint(0, _collapsedTopSkip));
@@ -455,7 +455,7 @@ auto Selector::paintExpandingBg(QPainter &p, float64 progress)
 	const auto radius = _reactions.customAllowed
 		? (radiusStart + progress * (radiusEnd - radiusStart))
 		: radiusStart;
-	const auto extents = extentsForShadow();
+	const auto margins = marginsForShadow();
 	const auto expanding = anim::easeOutCirc(1., progress);
 	const auto expandUp = anim::interpolate(0, _collapsedTopSkip, expanding);
 	const auto expandDown = anim::interpolate(
@@ -470,7 +470,7 @@ auto Selector::paintExpandingBg(QPainter &p, float64 progress)
 			p.fillRect(fill, _st.bg);
 		}
 	} else {
-		const auto inner = outer.marginsRemoved(extentsForShadow());
+		const auto inner = outer.marginsRemoved(marginsForShadow());
 		p.fillRect(inner, _st.bg);
 		p.fillRect(
 			inner.x(),
@@ -483,7 +483,7 @@ auto Selector::paintExpandingBg(QPainter &p, float64 progress)
 		0,
 		extendTopForCategories(),
 		expanding);
-	const auto inner = outer.marginsRemoved(extents);
+	const auto inner = outer.marginsRemoved(margins);
 	_shadowTop = inner.y() + categories;
 	_shadowSkip = (_useTransparency && categories < radius)
 		? int(base::SafeRound(
@@ -494,7 +494,7 @@ auto Selector::paintExpandingBg(QPainter &p, float64 progress)
 		.list = inner.marginsRemoved({ 0, categories, 0, 0 }),
 		.radius = radius,
 		.expanding = expanding,
-		.finalBottom = height() - extents.bottom(),
+		.finalBottom = height() - margins.bottom(),
 	};
 }
 
@@ -521,7 +521,7 @@ void Selector::paintExpanded(QPainter &p) {
 	if (_useTransparency) {
 		p.drawImage(0, 0, _paintBuffer);
 	} else {
-		const auto inner = rect().marginsRemoved(extentsForShadow());
+		const auto inner = rect().marginsRemoved(marginsForShadow());
 		p.fillRect(inner, _st.bg);
 		p.fillRect(
 			inner.x(),
@@ -694,13 +694,13 @@ void Selector::expand() {
 	_willExpand.fire({});
 	preloadAllRecentsAnimations();
 	const auto parent = parentWidget()->geometry();
-	const auto extents = extentsForShadow();
+	const auto margins = marginsForShadow();
 	const auto heightLimit = _reactions.customAllowed
 		? st::emojiPanMaxHeight
 		: minimalHeight();
 	const auto willBeHeight = std::min(
 		parent.height() - y(),
-		extents.top() + heightLimit + extents.bottom());
+		margins.top() + heightLimit + margins.bottom());
 	const auto additionalBottom = willBeHeight - height();
 	const auto additional = _specialExpandTopSkip + additionalBottom;
 	if (additionalBottom < 0 || additional <= 0) {
@@ -834,7 +834,7 @@ void Selector::createList() {
 	_list->jumpedToPremium(
 	) | rpl::start_with_next(_jumpedToPremium, _list->lifetime());
 
-	const auto inner = rect().marginsRemoved(extentsForShadow());
+	const auto inner = rect().marginsRemoved(marginsForShadow());
 	const auto footer = _reactions.customAllowed
 		? _list->createFooter().data()
 		: nullptr;
@@ -904,16 +904,16 @@ bool AdjustMenuGeometryForSelector(
 	const auto desiredWidth = menu->menu()->width() + added;
 	const auto maxWidth = menu->st().menu.widthMax + added;
 	const auto width = selector->countWidth(desiredWidth, maxWidth);
-	const auto extents = selector->extentsForShadow();
+	const auto margins = selector->marginsForShadow();
 	const auto categoriesTop = selector->useTransparency()
 		? selector->extendTopForCategories()
 		: 0;
 	menu->setForceWidth(width - added);
 	const auto height = menu->height();
-	const auto fullTop = extents.top() + categoriesTop + extend.top();
-	const auto minimalHeight = extents.top()
+	const auto fullTop = margins.top() + categoriesTop + extend.top();
+	const auto minimalHeight = margins.top()
 		+ selector->minimalHeight()
-		+ extents.bottom();
+		+ margins.bottom();
 	const auto willBeHeightWithoutBottomPadding = fullTop
 		+ height
 		- menu->st().shadow.extend.top();
@@ -924,15 +924,15 @@ bool AdjustMenuGeometryForSelector(
 			? (minimalHeight - willBeHeightWithoutBottomPadding)
 			: 0);
 	menu->setAdditionalMenuPadding(QMargins(
-		extents.left() + extend.left(),
+		margins.left() + extend.left(),
 		fullTop,
-		extents.right() + extend.right(),
+		margins.right() + extend.right(),
 		additionalPaddingBottom
 	), QMargins(
-		extents.left(),
-		extents.top(),
-		extents.right(),
-		std::min(additionalPaddingBottom, extents.bottom())
+		margins.left(),
+		margins.top(),
+		margins.right(),
+		std::min(additionalPaddingBottom, margins.bottom())
 	));
 	if (!menu->prepareGeometryFor(desiredPosition)) {
 		return false;
@@ -944,14 +944,14 @@ bool AdjustMenuGeometryForSelector(
 		return true;
 	}
 	menu->setAdditionalMenuPadding(QMargins(
-		extents.left() + extend.left(),
+		margins.left() + extend.left(),
 		fullTop + additionalPaddingBottom,
-		extents.right() + extend.right(),
+		margins.right() + extend.right(),
 		0
 	), QMargins(
-		extents.left(),
-		extents.top(),
-		extents.right(),
+		margins.left(),
+		margins.top(),
+		margins.right(),
 		0
 	));
 	selector->setSpecialExpandTopSkip(additionalPaddingBottom);
diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h
index 8d3b34a03..bf9ddb72b 100644
--- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h
+++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h
@@ -61,7 +61,7 @@ public:
 	[[nodiscard]] bool useTransparency() const;
 
 	int countWidth(int desiredWidth, int maxWidth);
-	[[nodiscard]] QMargins extentsForShadow() const;
+	[[nodiscard]] QMargins marginsForShadow() const;
 	[[nodiscard]] int extendTopForCategories() const;
 	[[nodiscard]] int minimalHeight() const;
 	[[nodiscard]] int countAppearedWidth(float64 progress) const;
diff --git a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp
index 02b42d593..8057829e3 100644
--- a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp
+++ b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp
@@ -243,18 +243,18 @@ void Reactions::Panel::create() {
 	const auto desiredWidth = st::storiesReactionsWidth;
 	const auto maxWidth = desiredWidth * 2;
 	const auto width = _selector->countWidth(desiredWidth, maxWidth);
-	const auto extents = _selector->extentsForShadow();
+	const auto margins = _selector->marginsForShadow();
 	const auto categoriesTop = _selector->extendTopForCategories();
-	const auto full = extents.left() + width + extents.right();
+	const auto full = margins.left() + width + margins.right();
 
 	_shownValue = 0.;
 	rpl::combine(
 		_controller->layoutValue(),
 		_shownValue.value()
 	) | rpl::start_with_next([=](const Layout &layout, float64 shown) {
-		const auto width = extents.left()
+		const auto width = margins.left()
 			+ _selector->countAppearedWidth(shown)
-			+ extents.right();
+			+ margins.right();
 		const auto height = layout.reactions.height();
 		const auto shift = (width / 2);
 		const auto right = (mode == Mode::Message)
@@ -271,7 +271,7 @@ void Reactions::Panel::create() {
 		const auto innerTop = height
 			- st::storiesReactionsBottomSkip
 			- st::reactStripHeight;
-		const auto maxAdded = innerTop - extents.top() - categoriesTop;
+		const auto maxAdded = innerTop - margins.top() - categoriesTop;
 		const auto added = std::min(maxAdded, st::storiesReactionsAddedTop);
 		_selector->setSpecialExpandTopSkip(added);
 		_selector->initGeometry(innerTop);
diff --git a/Telegram/lib_ui b/Telegram/lib_ui
index 552db4b24..bcf88b906 160000
--- a/Telegram/lib_ui
+++ b/Telegram/lib_ui
@@ -1 +1 @@
-Subproject commit 552db4b24f4542dcb6d19302d7e29ff00349b156
+Subproject commit bcf88b90658c7e0925c11c486732d874477baf0d

From 48206bcf95b580984b44e905fb5530965c6173c0 Mon Sep 17 00:00:00 2001
From: Ilya Fedin <fedin-ilja2010@ya.ru>
Date: Sun, 27 Aug 2023 00:08:29 +0400
Subject: [PATCH 11/29] Re-use base_linux_library

---
 .../SourceFiles/ffmpeg/ffmpeg_utility.cpp     | 31 ++++++-------------
 1 file changed, 9 insertions(+), 22 deletions(-)

diff --git a/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp b/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp
index e105cd441..a2072b065 100644
--- a/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp
+++ b/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp
@@ -10,19 +10,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "base/algorithm.h"
 #include "logs.h"
 
+#if !defined DESKTOP_APP_USE_PACKAGED && !defined Q_OS_WIN && !defined Q_OS_MAC
+#include "base/platform/linux/base_linux_library.h"
+#include <deque>
+#endif // !DESKTOP_APP_USE_PACKAGED && !Q_OS_WIN && !Q_OS_MAC
+
 #include <QImage>
 
 #ifdef LIB_FFMPEG_USE_QT_PRIVATE_API
 #include <private/qdrawhelper_p.h>
 #endif // LIB_FFMPEG_USE_QT_PRIVATE_API
 
-#include <deque>
-
 extern "C" {
 #include <libavutil/opt.h>
-#if !defined DESKTOP_APP_USE_PACKAGED && !defined Q_OS_WIN && !defined Q_OS_MAC
-#include <dlfcn.h>
-#endif // !DESKTOP_APP_USE_PACKAGED && !Q_OS_WIN && !Q_OS_MAC
 } // extern "C"
 
 namespace FFmpeg {
@@ -95,19 +95,10 @@ void PremultiplyLine(uchar *dst, const uchar *src, int intsCount) {
 	auto list = std::deque{
 		AV_PIX_FMT_CUDA,
 	};
-	const auto vdpau = [&] {
-		if (const auto handle = dlopen("libvdpau.so.1", RTLD_LAZY)) {
-			dlclose(handle);
-		}
-		if (dlerror()) {
-			return false;
-		}
-		return true;
-	}();
-	if (vdpau) {
+	if (base::Platform::LoadLibrary("libvdpau.so.1")) {
 		list.push_front(AV_PIX_FMT_VDPAU);
 	}
-	const auto va = [&] {
+	if ([&] {
 		const auto list = std::array{
 			"libva-drm.so.1",
 			"libva-x11.so.1",
@@ -115,16 +106,12 @@ void PremultiplyLine(uchar *dst, const uchar *src, int intsCount) {
 			"libdrm.so.2",
 		};
 		for (const auto lib : list) {
-			if (const auto handle = dlopen(lib, RTLD_LAZY)) {
-				dlclose(handle);
-			}
-			if (dlerror()) {
+			if (!base::Platform::LoadLibrary(lib)) {
 				return false;
 			}
 		}
 		return true;
-	}();
-	if (va) {
+	}()) {
 		list.push_front(AV_PIX_FMT_VAAPI);
 	}
 	return list;

From 02e37ab2f202d51b8dbcf5cc554d0da2a82ae060 Mon Sep 17 00:00:00 2001
From: Ilya Fedin <fedin-ilja2010@ya.ru>
Date: Tue, 29 Aug 2023 10:17:46 +0400
Subject: [PATCH 12/29] Update submodules

---
 Telegram/lib_base       | 2 +-
 Telegram/lib_spellcheck | 2 +-
 Telegram/lib_webview    | 2 +-
 cmake                   | 2 +-
 4 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/Telegram/lib_base b/Telegram/lib_base
index 35beda840..764b55db1 160000
--- a/Telegram/lib_base
+++ b/Telegram/lib_base
@@ -1 +1 @@
-Subproject commit 35beda8406844808a27deec9ef718c3f2a345a05
+Subproject commit 764b55db1a4a8b9d8ba966d7f0fa46c9f384737e
diff --git a/Telegram/lib_spellcheck b/Telegram/lib_spellcheck
index b94ec0107..c989a9f41 160000
--- a/Telegram/lib_spellcheck
+++ b/Telegram/lib_spellcheck
@@ -1 +1 @@
-Subproject commit b94ec0107b03f13c28f336642411c6df21107a63
+Subproject commit c989a9f41e5bd8268587af2256efa89327cb3ae0
diff --git a/Telegram/lib_webview b/Telegram/lib_webview
index 585056693..5b9092bcb 160000
--- a/Telegram/lib_webview
+++ b/Telegram/lib_webview
@@ -1 +1 @@
-Subproject commit 5850566934f2f6cae56646c36cb95f85c8a9c752
+Subproject commit 5b9092bcb27a207fed3cb2155bb98db46d7cedfa
diff --git a/cmake b/cmake
index 9fd1c7580..dbe089746 160000
--- a/cmake
+++ b/cmake
@@ -1 +1 @@
-Subproject commit 9fd1c758053f88c2a432e61991ac6d7fbc9af263
+Subproject commit dbe089746f9ae84456f3c5dafa392190a88db2d1

From 7b184e553bbf04aca6bfaf1f9a0e54de33d02576 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Tue, 29 Aug 2023 17:02:23 +0400
Subject: [PATCH 13/29] Fix build with Xcode.

---
 .../settings/settings_notifications_type.h    | 36 +++++++++----------
 1 file changed, 17 insertions(+), 19 deletions(-)

diff --git a/Telegram/SourceFiles/settings/settings_notifications_type.h b/Telegram/SourceFiles/settings/settings_notifications_type.h
index 4cb04c7a2..59014f0d5 100644
--- a/Telegram/SourceFiles/settings/settings_notifications_type.h
+++ b/Telegram/SourceFiles/settings/settings_notifications_type.h
@@ -16,25 +16,6 @@ enum class DefaultNotify;
 
 namespace Settings {
 
-class NotificationsType;
-
-template <Data::DefaultNotify kType>
-struct NotificationsTypeMetaImplementation : SectionMeta {
-	object_ptr<AbstractSection> create(
-		not_null<QWidget*> parent,
-		not_null<Window::SessionController*> controller
-	) const final override {
-		return object_ptr<NotificationsType>(parent, controller, kType);
-	}
-
-	[[nodiscard]] static not_null<SectionMeta*> Meta() {
-		static NotificationsTypeMetaImplementation result;
-		return &result;
-	}
-};
-
-[[nodiscard]] Type NotificationsTypeId(Data::DefaultNotify type);
-
 class NotificationsType : public AbstractSection {
 public:
 	NotificationsType(
@@ -53,6 +34,23 @@ private:
 
 };
 
+template <Data::DefaultNotify kType>
+struct NotificationsTypeMetaImplementation : SectionMeta {
+	object_ptr<AbstractSection> create(
+		not_null<QWidget*> parent,
+		not_null<Window::SessionController*> controller
+	) const final override {
+		return object_ptr<NotificationsType>(parent, controller, kType);
+	}
+
+	[[nodiscard]] static not_null<SectionMeta*> Meta() {
+		static NotificationsTypeMetaImplementation result;
+		return &result;
+	}
+};
+
+[[nodiscard]] Type NotificationsTypeId(Data::DefaultNotify type);
+
 [[nodiscard]] bool NotificationsEnabledForType(
 	not_null<Main::Session*> session,
 	Data::DefaultNotify type);

From cfe3285e68ffb33613e379053a83a8cd309007b6 Mon Sep 17 00:00:00 2001
From: Ilya Fedin <fedin-ilja2010@ya.ru>
Date: Tue, 29 Aug 2023 17:44:38 +0400
Subject: [PATCH 14/29] Update cmake_helpers

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

diff --git a/cmake b/cmake
index dbe089746..385ba19a5 160000
--- a/cmake
+++ b/cmake
@@ -1 +1 @@
-Subproject commit dbe089746f9ae84456f3c5dafa392190a88db2d1
+Subproject commit 385ba19a52ee7fc04aeac5a55d717896e8a4793e

From 374e95de3187e780df6e264229b7e49a3ce5af47 Mon Sep 17 00:00:00 2001
From: Ilya Fedin <fedin-ilja2010@ya.ru>
Date: Wed, 30 Aug 2023 06:51:01 +0400
Subject: [PATCH 15/29] Update kimageformats

---
 Telegram/ThirdParty/kimageformats | 2 +-
 cmake                             | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/Telegram/ThirdParty/kimageformats b/Telegram/ThirdParty/kimageformats
index 63056c52f..b2b677b8a 160000
--- a/Telegram/ThirdParty/kimageformats
+++ b/Telegram/ThirdParty/kimageformats
@@ -1 +1 @@
-Subproject commit 63056c52f91aa4a34bb0637f1723848dc25a5f87
+Subproject commit b2b677b8a5e4c3cf34790eb990218217bf867c18
diff --git a/cmake b/cmake
index 385ba19a5..121c2afbb 160000
--- a/cmake
+++ b/cmake
@@ -1 +1 @@
-Subproject commit 385ba19a52ee7fc04aeac5a55d717896e8a4793e
+Subproject commit 121c2afbbbf0ea89522da79cff56380fb1796c34

From 4c2be58dd382444739a81dcd3df26aff9bff81c5 Mon Sep 17 00:00:00 2001
From: Ilya Fedin <fedin-ilja2010@ya.ru>
Date: Wed, 30 Aug 2023 07:03:45 +0400
Subject: [PATCH 16/29] Update Qt patches on Linux

---
 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 912fa52bf..2c2ac3de2 100644
--- a/Telegram/build/docker/centos_env/Dockerfile
+++ b/Telegram/build/docker/centos_env/Dockerfile
@@ -59,7 +59,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 dbd8a5b00f5a0e7e09b11878b9e46afeb4b593c0 \
+	&& git fetch --depth=1 origin 2cf975200b9baac520b9b298776e0a20b24e397a \
 	&& git reset --hard FETCH_HEAD \
 	&& rm -rf .git
 

From 95b26911e0272bcddeb8272d86f72e022d5f27b6 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Thu, 24 Aug 2023 18:02:20 +0200
Subject: [PATCH 17/29] Use inline image/path expanding from lib_ui.

---
 Telegram/SourceFiles/ui/image/image.cpp | 254 ------------------------
 Telegram/SourceFiles/ui/image/image.h   |   8 -
 2 files changed, 262 deletions(-)

diff --git a/Telegram/SourceFiles/ui/image/image.cpp b/Telegram/SourceFiles/ui/image/image.cpp
index 17f95e354..14cefdb5a 100644
--- a/Telegram/SourceFiles/ui/image/image.cpp
+++ b/Telegram/SourceFiles/ui/image/image.cpp
@@ -41,260 +41,6 @@ namespace {
 
 } // namespace
 
-QByteArray ExpandInlineBytes(const QByteArray &bytes) {
-	if (bytes.size() < 3 || bytes[0] != '\x01') {
-		return QByteArray();
-	}
-	const char header[] = "\xff\xd8\xff\xe0\x00\x10\x4a\x46\x49"
-		"\x46\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00\xff\xdb\x00\x43\x00\x28\x1c"
-		"\x1e\x23\x1e\x19\x28\x23\x21\x23\x2d\x2b\x28\x30\x3c\x64\x41\x3c\x37\x37"
-		"\x3c\x7b\x58\x5d\x49\x64\x91\x80\x99\x96\x8f\x80\x8c\x8a\xa0\xb4\xe6\xc3"
-		"\xa0\xaa\xda\xad\x8a\x8c\xc8\xff\xcb\xda\xee\xf5\xff\xff\xff\x9b\xc1\xff"
-		"\xff\xff\xfa\xff\xe6\xfd\xff\xf8\xff\xdb\x00\x43\x01\x2b\x2d\x2d\x3c\x35"
-		"\x3c\x76\x41\x41\x76\xf8\xa5\x8c\xa5\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8"
-		"\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8"
-		"\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8"
-		"\xf8\xf8\xf8\xf8\xf8\xff\xc0\x00\x11\x08\x00\x00\x00\x00\x03\x01\x22\x00"
-		"\x02\x11\x01\x03\x11\x01\xff\xc4\x00\x1f\x00\x00\x01\x05\x01\x01\x01\x01"
-		"\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08"
-		"\x09\x0a\x0b\xff\xc4\x00\xb5\x10\x00\x02\x01\x03\x03\x02\x04\x03\x05\x05"
-		"\x04\x04\x00\x00\x01\x7d\x01\x02\x03\x00\x04\x11\x05\x12\x21\x31\x41\x06"
-		"\x13\x51\x61\x07\x22\x71\x14\x32\x81\x91\xa1\x08\x23\x42\xb1\xc1\x15\x52"
-		"\xd1\xf0\x24\x33\x62\x72\x82\x09\x0a\x16\x17\x18\x19\x1a\x25\x26\x27\x28"
-		"\x29\x2a\x34\x35\x36\x37\x38\x39\x3a\x43\x44\x45\x46\x47\x48\x49\x4a\x53"
-		"\x54\x55\x56\x57\x58\x59\x5a\x63\x64\x65\x66\x67\x68\x69\x6a\x73\x74\x75"
-		"\x76\x77\x78\x79\x7a\x83\x84\x85\x86\x87\x88\x89\x8a\x92\x93\x94\x95\x96"
-		"\x97\x98\x99\x9a\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xb2\xb3\xb4\xb5\xb6"
-		"\xb7\xb8\xb9\xba\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xd2\xd3\xd4\xd5\xd6"
-		"\xd7\xd8\xd9\xda\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xf1\xf2\xf3\xf4"
-		"\xf5\xf6\xf7\xf8\xf9\xfa\xff\xc4\x00\x1f\x01\x00\x03\x01\x01\x01\x01\x01"
-		"\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08"
-		"\x09\x0a\x0b\xff\xc4\x00\xb5\x11\x00\x02\x01\x02\x04\x04\x03\x04\x07\x05"
-		"\x04\x04\x00\x01\x02\x77\x00\x01\x02\x03\x11\x04\x05\x21\x31\x06\x12\x41"
-		"\x51\x07\x61\x71\x13\x22\x32\x81\x08\x14\x42\x91\xa1\xb1\xc1\x09\x23\x33"
-		"\x52\xf0\x15\x62\x72\xd1\x0a\x16\x24\x34\xe1\x25\xf1\x17\x18\x19\x1a\x26"
-		"\x27\x28\x29\x2a\x35\x36\x37\x38\x39\x3a\x43\x44\x45\x46\x47\x48\x49\x4a"
-		"\x53\x54\x55\x56\x57\x58\x59\x5a\x63\x64\x65\x66\x67\x68\x69\x6a\x73\x74"
-		"\x75\x76\x77\x78\x79\x7a\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x92\x93\x94"
-		"\x95\x96\x97\x98\x99\x9a\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xb2\xb3\xb4"
-		"\xb5\xb6\xb7\xb8\xb9\xba\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xd2\xd3\xd4"
-		"\xd5\xd6\xd7\xd8\xd9\xda\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xf2\xf3\xf4"
-		"\xf5\xf6\xf7\xf8\xf9\xfa\xff\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11\x00"
-		"\x3f\x00";
-	const char footer[] = "\xff\xd9";
-	auto real = QByteArray(header, sizeof(header) - 1);
-	real[164] = bytes[1];
-	real[166] = bytes[2];
-	return real
-		+ bytes.mid(3)
-		+ QByteArray::fromRawData(footer, sizeof(footer) - 1);
-}
-
-QImage FromInlineBytes(const QByteArray &bytes) {
-	return Read({ .content = ExpandInlineBytes(bytes) }).image;
-}
-
-// Thanks TDLib for code.
-QByteArray ExpandPathInlineBytes(const QByteArray &bytes) {
-	auto result = QByteArray();
-	result.reserve(3 * (bytes.size() + 1));
-	result.append('M');
-	for (unsigned char c : bytes) {
-		if (c >= 128 + 64) {
-			result.append("AACAAAAHAAALMAAAQASTAVAAAZ"
-				"aacaaaahaaalmaaaqastava.az0123456789-,"[c - 128 - 64]);
-		} else {
-			if (c >= 128) {
-				result.append(',');
-			} else if (c >= 64) {
-				result.append('-');
-			}
-			//char buffer[3] = { 0 }; // Unavailable on macOS < 10.15.
-			//std::to_chars(buffer, buffer + 3, (c & 63));
-			//result.append(buffer);
-			result.append(QByteArray::number(c & 63));
-		}
-	}
-	result.append('z');
-	return result;
-}
-
-QPainterPath PathFromInlineBytes(const QByteArray &bytes) {
-	if (bytes.isEmpty()) {
-		return QPainterPath();
-	}
-	const auto expanded = ExpandPathInlineBytes(bytes);
-	const auto path = expanded.data(); // Allows checking for '\0' by index.
-	auto position = 0;
-
-	const auto isAlpha = [](char c) {
-		c |= 0x20;
-		return 'a' <= c && c <= 'z';
-	};
-	const auto isDigit = [](char c) {
-		return '0' <= c && c <= '9';
-	};
-	const auto skipCommas = [&] {
-		while (path[position] == ',') {
-			++position;
-		}
-	};
-	const auto getNumber = [&] {
-		skipCommas();
-		auto sign = 1;
-		if (path[position] == '-') {
-			sign = -1;
-			++position;
-		}
-		double res = 0;
-		while (isDigit(path[position])) {
-			res = res * 10 + path[position++] - '0';
-		}
-		if (path[position] == '.') {
-			++position;
-			double mul = 0.1;
-			while (isDigit(path[position])) {
-				res += (path[position] - '0') * mul;
-				mul *= 0.1;
-				++position;
-			}
-		}
-		return sign * res;
-	};
-
-	auto result = QPainterPath();
-	auto x = 0.;
-	auto y = 0.;
-	while (path[position] != '\0') {
-		skipCommas();
-		if (path[position] == '\0') {
-			break;
-		}
-
-		while (path[position] == 'm' || path[position] == 'M') {
-			auto command = path[position++];
-			do {
-				if (command == 'm') {
-					x += getNumber();
-					y += getNumber();
-				} else {
-					x = getNumber();
-					y = getNumber();
-				}
-				skipCommas();
-			} while (path[position] != '\0' && !isAlpha(path[position]));
-		}
-
-		auto xStart = x;
-		auto yStart = y;
-		result.moveTo(xStart, yStart);
-		auto haveLastEndControlPoint = false;
-		auto xLastEndControlPoint = 0.;
-		auto yLastEndControlPoint = 0.;
-		auto isClosed = false;
-		auto command = '-';
-		while (!isClosed) {
-			skipCommas();
-			if (path[position] == '\0') {
-				LOG(("SVG Error: Receive unclosed path: %1"
-					).arg(QString::fromLatin1(path)));
-				return QPainterPath();
-			}
-			if (isAlpha(path[position])) {
-				command = path[position++];
-			}
-			switch (command) {
-			case 'l':
-			case 'L':
-			case 'h':
-			case 'H':
-			case 'v':
-			case 'V':
-				if (command == 'l' || command == 'h') {
-					x += getNumber();
-				} else if (command == 'L' || command == 'H') {
-					x = getNumber();
-				}
-				if (command == 'l' || command == 'v') {
-					y += getNumber();
-				} else if (command == 'L' || command == 'V') {
-					y = getNumber();
-				}
-				result.lineTo(x, y);
-				haveLastEndControlPoint = false;
-				break;
-			case 'C':
-			case 'c':
-			case 'S':
-			case 's': {
-				auto xStartControlPoint = 0.;
-				auto yStartControlPoint = 0.;
-				if (command == 'S' || command == 's') {
-					if (haveLastEndControlPoint) {
-						xStartControlPoint = 2 * x - xLastEndControlPoint;
-						yStartControlPoint = 2 * y - yLastEndControlPoint;
-					} else {
-						xStartControlPoint = x;
-						yStartControlPoint = y;
-					}
-				} else {
-					xStartControlPoint = getNumber();
-					yStartControlPoint = getNumber();
-					if (command == 'c') {
-						xStartControlPoint += x;
-						yStartControlPoint += y;
-					}
-				}
-
-				xLastEndControlPoint = getNumber();
-				yLastEndControlPoint = getNumber();
-				if (command == 'c' || command == 's') {
-					xLastEndControlPoint += x;
-					yLastEndControlPoint += y;
-				}
-				haveLastEndControlPoint = true;
-
-				if (command == 'c' || command == 's') {
-					x += getNumber();
-					y += getNumber();
-				} else {
-					x = getNumber();
-					y = getNumber();
-				}
-				result.cubicTo(
-					xStartControlPoint,
-					yStartControlPoint,
-					xLastEndControlPoint,
-					yLastEndControlPoint,
-					x,
-					y);
-				break;
-			}
-			case 'm':
-			case 'M':
-				--position;
-				[[fallthrough]];
-			case 'z':
-			case 'Z':
-				if (x != xStart || y != yStart) {
-					x = xStart;
-					y = yStart;
-					result.lineTo(x, y);
-				}
-				isClosed = true;
-				break;
-			default:
-				LOG(("SVG Error: Receive invalid command %1 at pos %2: %3"
-					).arg(command
-					).arg(position
-					).arg(QString::fromLatin1(path)));
-				return QPainterPath();
-			}
-		}
-	}
-	return result;
-}
-
 } // namespace Images
 
 Image::Image(const QString &path)
diff --git a/Telegram/SourceFiles/ui/image/image.h b/Telegram/SourceFiles/ui/image/image.h
index 1735a87a2..61095ce3c 100644
--- a/Telegram/SourceFiles/ui/image/image.h
+++ b/Telegram/SourceFiles/ui/image/image.h
@@ -11,14 +11,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 class QPainterPath;
 
-namespace Images {
-
-[[nodiscard]] QByteArray ExpandInlineBytes(const QByteArray &bytes);
-[[nodiscard]] QImage FromInlineBytes(const QByteArray &bytes);
-[[nodiscard]] QPainterPath PathFromInlineBytes(const QByteArray &bytes);
-
-} // namespace Images
-
 class Image final {
 public:
 	explicit Image(const QString &path);

From 0d4a83ea47d1368b0b83e8aab55a557abab5cf2c Mon Sep 17 00:00:00 2001
From: 23rd <23rd@vivaldi.net>
Date: Wed, 23 Aug 2023 17:10:24 +0300
Subject: [PATCH 18/29] Added ability to remove sticker set from tab of
 featured sticker sets.

---
 Telegram/SourceFiles/boxes/stickers_box.cpp   | 124 ++++++++++++++----
 .../chat_helpers/chat_helpers.style           |   7 +-
 2 files changed, 100 insertions(+), 31 deletions(-)

diff --git a/Telegram/SourceFiles/boxes/stickers_box.cpp b/Telegram/SourceFiles/boxes/stickers_box.cpp
index 237306d84..355cb649c 100644
--- a/Telegram/SourceFiles/boxes/stickers_box.cpp
+++ b/Telegram/SourceFiles/boxes/stickers_box.cpp
@@ -105,9 +105,14 @@ public:
 	void setFullOrder(const StickersSetsOrder &order);
 	void setRemovedSets(const StickersSetsOrder &removed);
 
+	void setRowRemovedBySetId(uint64 setId, bool removed);
+
 	void setInstallSetCallback(Fn<void(uint64 setId)> callback) {
 		_installSetCallback = std::move(callback);
 	}
+	void setRemoveSetCallback(Fn<void(uint64 setId)> callback) {
+		_removeSetCallback = std::move(callback);
+	}
 	void setLoadMoreCallback(Fn<void()> callback) {
 		_loadMoreCallback = std::move(callback);
 	}
@@ -212,7 +217,11 @@ private:
 	void setPressed(SelectedRow pressed);
 	void setup();
 	QRect relativeButtonRect(bool removeButton, bool installedSet) const;
-	void ensureRipple(const style::RippleAnimation &st, QImage mask, bool removeButton);
+	void ensureRipple(
+		const style::RippleAnimation &st,
+		QImage mask,
+		bool removeButton,
+		bool installedSet);
 
 	bool shiftingAnimationCallback(crl::time now);
 	void paintRow(Painter &p, not_null<Row*> row, int index);
@@ -270,6 +279,7 @@ private:
 	Ui::Animations::Basic _shiftingAnimation;
 
 	Fn<void(uint64 setId)> _installSetCallback;
+	Fn<void(uint64 setId)> _removeSetCallback;
 	Fn<void()> _loadMoreCallback;
 
 	int _visibleTop = 0;
@@ -598,17 +608,43 @@ void StickersBox::prepare() {
 		_attached.widget()->hide();
 	}
 
-	const auto installCallback = [=](uint64 setId) { installSet(setId); };
-	if (_featured.widget()) {
-		_featured.widget()->setInstallSetCallback(installCallback);
-	}
-	if (_archived.widget()) {
-		_archived.widget()->setInstallSetCallback(installCallback);
-		_archived.widget()->setLoadMoreCallback([=] { loadMoreArchived(); });
-	}
-	if (_attached.widget()) {
-		_attached.widget()->setInstallSetCallback(installCallback);
-		_attached.widget()->setLoadMoreCallback([=] { showAttachedStickers(); });
+	{
+		const auto installCallback = [=](uint64 setId) { installSet(setId); };
+		const auto markAsInstalledCallback = [=](uint64 setId) {
+			if (_installed.widget()) {
+				_installed.widget()->setRowRemovedBySetId(setId, false);
+			}
+			if (_featured.widget()) {
+				_featured.widget()->setRowRemovedBySetId(setId, false);
+			}
+		};
+		const auto markAsRemovedCallback = [=](uint64 setId) {
+			if (_installed.widget()) {
+				_installed.widget()->setRowRemovedBySetId(setId, true);
+			}
+			if (_featured.widget()) {
+				_featured.widget()->setRowRemovedBySetId(setId, true);
+			}
+		};
+		if (const auto installed = _installed.widget()) {
+			installed->setInstallSetCallback(markAsInstalledCallback);
+			installed->setRemoveSetCallback(markAsRemovedCallback);
+		}
+		if (const auto featured = _featured.widget()) {
+			featured->setInstallSetCallback([=](uint64 setId) {
+				markAsInstalledCallback(setId);
+				installCallback(setId);
+			});
+			featured->setRemoveSetCallback(markAsRemovedCallback);
+		}
+		if (const auto archived = _archived.widget()) {
+			archived->setInstallSetCallback(installCallback);
+			archived->setLoadMoreCallback([=] { loadMoreArchived(); });
+		}
+		if (const auto attached = _attached.widget()) {
+			attached->setInstallSetCallback(installCallback);
+			attached->setLoadMoreCallback([=] { showAttachedStickers(); });
+		}
 	}
 
 	if (_megagroupSet) {
@@ -998,12 +1034,12 @@ void StickersBox::rebuildList(Tab *tab) {
 		tab = _tab;
 	}
 
-	if ((tab == &_installed) || (tab == &_masks)) {
+	if ((tab == &_installed) || (tab == &_masks) || (_tab == &_featured)) {
 		_localOrder = tab->widget()->getFullOrder();
 		_localRemoved = tab->widget()->getRemovedSets();
 	}
 	tab->widget()->rebuild(_isMasks);
-	if ((tab == &_installed) || (tab == &_masks)) {
+	if ((tab == &_installed) || (tab == &_masks) || (_tab == &_featured)) {
 		tab->widget()->setFullOrder(_localOrder);
 	}
 	tab->widget()->setRemovedSets(_localRemoved);
@@ -1594,6 +1630,12 @@ void StickersBox::Inner::paintFakeButton(Painter &p, not_null<Row*> row, int ind
 		const auto textWidth = _installedWidth;
 		const auto &text = _installedText;
 		_inactiveButtonBg.paint(p, myrtlrect(rect));
+		if (row->ripple) {
+			row->ripple->paint(p, rect.x(), rect.y(), width());
+			if (row->ripple->empty()) {
+				row->ripple.reset();
+			}
+		}
 		p.setFont(st.font);
 		p.setPen(st.textFg);
 		p.drawTextLeft(rect.x() - (st.width / 2), rect.y() + st.textTop, width(), text, textWidth);
@@ -1673,16 +1715,27 @@ void StickersBox::Inner::setActionDown(int newActionDown) {
 				if (row->removed) {
 					auto rippleSize = QSize(_undoWidth - st::stickersUndoRemove.width, st::stickersUndoRemove.height);
 					auto rippleMask = Ui::RippleAnimation::RoundRectMask(rippleSize, st::roundRadiusLarge);
-					ensureRipple(st::stickersUndoRemove.ripple, std::move(rippleMask), removeButton);
+					ensureRipple(st::stickersUndoRemove.ripple, std::move(rippleMask), removeButton, false);
 				} else {
 					auto rippleSize = st::stickersRemove.rippleAreaSize;
 					auto rippleMask = Ui::RippleAnimation::EllipseMask(QSize(rippleSize, rippleSize));
-					ensureRipple(st::stickersRemove.ripple, std::move(rippleMask), removeButton);
+					ensureRipple(st::stickersRemove.ripple, std::move(rippleMask), removeButton, false);
 				}
-			} else if (!row->isInstalled() || row->isArchived() || row->removed) {
-				auto rippleSize = QSize(_addWidth - st::stickersTrendingAdd.width, st::stickersTrendingAdd.height);
-				auto rippleMask = Ui::RippleAnimation::RoundRectMask(rippleSize, st::roundRadiusLarge);
-				ensureRipple(st::stickersTrendingAdd.ripple, std::move(rippleMask), removeButton);
+			} else {
+				const auto installedSet = row->isInstalled()
+					&& !row->isArchived()
+					&& !row->removed;
+				const auto &st = installedSet
+					? st::stickersTrendingInstalled
+					: st::stickersTrendingAdd;
+				auto rippleMask = Ui::RippleAnimation::RoundRectMask(
+					QSize(_addWidth - st.width, st.height),
+					st::roundRadiusLarge);
+				ensureRipple(
+					st.ripple,
+					std::move(rippleMask),
+					removeButton,
+					installedSet);
 			}
 		}
 		if (row->ripple) {
@@ -1747,9 +1800,14 @@ void StickersBox::Inner::setPressed(SelectedRow pressed) {
 	}
 }
 
-void StickersBox::Inner::ensureRipple(const style::RippleAnimation &st, QImage mask, bool removeButton) {
-	_rows[_actionDown]->ripple = std::make_unique<Ui::RippleAnimation>(st, std::move(mask), [this, index = _actionDown, removeButton] {
-		update(myrtlrect(relativeButtonRect(removeButton, false).translated(0, _itemsTop + index * _rowHeight)));
+void StickersBox::Inner::ensureRipple(
+		const style::RippleAnimation &st,
+		QImage mask,
+		bool removeButton,
+		bool installedSet) {
+	const auto dy = _itemsTop + _actionDown * _rowHeight;
+	_rows[_actionDown]->ripple = std::make_unique<Ui::RippleAnimation>(st, std::move(mask), [=] {
+		update(myrtlrect(relativeButtonRect(removeButton, installedSet).translated(0, dy)));
 	});
 }
 
@@ -1812,7 +1870,7 @@ void StickersBox::Inner::updateSelected() {
 			selected = selectedIndex;
 			local.setY(local.y() - _itemsTop - selectedIndex * _rowHeight);
 			const auto row = _rows[selectedIndex].get();
-			if (!_megagroupSet && (_isInstalledTab || !row->isInstalled() || row->isArchived() || row->removed)) {
+			if (!_megagroupSet && (_isInstalledTab || (_section == Section::Featured) || !row->isInstalled() || row->isArchived() || row->removed)) {
 				auto removeButton = (_isInstalledTab && !row->removed);
 				auto rect = myrtlrect(relativeButtonRect(removeButton, false));
 				actionSel = rect.contains(local) ? selectedIndex : -1;
@@ -1857,7 +1915,7 @@ float64 StickersBox::Inner::aboveShadowOpacity() const {
 
 	auto dx = 0;
 	auto dy = qAbs(_above * _rowHeight + qRound(_rows[_above]->yadd.current()) - _started * _rowHeight);
-	return qMin((dx + dy)  * 2. / _rowHeight, 1.);
+	return qMin((dx + dy) * 2. / _rowHeight, 1.);
 }
 
 void StickersBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
@@ -1868,10 +1926,11 @@ void StickersBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
 	_mouse = e->globalPos();
 	updateSelected();
 	if (_actionDown == _actionSel && _actionSel >= 0) {
-		if (_isInstalledTab) {
-			setRowRemoved(_actionDown, !_rows[_actionDown]->removed);
-		} else if (_installSetCallback) {
-			_installSetCallback(_rows[_actionDown]->set->id);
+		const auto callback = _rows[_actionDown]->removed
+			? _installSetCallback
+			: _removeSetCallback;
+		if (callback) {
+			callback(_rows[_actionDown]->set->id);
 		}
 	} else if (_dragging >= 0) {
 		_rows[_dragging]->yadd.start(0.);
@@ -1921,6 +1980,13 @@ void StickersBox::Inner::saveGroupSet() {
 	}
 }
 
+void StickersBox::Inner::setRowRemovedBySetId(uint64 setId, bool removed) {
+	const auto index = getRowIndex(setId);
+	if (index >= 0) {
+		setRowRemoved(index, removed);
+	}
+}
+
 void StickersBox::Inner::setRowRemoved(int index, bool removed) {
 	auto &row = _rows[index];
 	if (row->removed != removed) {
diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style
index fb85201eb..20b1d6287 100644
--- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style
+++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style
@@ -294,8 +294,11 @@ stickersTrendingAdd: RoundButton(defaultActiveButton) {
 stickersTrendingInstalled: RoundButton(stickersTrendingAdd) {
 	textFg: activeButtonBg;
 	textFgOver: activeButtonBgOver;
-	textBg: activeButtonSecondaryFg;
-	textBgOver: activeButtonSecondaryFgOver;
+	textBg: lightButtonBgOver;
+	textBgOver: lightButtonBgOver;
+	ripple: RippleAnimation(defaultRippleAnimation) {
+		color: activeButtonSecondaryFg;
+	}
 }
 stickersRemove: IconButton(defaultIconButton) {
 	width: 40px;

From aeb593dd771dfc17c9a9ac28e2e55d2389428194 Mon Sep 17 00:00:00 2001
From: 23rd <23rd@vivaldi.net>
Date: Wed, 23 Aug 2023 17:16:37 +0300
Subject: [PATCH 19/29] Slightly refactored StickersBox class.

---
 Telegram/SourceFiles/boxes/stickers_box.cpp | 107 +++++++++-----------
 Telegram/SourceFiles/boxes/stickers_box.h   |   8 +-
 2 files changed, 52 insertions(+), 63 deletions(-)

diff --git a/Telegram/SourceFiles/boxes/stickers_box.cpp b/Telegram/SourceFiles/boxes/stickers_box.cpp
index 355cb649c..b1eef8d91 100644
--- a/Telegram/SourceFiles/boxes/stickers_box.cpp
+++ b/Telegram/SourceFiles/boxes/stickers_box.cpp
@@ -53,6 +53,22 @@ constexpr auto kArchivedLimitFirstRequest = 10;
 constexpr auto kArchivedLimitPerPage = 30;
 constexpr auto kHandleMegagroupSetAddressChangeTimeout = crl::time(1000);
 
+[[nodiscard]] QString FillSetTitle(
+		not_null<StickersSet*> set,
+		int maxNameWidth,
+		int *outTitleWidth) {
+	auto result = set->title;
+	auto titleWidth = st::contactsNameStyle.font->width(result);
+	if (titleWidth > maxNameWidth) {
+		result = st::contactsNameStyle.font->elided(result, maxNameWidth);
+		titleWidth = st::contactsNameStyle.font->width(result);
+	}
+	if (outTitleWidth) {
+		*outTitleWidth = titleWidth;
+	}
+	return result;
+}
+
 } // namespace
 
 class StickersBox::CounterWidget : public Ui::RpWidget {
@@ -98,9 +114,9 @@ public:
 	void updateRows(); // refresh only pack cover stickers
 	bool appendSet(not_null<StickersSet*> set);
 
-	StickersSetsOrder getOrder() const;
-	StickersSetsOrder getFullOrder() const;
-	StickersSetsOrder getRemovedSets() const;
+	StickersSetsOrder order() const;
+	StickersSetsOrder fullOrder() const;
+	StickersSetsOrder removedSets() const;
 
 	void setFullOrder(const StickersSetsOrder &order);
 	void setRemovedSets(const StickersSetsOrder &removed);
@@ -117,8 +133,6 @@ public:
 		_loadMoreCallback = std::move(callback);
 	}
 
-	void setMinHeight(int newWidth, int minHeight);
-
 	int getVisibleTop() const {
 		return _visibleTop;
 	}
@@ -156,13 +170,13 @@ private:
 			int32 pixh);
 		~Row();
 
-		bool isRecentSet() const;
-		bool isMasksSet() const;
-		bool isEmojiSet() const;
-		bool isWebm() const;
-		bool isInstalled() const;
-		bool isUnread() const;
-		bool isArchived() const;
+		[[nodiscard]] bool isRecentSet() const;
+		[[nodiscard]] bool isMasksSet() const;
+		[[nodiscard]] bool isEmojiSet() const;
+		[[nodiscard]] bool isWebm() const;
+		[[nodiscard]] bool isInstalled() const;
+		[[nodiscard]] bool isUnread() const;
+		[[nodiscard]] bool isArchived() const;
 
 		const not_null<StickersSet*> set;
 		DocumentData *sticker = nullptr;
@@ -246,10 +260,6 @@ private:
 	void rebuildAppendSet(not_null<StickersSet*> set);
 	void fillSetCover(not_null<StickersSet*> set, DocumentData **outSticker, int *outWidth, int *outHeight) const;
 	int fillSetCount(not_null<StickersSet*> set) const;
-	[[nodiscard]] QString fillSetTitle(
-		not_null<StickersSet*> set,
-		int maxNameWidth,
-		int *outTitleWidth) const;
 	[[nodiscard]] Data::StickersSetFlags fillSetFlags(
 		not_null<StickersSet*> set) const;
 	void rebuildMegagroupSet();
@@ -670,7 +680,7 @@ void StickersBox::prepare() {
 	} else { // _section == Section::Featured
 		_tab = &_featured;
 	}
-	setInnerWidget(_tab->takeWidget(), getTopSkip());
+	setInnerWidget(_tab->takeWidget(), topSkip());
 	setDimensions(st::boxWideWidth, st::boxMaxListHeight);
 
 	session().data().stickers().updated(_isEmoji
@@ -793,7 +803,7 @@ void StickersBox::paintEvent(QPaintEvent *e) {
 	Painter p(this);
 
 	if (_slideAnimation) {
-		_slideAnimation->paintFrame(p, 0, getTopSkip(), width());
+		_slideAnimation->paintFrame(p, 0, topSkip(), width());
 		if (!_slideAnimation->animating()) {
 			_slideAnimation.reset();
 			setInnerVisible(true);
@@ -810,7 +820,7 @@ void StickersBox::updateTabsGeometry() {
 	_tabs->resizeToWidth(_tabIndices.size() * width() / maxTabs);
 	_unreadBadge->setVisible(_tabIndices.contains(Section::Featured));
 
-	setInnerTopSkip(getTopSkip());
+	setInnerTopSkip(topSkip());
 
 	auto featuredLeft = width() / maxTabs;
 	auto featuredRight = 2 * width() / maxTabs;
@@ -826,7 +836,7 @@ void StickersBox::updateTabsGeometry() {
 	_tabs->moveToLeft(0, 0);
 }
 
-int StickersBox::getTopSkip() const {
+int StickersBox::topSkip() const {
 	return _tabs ? (_tabs->height() - st::lineWidth) : 0;
 }
 
@@ -855,8 +865,8 @@ void StickersBox::switchTab() {
 	}
 
 	if (_tab == &_installed) {
-		_localOrder = _tab->widget()->getFullOrder();
-		_localRemoved = _tab->widget()->getRemovedSets();
+		_localOrder = _tab->widget()->fullOrder();
+		_localRemoved = _tab->widget()->removedSets();
 	}
 	auto wasCache = grabContentCache();
 	auto wasIndex = _tab->index();
@@ -867,12 +877,12 @@ void StickersBox::switchTab() {
 	_tab->returnWidget(std::move(widget));
 	_tab = newTab;
 	_section = newSection;
-	setInnerWidget(_tab->takeWidget(), getTopSkip());
+	setInnerWidget(_tab->takeWidget(), topSkip());
 	_tabs->raise();
 	_unreadBadge->raise();
 	_tab->widget()->show();
 	rebuildList();
-	scrollToY(_tab->getScrollTop());
+	scrollToY(_tab->scrollTop());
 	setInnerVisible(true);
 	auto nowCache = grabContentCache();
 	auto nowIndex = _tab->index();
@@ -937,7 +947,7 @@ void StickersBox::installSet(uint64 setId) {
 }
 
 void StickersBox::installDone(
-		const MTPmessages_StickerSetInstallResult &result) {
+		const MTPmessages_StickerSetInstallResult &result) const {
 	if (result.type() == mtpc_messages_stickerSetInstallResultArchive) {
 		session().data().stickers().applyArchivedResult(
 			result.c_messages_stickerSetInstallResultArchive());
@@ -1035,8 +1045,8 @@ void StickersBox::rebuildList(Tab *tab) {
 	}
 
 	if ((tab == &_installed) || (tab == &_masks) || (_tab == &_featured)) {
-		_localOrder = tab->widget()->getFullOrder();
-		_localRemoved = tab->widget()->getRemovedSets();
+		_localOrder = tab->widget()->fullOrder();
+		_localRemoved = tab->widget()->removedSets();
 	}
 	tab->widget()->rebuild(_isMasks);
 	if ((tab == &_installed) || (tab == &_masks) || (_tab == &_featured)) {
@@ -1066,14 +1076,14 @@ void StickersBox::saveChanges() {
 	}
 	if (installed) {
 		session().api().saveStickerSets(
-			installed->getOrder(),
-			installed->getRemovedSets(),
+			installed->order(),
+			installed->removedSets(),
 			Data::StickersType::Stickers);
 	}
 	if (masks) {
 		session().api().saveStickerSets(
-			masks->getOrder(),
-			masks->getRemovedSets(),
+			masks->order(),
+			masks->removedSets(),
 			Data::StickersType::Masks);
 	}
 }
@@ -1092,7 +1102,7 @@ const Data::StickersSetsOrder &StickersBox::archivedSetsOrder() const {
 		: session().data().stickers().archivedMaskSetsOrder();
 }
 
-Data::StickersSetsOrder &StickersBox::archivedSetsOrderRef() {
+Data::StickersSetsOrder &StickersBox::archivedSetsOrderRef() const {
 	return !_isMasks
 		? session().data().stickers().archivedSetsOrderRef()
 		: session().data().stickers().archivedMaskSetsOrderRef();
@@ -2180,7 +2190,7 @@ void StickersBox::Inner::rebuildMegagroupSet() {
 	auto removed = false;
 	auto maxNameWidth = countMaxNameWidth(!_isInstalledTab);
 	auto titleWidth = 0;
-	auto title = fillSetTitle(set, maxNameWidth, &titleWidth);
+	auto title = FillSetTitle(set, maxNameWidth, &titleWidth);
 	if (!_megagroupSelectedSet
 		|| _megagroupSelectedSet->set->id != set->id) {
 		_megagroupSetField->setText(set->shortName);
@@ -2283,11 +2293,6 @@ void StickersBox::Inner::setMegagroupSelectedSet(
 	updateSelected();
 }
 
-void StickersBox::Inner::setMinHeight(int newWidth, int minHeight) {
-	_minHeight = minHeight;
-	updateSize(newWidth);
-}
-
 void StickersBox::Inner::updateSize(int newWidth) {
 	auto naturalHeight = _itemsTop + int(_rows.size()) * _rowHeight + st::membersMarginBottom;
 	resize(newWidth ? newWidth : width(), qMax(_minHeight, naturalHeight));
@@ -2335,7 +2340,7 @@ void StickersBox::Inner::updateRows() {
 			&& row->isInstalled()
 			&& !row->isArchived()
 			&& !row->removed);
-		row->title = fillSetTitle(
+		row->title = FillSetTitle(
 			set,
 			installedSet ? maxNameWidthInstalled : maxNameWidth,
 			&row->titleWidth);
@@ -2397,7 +2402,7 @@ void StickersBox::Inner::rebuildAppendSet(not_null<StickersSet*> set) {
 		&& !(flagsOverride & SetFlag::Archived)
 		&& !removed);
 	int titleWidth = 0;
-	QString title = fillSetTitle(set, maxNameWidth, &titleWidth);
+	QString title = FillSetTitle(set, maxNameWidth, &titleWidth);
 	int count = fillSetCount(set);
 
 	const auto existing = [&]{
@@ -2524,22 +2529,6 @@ int StickersBox::Inner::fillSetCount(not_null<StickersSet*> set) const {
 	return result + added;
 }
 
-QString StickersBox::Inner::fillSetTitle(
-		not_null<StickersSet*> set,
-		int maxNameWidth,
-		int *outTitleWidth) const {
-	auto result = set->title;
-	int titleWidth = st::contactsNameStyle.font->width(result);
-	if (titleWidth > maxNameWidth) {
-		result = st::contactsNameStyle.font->elided(result, maxNameWidth);
-		titleWidth = st::contactsNameStyle.font->width(result);
-	}
-	if (outTitleWidth) {
-		*outTitleWidth = titleWidth;
-	}
-	return result;
-}
-
 Data::StickersSetFlags StickersBox::Inner::fillSetFlags(
 		not_null<StickersSet*> set) const {
 	const auto result = set->flags;
@@ -2560,19 +2549,19 @@ StickersSetsOrder StickersBox::Inner::collectSets(Check check) const {
 	return result;
 }
 
-StickersSetsOrder StickersBox::Inner::getOrder() const {
+StickersSetsOrder StickersBox::Inner::order() const {
 	return collectSets([](Row *row) {
 		return !row->isArchived() && !row->removed && !row->isRecentSet();
 	});
 }
 
-StickersSetsOrder StickersBox::Inner::getFullOrder() const {
+StickersSetsOrder StickersBox::Inner::fullOrder() const {
 	return collectSets([](Row *row) {
 		return !row->isRecentSet();
 	});
 }
 
-StickersSetsOrder StickersBox::Inner::getRemovedSets() const {
+StickersSetsOrder StickersBox::Inner::removedSets() const {
 	return collectSets([](Row *row) {
 		return row->removed;
 	});
diff --git a/Telegram/SourceFiles/boxes/stickers_box.h b/Telegram/SourceFiles/boxes/stickers_box.h
index f0e54374a..e374ba0b4 100644
--- a/Telegram/SourceFiles/boxes/stickers_box.h
+++ b/Telegram/SourceFiles/boxes/stickers_box.h
@@ -104,7 +104,7 @@ private:
 		[[nodiscard]] int index() const;
 
 		void saveScrollTop();
-		int getScrollTop() const {
+		int scrollTop() const {
 			return _scrollTop;
 		}
 
@@ -122,12 +122,12 @@ private:
 	void updateTabsGeometry();
 	void switchTab();
 	void installSet(uint64 setId);
-	int getTopSkip() const;
+	int topSkip() const;
 	void saveChanges();
 
 	QPixmap grabContentCache();
 
-	void installDone(const MTPmessages_StickerSetInstallResult &result);
+	void installDone(const MTPmessages_StickerSetInstallResult &result) const;
 	void installFail(const MTP::Error &error, uint64 setId);
 
 	void preloadArchivedSets();
@@ -139,7 +139,7 @@ private:
 	void showAttachedStickers();
 
 	const Data::StickersSetsOrder &archivedSetsOrder() const;
-	Data::StickersSetsOrder &archivedSetsOrderRef();
+	Data::StickersSetsOrder &archivedSetsOrderRef() const;
 
 	std::array<Inner*, 5> widgets() const;
 

From 4b503ad7edd08a8118ab849992a2aa09f6a1e857 Mon Sep 17 00:00:00 2001
From: 23rd <23rd@vivaldi.net>
Date: Mon, 28 Aug 2023 21:24:09 +0300
Subject: [PATCH 20/29] Slightly refactored include directives in some source
 files.

---
 Telegram/SourceFiles/boxes/send_files_box.cpp | 21 +++++++------------
 Telegram/SourceFiles/boxes/send_files_box.h   |  1 -
 Telegram/SourceFiles/boxes/share_box.cpp      |  3 +--
 Telegram/SourceFiles/boxes/share_box.h        |  2 +-
 .../history/history_inner_widget.cpp          |  8 -------
 .../SourceFiles/history/history_widget.cpp    |  9 --------
 .../media/view/media_view_overlay_widget.cpp  |  2 --
 .../window/window_session_controller.cpp      |  5 -----
 .../window/window_session_controller.h        |  2 --
 9 files changed, 9 insertions(+), 44 deletions(-)

diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp
index c639460ba..dcab2b97c 100644
--- a/Telegram/SourceFiles/boxes/send_files_box.cpp
+++ b/Telegram/SourceFiles/boxes/send_files_box.cpp
@@ -28,8 +28,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "base/call_delayed.h"
 #include "boxes/premium_limits_box.h"
 #include "boxes/premium_preview_box.h"
-#include "ui/boxes/confirm_box.h"
-#include "ui/effects/animations.h"
 #include "ui/effects/scroll_content_shadow.h"
 #include "ui/widgets/checkbox.h"
 #include "ui/widgets/buttons.h"
@@ -42,9 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/chat/attach/attach_album_preview.h"
 #include "ui/chat/attach/attach_single_file_preview.h"
 #include "ui/chat/attach/attach_single_media_preview.h"
-#include "ui/text/format_values.h"
 #include "ui/grouped_layout.h"
-#include "ui/text/text_options.h"
 #include "ui/toast/toast.h"
 #include "ui/controls/emoji_button.h"
 #include "ui/painter.h"
@@ -54,16 +50,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_premium_limits.h"
 #include "data/stickers/data_stickers.h"
 #include "data/stickers/data_custom_emoji.h"
-#include "media/clip/media_clip_reader.h"
 #include "api/api_common.h"
 #include "window/window_session_controller.h"
 #include "core/application.h"
 #include "core/core_settings.h"
-#include "styles/style_chat.h"
 #include "styles/style_layers.h"
 #include "styles/style_boxes.h"
 #include "styles/style_chat_helpers.h"
-#include "styles/style_info.h"
 #include "styles/style_menu_icons.h"
 
 #include <QtCore/QMimeData>
@@ -74,10 +67,14 @@ constexpr auto kMaxMessageLength = 4096;
 
 using Ui::SendFilesWay;
 
-inline bool CanAddUrls(const QList<QUrl> &urls) {
+[[nodiscard]] inline bool CanAddUrls(const QList<QUrl> &urls) {
 	return !urls.isEmpty() && ranges::all_of(urls, &QUrl::isLocalFile);
 }
 
+[[nodiscard]] bool CanAddFiles(not_null<const QMimeData*> data) {
+	return data->hasImage() || CanAddUrls(Core::ReadMimeUrls(data));
+}
+
 void FileDialogCallback(
 		FileDialog::OpenResult &&result,
 		Fn<bool(const Ui::PreparedList&)> checkResult,
@@ -451,7 +448,7 @@ void SendFilesBox::setupDragArea() {
 	};
 	const auto areas = DragArea::SetupDragAreaToContainer(
 		this,
-		[=](not_null<const QMimeData*> d) { return canAddFiles(d); },
+		CanAddFiles,
 		[=](bool f) { _caption->setAcceptDrops(f); },
 		[=] { updateControlsGeometry(); },
 		std::move(computeState));
@@ -1046,7 +1043,7 @@ void SendFilesBox::setupCaption() {
 			not_null<const QMimeData*> data,
 			Ui::InputField::MimeAction action) {
 		if (action == Ui::InputField::MimeAction::Check) {
-			return canAddFiles(data);
+			return CanAddFiles(data);
 		} else if (action == Ui::InputField::MimeAction::Insert) {
 			return addFiles(data);
 		}
@@ -1142,10 +1139,6 @@ void SendFilesBox::captionResized() {
 	update();
 }
 
-bool SendFilesBox::canAddFiles(not_null<const QMimeData*> data) const {
-	return data->hasImage() || CanAddUrls(Core::ReadMimeUrls(data));
-}
-
 bool SendFilesBox::addFiles(not_null<const QMimeData*> data) {
 	const auto premium = _show->session().premium();
 	auto list = [&] {
diff --git a/Telegram/SourceFiles/boxes/send_files_box.h b/Telegram/SourceFiles/boxes/send_files_box.h
index 6e6c6fa82..2325e911a 100644
--- a/Telegram/SourceFiles/boxes/send_files_box.h
+++ b/Telegram/SourceFiles/boxes/send_files_box.h
@@ -213,7 +213,6 @@ private:
 	void updateControlsGeometry();
 	void updateCaptionPlaceholder();
 
-	bool canAddFiles(not_null<const QMimeData*> data) const;
 	bool addFiles(not_null<const QMimeData*> data);
 	bool addFiles(Ui::PreparedList list);
 	void addFile(Ui::PreparedFile &&file);
diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp
index 7cba7daba..154961016 100644
--- a/Telegram/SourceFiles/boxes/share_box.cpp
+++ b/Telegram/SourceFiles/boxes/share_box.cpp
@@ -18,7 +18,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/toast/toast.h"
 #include "ui/widgets/checkbox.h"
 #include "ui/widgets/multi_select.h"
-#include "ui/widgets/buttons.h"
 #include "ui/widgets/scroll_area.h"
 #include "ui/widgets/input_fields.h"
 #include "ui/widgets/popup_menu.h"
@@ -602,7 +601,7 @@ void ShareBox::submitWhenOnline() {
 	submit(Api::DefaultSendWhenOnlineOptions());
 }
 
-void ShareBox::copyLink() {
+void ShareBox::copyLink() const {
 	if (const auto onstack = _descriptor.copyCallback) {
 		onstack();
 	}
diff --git a/Telegram/SourceFiles/boxes/share_box.h b/Telegram/SourceFiles/boxes/share_box.h
index b13638577..e793abb6e 100644
--- a/Telegram/SourceFiles/boxes/share_box.h
+++ b/Telegram/SourceFiles/boxes/share_box.h
@@ -119,7 +119,7 @@ private:
 	void submitSilent();
 	void submitScheduled();
 	void submitWhenOnline();
-	void copyLink();
+	void copyLink() const;
 	bool searchByUsername(bool useCache = false);
 
 	SendMenu::Type sendMenuType() const;
diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp
index 35026b095..0071fd772 100644
--- a/Telegram/SourceFiles/history/history_inner_widget.cpp
+++ b/Telegram/SourceFiles/history/history_inner_widget.cpp
@@ -8,7 +8,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "history/history_inner_widget.h"
 
 #include "core/file_utilities.h"
-#include "core/crash_reports.h"
 #include "core/click_handler_types.h"
 #include "history/history.h"
 #include "history/admin_log/history_admin_log_item.h"
@@ -32,7 +31,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/widgets/menu/menu_add_action_callback_factory.h"
 #include "ui/widgets/menu/menu_multiline_action.h"
 #include "ui/widgets/popup_menu.h"
-#include "ui/image/image.h"
 #include "ui/effects/path_shift_gradient.h"
 #include "ui/effects/message_sending_animation_controller.h"
 #include "ui/effects/reaction_fly_animation.h"
@@ -40,16 +38,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/boxes/report_box.h"
 #include "ui/layers/generic_box.h"
 #include "ui/controls/delete_message_context_action.h"
-#include "ui/controls/who_reacted_context_action.h"
 #include "ui/painter.h"
 #include "ui/ui_utility.h"
-#include "ui/cached_round_corners.h"
 #include "ui/inactive_press.h"
-#include "window/window_adaptive.h"
 #include "window/window_session_controller.h"
 #include "window/window_controller.h"
 #include "window/window_peer_menu.h"
-#include "window/window_controller.h"
 #include "window/notifications_manager.h"
 #include "boxes/about_sponsored_box.h"
 #include "boxes/delete_messages_box.h"
@@ -94,12 +88,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_file_origin.h"
 #include "data/data_histories.h"
 #include "data/data_changes.h"
-#include "data/stickers/data_stickers.h"
 #include "data/data_sponsored_messages.h"
 #include "dialogs/ui/dialogs_video_userpic.h"
 #include "settings/settings_premium.h"
 #include "styles/style_chat.h"
-#include "styles/style_window.h" // st::windowMinWidth
 #include "styles/style_menu_icons.h"
 
 #include <QtGui/QClipboard>
diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp
index e4dad4d47..bd7e554e9 100644
--- a/Telegram/SourceFiles/history/history_widget.cpp
+++ b/Telegram/SourceFiles/history/history_widget.cpp
@@ -12,7 +12,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "api/api_chat_participants.h"
 #include "api/api_report.h"
 #include "api/api_sending.h"
-#include "api/api_text_entities.h"
 #include "api/api_send_progress.h"
 #include "api/api_unread_things.h"
 #include "ui/boxes/confirm_box.h"
@@ -32,12 +31,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/widgets/inner_dropdown.h"
 #include "ui/widgets/dropdown_menu.h"
 #include "ui/widgets/labels.h"
-#include "ui/widgets/shadow.h"
 #include "ui/effects/ripple_animation.h"
 #include "ui/effects/message_sending_animation_controller.h"
 #include "ui/text/text_utilities.h" // Ui::Text::ToUpper
 #include "ui/text/format_values.h"
-#include "ui/chat/forward_options_box.h"
 #include "ui/chat/message_bar.h"
 #include "ui/chat/attach/attach_send_files_way.h"
 #include "ui/chat/choose_send_as.h"
@@ -62,7 +59,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_document.h"
 #include "data/data_photo.h"
 #include "data/data_photo_media.h"
-#include "data/data_media_types.h"
 #include "data/data_channel.h"
 #include "data/data_chat.h"
 #include "data/data_forum.h"
@@ -107,7 +103,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "history/view/history_view_translate_bar.h"
 #include "history/view/media/history_view_media.h"
 #include "profile/profile_block_group_members.h"
-#include "info/info_memento.h"
 #include "core/click_handler_types.h"
 #include "chat_helpers/tabbed_panel.h"
 #include "chat_helpers/tabbed_selector.h"
@@ -138,7 +133,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/chat/continuous_scroll.h"
 #include "ui/widgets/popup_menu.h"
 #include "ui/item_text_options.h"
-#include "ui/unread_badge.h"
 #include "main/main_session.h"
 #include "main/main_session_settings.h"
 #include "main/session/send_as_peers.h"
@@ -152,7 +146,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "inline_bots/bot_attach_web_view.h"
 #include "info/profile/info_profile_values.h" // SharedMediaCountValue.
 #include "chat_helpers/emoji_suggestions_widget.h"
-#include "core/crash_reports.h"
 #include "core/shortcuts.h"
 #include "core/ui_integration.h"
 #include "support/support_common.h"
@@ -160,12 +153,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "support/support_preload.h"
 #include "dialogs/dialogs_key.h"
 #include "calls/calls_instance.h"
-#include "api/api_bot.h"
 #include "styles/style_chat.h"
 #include "styles/style_dialogs.h"
 #include "styles/style_window.h"
 #include "styles/style_boxes.h"
-#include "styles/style_profile.h"
 #include "styles/style_chat_helpers.h"
 #include "styles/style_info.h"
 
diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp
index 957679aba..59ed41e35 100644
--- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp
+++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp
@@ -88,7 +88,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "main/main_session_settings.h"
 #include "layout/layout_document_generic_preview.h"
 #include "platform/platform_overlay_widget.h"
-#include "settings/settings_premium.h"
 #include "storage/file_download.h"
 #include "storage/storage_account.h"
 #include "calls/calls_instance.h"
@@ -104,7 +103,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include <QtWidgets/QApplication>
 #include <QtCore/QBuffer>
 #include <QtGui/QGuiApplication>
-#include <QtGui/QClipboard>
 #include <QtGui/QWindow>
 #include <QtGui/QScreen>
 
diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp
index 486087771..4ec3e2dfb 100644
--- a/Telegram/SourceFiles/window/window_session_controller.cpp
+++ b/Telegram/SourceFiles/window/window_session_controller.cpp
@@ -10,11 +10,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "boxes/add_contact_box.h"
 #include "boxes/peers/add_bot_to_chat_box.h"
 #include "boxes/peers/edit_peer_info_box.h"
-#include "boxes/peer_list_controllers.h"
 #include "boxes/delete_messages_box.h"
 #include "window/window_adaptive.h"
 #include "window/window_controller.h"
-#include "window/main_window.h"
 #include "window/window_filters_menu.h"
 #include "info/info_memento.h"
 #include "info/info_controller.h"
@@ -37,7 +35,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_user.h"
 #include "data/data_document.h"
 #include "data/data_document_media.h"
-#include "data/data_document_resolver.h"
 #include "data/data_changes.h"
 #include "data/data_group_call.h"
 #include "data/data_forum.h"
@@ -54,13 +51,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "core/core_settings.h"
 #include "core/click_handler_types.h"
 #include "base/unixtime.h"
-#include "base/random.h"
 #include "ui/layers/generic_box.h"
 #include "ui/text/text_utilities.h"
 #include "ui/text/format_values.h" // Ui::FormatPhone.
 #include "ui/delayed_activation.h"
 #include "ui/chat/attach/attach_bot_webview.h"
-#include "ui/chat/message_bubble.h"
 #include "ui/chat/chat_style.h"
 #include "ui/chat/chat_theme.h"
 #include "ui/effects/message_sending_animation_controller.h"
diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h
index 9536894ee..7f541cf5e 100644
--- a/Telegram/SourceFiles/window/window_session_controller.h
+++ b/Telegram/SourceFiles/window/window_session_controller.h
@@ -16,7 +16,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_chat_participant_status.h"
 #include "dialogs/dialogs_key.h"
 #include "ui/layers/layer_widget.h"
-#include "ui/layers/show.h"
 #include "settings/settings_type.h"
 #include "window/window_adaptive.h"
 #include "mtproto/sender.h"
@@ -89,7 +88,6 @@ namespace Window {
 using GifPauseReason = ChatHelpers::PauseReason;
 using GifPauseReasons = ChatHelpers::PauseReasons;
 
-class MainWindow;
 class SectionMemento;
 class Controller;
 class FiltersMenu;

From 089432be5e02a09676d63492f652f98d49135763 Mon Sep 17 00:00:00 2001
From: 23rd <23rd@vivaldi.net>
Date: Mon, 28 Aug 2023 20:52:08 +0300
Subject: [PATCH 21/29] Added icon for forwarded messages in dialogs list.

---
 Telegram/Resources/icons/mini_forward.png     | Bin 0 -> 337 bytes
 Telegram/Resources/icons/mini_forward@2x.png  | Bin 0 -> 527 bytes
 Telegram/Resources/icons/mini_forward@3x.png  | Bin 0 -> 772 bytes
 Telegram/SourceFiles/dialogs/dialogs.style    |   5 +++++
 .../dialogs/ui/dialogs_message_view.cpp       |  16 ++++++++++++++--
 .../dialogs/ui/dialogs_message_view.h         |   1 +
 Telegram/SourceFiles/history/history_item.cpp |   3 +++
 .../history/view/history_view_item_preview.h  |   2 ++
 .../window/notifications_manager.cpp          |  18 ++++++++++++++----
 9 files changed, 39 insertions(+), 6 deletions(-)
 create mode 100644 Telegram/Resources/icons/mini_forward.png
 create mode 100644 Telegram/Resources/icons/mini_forward@2x.png
 create mode 100644 Telegram/Resources/icons/mini_forward@3x.png

diff --git a/Telegram/Resources/icons/mini_forward.png b/Telegram/Resources/icons/mini_forward.png
new file mode 100644
index 0000000000000000000000000000000000000000..0b12af15d0dac8b19147b9d028e133f6f8e5c890
GIT binary patch
literal 337
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|jKx9jP7LeL$-D$|Tv8)E(|mmy
zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uuz(rC1}QWNE&K$ec6+)whG>W;
zCrDT+*xSzs0+#P~b$@<*dU|@(rcEv{1qB2u{{8v+|NsB_HkFH(FK_qnpEm8<$H&JT
zy&gVze7s*zPw&Rpi0J70A0HUkiJbfK_4V}4n~itL>FKTe{r&x6QAOjpeKjlBuI*j7
zaLX2vbuXTupI`9p>}>O?Q>I)9W&T<H{ax+<f3?BEle@aOCUEnpE||C2y`PJlJ2{Y7
z<ksG5a~Dk)sTGNDZ*7&4k+~6?+!YlOVPI&O*v0Q8$Y7V*b}Kru<PFF>p00i_>zopr
E0J(*C<NyEw

literal 0
HcmV?d00001

diff --git a/Telegram/Resources/icons/mini_forward@2x.png b/Telegram/Resources/icons/mini_forward@2x.png
new file mode 100644
index 0000000000000000000000000000000000000000..c8194ef007ab6f69d2a1f18118eebe74e6673fb5
GIT binary patch
literal 527
zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1SJ1Ryj={W7>k44ofy`glX(f`xTHpSruq6Z
zXaU(A42<oW3@jieKr98s3=GT*7#Wy>G$TlC0TWzSVF5FO4N|zKE$K5*S*53oV~7WD
za)JcwVnv7V0uLTN5)u$tuyE^av)rox|Mu?PYrFQ1McJDh|NsB5|MNpoPR>u*T0xBA
z?T_#8<2^k&zrJmVX4t~aq2@d5$dMy1ve^}tm1obK$-A?I(NM~7UG(<8$&)A7SW8Gs
zE?%^#PsWnTTyQeumuVX|7+98hczC?IwUzs_i_gT_=J{HhniJF7r%bu>_V#wevxiha
zeR}FGEG(S#mZ9y(*VoqzXSRCFyx=$CE>LT*EPj@ek+EQ<+6mk8cQTHq3asnat=n;@
z!@cI@B-Nap9GA#~KR*g>*b=8^<>#|6GhgHr9v*%?>&Wx-^BXUAXnqCKXU_O6>S@zh
zwQgP8>`y?iBp97Naf0C%*F@LPcdoCGudS{1G>~0Ua(bGs0Qa`MyQ`KiZCxh0b4A71
zSE2^_i!~L_)c*TpdEsGYWo5+FL%IxUf~l#gYqB^v-re18Ze%1hU8#fhK!cE=;K8MB
wN+0jsxg%+u77(&4Fy;S>XBPz)IC3yBczu7pRm%NwGAJ@UUHx3vIVCg!0F>F#Hvj+t

literal 0
HcmV?d00001

diff --git a/Telegram/Resources/icons/mini_forward@3x.png b/Telegram/Resources/icons/mini_forward@3x.png
new file mode 100644
index 0000000000000000000000000000000000000000..19cac4700ebfa851de3026975d9f17e08b7da48b
GIT binary patch
literal 772
zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1SD@H<Xr$#jKx9jP7LeL$-D$|Tv8)E(|mmy
zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(;Zuz(rC1}VI4p2Neyz?A3d;usRa
z`F6U0hhd`3@#$aRKWJ#SSkoO8Q&9Y<sVM5W?a5YOrEu}`MRzPMJXCh>Oqx8!BWP0D
z>BjCN2a%2M*O%W{J8PXbFHLT1`^@IFc|ZT{yWfA`=lth0mxb#;$Ud+>P~PzV!25Rx
zjP-0*U!8UOX^PR!43k4oPo7*Jpi%SR_R`BO4hj#Rv@YEdwDJ$b!Tg;u)4Y~O<lLNn
zhlw$G_uVk9sXA|3bg}}x-jr1@zkKm!iN>Sovx^%P4yPD>t+MStS~UB3#Xkpy6W6k~
z%FD~=ZJ#}nc{W3w$nwjQJZ#4w8-5I$?04?@=KJrDi+N_MO!T;S?_Si}YhSBWkKW(y
zqarjVOoGi;?tIqPUE)5*^UgnaRT5-lI^?7ymcBW1mdCN^UN>jG>B4Pu{2rXYnq_*-
z!|h;#!G#QyIROhKGFDyHN|spk;Pq?owPD3cr&5eM5<50U=p2)K@%5{yAzSRT0*eTy
zl_9InrZvwyvchJrpWXa^Ro*G8lRb~|lpHMEUAi;oO=iM7dl|kLm0wOhEy^^Tee<$I
zs*T+8^A7v1XZpm&#aUc*Fnr_wQh8zh<(DNJBQBhtb#|H4!UtZ-1`-;{42$B{@87>)
zy4P*thx>JnY16c(dTsbtxU}(CfQAdxtg~qw@`BI(T_UR-IKyk{hTM%0wVOWNeOuP(
z@Zjgqcj2f0J4`>VD#MqY!INkx!&l$%>+im}_3Fno{XHB$MC^^zUpndUgS!3f3KjC2
z^94_S7t7cicRR;y62r$ydF4SmVu`-JE=tEA3tr?)nLmI2hQwLtpTAJ$4|H1`n32By
x_S0QG$xE6uWfx259|gug!+pkm%=@=6*E2M!wx{e$eFjQ^44$rjF6*2UngE@2PssoP

literal 0
HcmV?d00001

diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style
index 622016bb8..c2d287b31 100644
--- a/Telegram/SourceFiles/dialogs/dialogs.style
+++ b/Telegram/SourceFiles/dialogs/dialogs.style
@@ -436,6 +436,11 @@ dialogsMiniPreviewSkip: 2px;
 dialogsMiniPreviewRight: 3px;
 dialogsMiniPlay: icon{{ "dialogs/dialogs_mini_play", videoPlayIconFg }};
 
+dialogsMiniForwardIcon: icon {{ "mini_forward", dialogsTextFg, point(0px, 1px) }};
+dialogsMiniForwardIconOver: icon {{ "mini_forward", dialogsTextFgOver, point(0px, 1px) }};
+dialogsMiniForwardIconActive: icon {{ "mini_forward", dialogsTextFgActive, point(0px, 1px) }};
+dialogsMiniForwardIconSkip: 2px;
+
 dialogsUnreadMention: icon{{ "dialogs/dialogs_mention", dialogsUnreadFg }};
 dialogsUnreadMentionOver: icon{{ "dialogs/dialogs_mention", dialogsUnreadFgOver }};
 dialogsUnreadMentionActive: icon{{ "dialogs/dialogs_mention", dialogsUnreadFgActive }};
diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp
index b1f59fb43..af4d0d3c3 100644
--- a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp
+++ b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp
@@ -16,7 +16,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/effects/spoiler_mess.h"
 #include "ui/text/text_options.h"
 #include "ui/text/text_utilities.h"
-#include "ui/image/image.h"
 #include "ui/painter.h"
 #include "ui/power_saving.h"
 #include "core/ui_integration.h"
@@ -159,6 +158,7 @@ void MessageView::prepare(
 	options.ignoreTopic = true;
 	options.spoilerLoginCode = true;
 	auto preview = item->toPreview(options);
+	_displayMiniForwardIcon = preview.forwardedMessage;
 	const auto hasImages = !preview.images.empty();
 	const auto history = item->history();
 	const auto context = Core::MarkedTextContext{
@@ -169,7 +169,7 @@ void MessageView::prepare(
 	const auto senderTill = (preview.arrowInTextPosition > 0)
 		? preview.arrowInTextPosition
 		: preview.imagesInTextPosition;
-	if (hasImages && senderTill > 0) {
+	if ((hasImages || _displayMiniForwardIcon) && senderTill > 0) {
 		auto sender = Text::Mid(preview.text, 0, senderTill);
 		TextUtilities::Trim(sender);
 		_senderCache.setMarkedText(
@@ -314,6 +314,18 @@ void MessageView::paint(
 			rect.setLeft(rect.x() + skip);
 		}
 	}
+
+	if (_displayMiniForwardIcon) {
+		const auto &icon = context.active
+			? st::dialogsMiniForwardIconActive
+			: context.selected
+			? st::dialogsMiniForwardIconOver
+			: st::dialogsMiniForwardIcon;
+		icon.paint(p, rect.topLeft(), rect.width());
+		rect.setLeft(rect.x()
+			+ icon.width()
+			+ st::dialogsMiniForwardIconSkip);
+	}
 	for (const auto &image : _imagesCache) {
 		if (rect.width() < st::dialogsMiniPreview) {
 			break;
diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h
index 87b69b435..a23dbea49 100644
--- a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h
+++ b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h
@@ -92,6 +92,7 @@ private:
 	mutable std::vector<ItemPreviewImage> _imagesCache;
 	mutable std::unique_ptr<SpoilerAnimation> _spoiler;
 	mutable std::unique_ptr<LoadingContext> _loadingContext;
+	mutable bool _displayMiniForwardIcon = false;
 
 };
 
diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp
index 27727f94d..3357ed3ba 100644
--- a/Telegram/SourceFiles/history/history_item.cpp
+++ b/Telegram/SourceFiles/history/history_item.cpp
@@ -2958,6 +2958,9 @@ ItemPreview HistoryItem::toPreview(ToPreviewOptions options) const {
 			? tr::lng_from_you(tr::now)
 			: sender->shortName();
 	};
+	if (!options.ignoreForwardedMessage) {
+		result.forwardedMessage = Get<HistoryMessageForwarded>() != nullptr;
+	}
 	const auto fromForwarded = [&]() -> std::optional<QString> {
 		if (const auto forwarded = Get<HistoryMessageForwarded>()) {
 			return forwarded->originalSender
diff --git a/Telegram/SourceFiles/history/view/history_view_item_preview.h b/Telegram/SourceFiles/history/view/history_view_item_preview.h
index 7ac0a2b18..cb15afb6e 100644
--- a/Telegram/SourceFiles/history/view/history_view_item_preview.h
+++ b/Telegram/SourceFiles/history/view/history_view_item_preview.h
@@ -28,6 +28,7 @@ struct ItemPreview {
 	int arrowInTextPosition = -1;
 	int imagesInTextPosition = 0;
 	std::any loadingContext;
+	bool forwardedMessage = false;
 };
 
 struct ToPreviewOptions {
@@ -37,6 +38,7 @@ struct ToPreviewOptions {
 	bool generateImages = true;
 	bool ignoreGroup = false;
 	bool ignoreTopic = true;
+	bool ignoreForwardedMessage = false;
 	bool spoilerLoginCode = false;
 	bool translated = false;
 };
diff --git a/Telegram/SourceFiles/window/notifications_manager.cpp b/Telegram/SourceFiles/window/notifications_manager.cpp
index 5a52f4480..c03080c7e 100644
--- a/Telegram/SourceFiles/window/notifications_manager.cpp
+++ b/Telegram/SourceFiles/window/notifications_manager.cpp
@@ -66,7 +66,15 @@ constexpr auto kSystemAlertDuration = crl::time(0);
 	return result;
 }
 
-QString TextWithPermanentSpoiler(const TextWithEntities &textWithEntities) {
+[[nodiscard]] QString TextWithForwardedChar(
+		const QString &text,
+		bool forwarded) {
+	static const auto result = QString::fromUtf8("\xE2\x9E\xA1\xEF\xB8\x8F");
+	return forwarded ? result + text : text;
+}
+
+[[nodiscard]] QString TextWithPermanentSpoiler(
+		const TextWithEntities &textWithEntities) {
 	auto text = textWithEntities.text;
 	for (const auto &e : textWithEntities.entities) {
 		if (e.type() == EntityType::Spoiler) {
@@ -1175,9 +1183,11 @@ void NativeManager::doShowNotification(NotificationFields &&fields) {
 		? tr::lng_forward_messages(tr::now, lt_count, fields.forwardedCount)
 		: item->groupId()
 		? tr::lng_in_dlg_album(tr::now)
-		: TextWithPermanentSpoiler(item->notificationText({
-			.spoilerLoginCode = options.spoilerLoginCode,
-		}));
+		: TextWithForwardedChar(
+			TextWithPermanentSpoiler(item->notificationText({
+				.spoilerLoginCode = options.spoilerLoginCode,
+			})),
+			(fields.forwardedCount == 1));
 
 	// #TODO optimize
 	auto userpicView = item->history()->peer->createUserpicView();

From 9d4b8bb9b0b63e76c4e31917ca69b9a316fcacdf Mon Sep 17 00:00:00 2001
From: 23rd <23rd@vivaldi.net>
Date: Wed, 30 Aug 2023 05:25:34 +0300
Subject: [PATCH 22/29] Introduced new struct to dialogs style for icons.

---
 Telegram/SourceFiles/boxes/peer_list_box.cpp  |   4 +-
 Telegram/SourceFiles/dialogs/dialogs.style    | 110 ++++++++-----
 .../dialogs/dialogs_inner_widget.cpp          |  10 +-
 .../dialogs/dialogs_three_state_icon.h        |  21 +++
 .../SourceFiles/dialogs/ui/dialogs_layout.cpp | 150 ++++++++----------
 .../dialogs/ui/dialogs_message_view.cpp       |  10 +-
 .../history/view/history_view_message.cpp     |  13 +-
 .../view/history_view_top_bar_widget.cpp      |   2 +-
 .../window/themes/window_theme_preview.cpp    |  26 ++-
 Telegram/cmake/td_ui.cmake                    |   1 +
 10 files changed, 203 insertions(+), 144 deletions(-)
 create mode 100644 Telegram/SourceFiles/dialogs/dialogs_three_state_icon.h

diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp
index 407eeb185..28286f377 100644
--- a/Telegram/SourceFiles/boxes/peer_list_box.cpp
+++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp
@@ -763,8 +763,8 @@ int PeerListRow::paintNameIconGetWidth(
 				? st::dialogsVerifiedIconOver
 				: st::dialogsVerifiedIcon),
 			.premium = &(selected
-				? st::dialogsPremiumIconOver
-				: st::dialogsPremiumIcon),
+				? st::dialogsPremiumIcon.over
+				: st::dialogsPremiumIcon.icon),
 			.scam = &(selected ? st::dialogsScamFgOver : st::dialogsScamFg),
 			.premiumFg = &(selected
 				? st::dialogsVerifiedIconBgOver
diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style
index c2d287b31..6c5b4e68a 100644
--- a/Telegram/SourceFiles/dialogs/dialogs.style
+++ b/Telegram/SourceFiles/dialogs/dialogs.style
@@ -23,6 +23,12 @@ DialogRow {
 	unreadMarkDiameter: pixels;
 }
 
+ThreeStateIcon {
+	icon: icon;
+	over: icon;
+	active: icon;
+}
+
 ForumTopicIcon {
 	size: pixels;
 	font: font;
@@ -316,38 +322,56 @@ dialogSearchFrom: IconButton(dialogCalendar) {
 }
 
 dialogsChatTypeSkip: 3px;
-dialogsChatIcon: icon {{ "dialogs/dialogs_chat", dialogsChatIconFg, point(1px, 4px) }};
-dialogsChatIconOver: icon {{ "dialogs/dialogs_chat", dialogsChatIconFgOver, point(1px, 4px) }};
-dialogsChatIconActive: icon {{ "dialogs/dialogs_chat", dialogsChatIconFgActive, point(1px, 4px) }};
-dialogsChannelIcon: icon {{ "dialogs/dialogs_channel", dialogsChatIconFg, point(3px, 4px) }};
-dialogsChannelIconOver: icon {{ "dialogs/dialogs_channel", dialogsChatIconFgOver, point(3px, 4px) }};
-dialogsChannelIconActive: icon {{ "dialogs/dialogs_channel", dialogsChatIconFgActive, point(3px, 4px) }};
-dialogsBotIcon: icon {{ "dialogs/dialogs_bot", dialogsChatIconFg, point(1px, 3px) }};
-dialogsBotIconOver: icon {{ "dialogs/dialogs_bot", dialogsChatIconFgOver, point(1px, 3px) }};
-dialogsBotIconActive: icon {{ "dialogs/dialogs_bot", dialogsChatIconFgActive, point(1px, 3px) }};
-dialogsForumIcon: icon {{ "dialogs/dialogs_forum", dialogsChatIconFg, point(1px, 4px) }};
-dialogsForumIconOver: icon {{ "dialogs/dialogs_forum", dialogsChatIconFgOver, point(1px, 4px) }};
-dialogsForumIconActive: icon {{ "dialogs/dialogs_forum", dialogsChatIconFgActive, point(1px, 4px) }};
+dialogsChatIcon: ThreeStateIcon {
+	icon: icon {{ "dialogs/dialogs_chat", dialogsChatIconFg, point(1px, 4px) }};
+	over: icon {{ "dialogs/dialogs_chat", dialogsChatIconFgOver, point(1px, 4px) }};
+	active: icon {{ "dialogs/dialogs_chat", dialogsChatIconFgActive, point(1px, 4px) }};
+}
+dialogsChannelIcon: ThreeStateIcon {
+	icon: icon {{ "dialogs/dialogs_channel", dialogsChatIconFg, point(3px, 4px) }};
+	over: icon {{ "dialogs/dialogs_channel", dialogsChatIconFgOver, point(3px, 4px) }};
+	active: icon {{ "dialogs/dialogs_channel", dialogsChatIconFgActive, point(3px, 4px) }};
+}
+dialogsBotIcon: ThreeStateIcon {
+	icon: icon {{ "dialogs/dialogs_bot", dialogsChatIconFg, point(1px, 3px) }};
+	over: icon {{ "dialogs/dialogs_bot", dialogsChatIconFgOver, point(1px, 3px) }};
+	active: icon {{ "dialogs/dialogs_bot", dialogsChatIconFgActive, point(1px, 3px) }};
+}
+dialogsForumIcon: ThreeStateIcon {
+	icon: icon {{ "dialogs/dialogs_forum", dialogsChatIconFg, point(1px, 4px) }};
+	over: icon {{ "dialogs/dialogs_forum", dialogsChatIconFgOver, point(1px, 4px) }};
+	active: icon {{ "dialogs/dialogs_forum", dialogsChatIconFgActive, point(1px, 4px) }};
+}
 dialogsArchiveUserpic: icon {{ "archive_userpic", historyPeerUserpicFg }};
 dialogsRepliesUserpic: icon {{ "replies_userpic", historyPeerUserpicFg }};
 dialogsInaccessibleUserpic: icon {{ "dialogs/inaccessible_userpic", historyPeerUserpicFg }};
 
 dialogsSendStateSkip: 20px;
-dialogsSendingIcon: icon {{ "dialogs/dialogs_sending", dialogsSendingIconFg, point(8px, 4px) }};
-dialogsSendingIconOver: icon {{ "dialogs/dialogs_sending", dialogsSendingIconFgOver, point(8px, 4px) }};
-dialogsSendingIconActive: icon {{ "dialogs/dialogs_sending", dialogsSendingIconFgActive, point(8px, 4px) }};
-dialogsSentIcon: icon {{ "dialogs/dialogs_sent", dialogsSentIconFg, point(10px, 4px) }};
-dialogsSentIconOver: icon {{ "dialogs/dialogs_sent", dialogsSentIconFgOver, point(10px, 4px) }};
-dialogsSentIconActive: icon {{ "dialogs/dialogs_sent", dialogsSentIconFgActive, point(10px, 4px) }};
-dialogsReceivedIcon: icon {{ "dialogs/dialogs_received", dialogsSentIconFg, point(5px, 4px) }};
-dialogsReceivedIconOver: icon {{ "dialogs/dialogs_received", dialogsSentIconFgOver, point(5px, 4px) }};
-dialogsReceivedIconActive: icon {{ "dialogs/dialogs_received", dialogsSentIconFgActive, point(5px, 4px) }};
-dialogsPinnedIcon: icon {{ "dialogs/dialogs_pinned", dialogsUnreadBgMuted }};
-dialogsPinnedIconOver: icon {{ "dialogs/dialogs_pinned", dialogsUnreadBgMutedOver }};
-dialogsPinnedIconActive: icon {{ "dialogs/dialogs_pinned", dialogsUnreadBgMutedActive }};
-dialogsLockIcon: icon {{ "emoji/premium_lock", dialogsUnreadBgMuted, point(4px, 0px) }};
-dialogsLockIconOver: icon {{ "emoji/premium_lock", dialogsUnreadBgMutedOver, point(4px, 0px) }};
-dialogsLockIconActive: icon {{ "emoji/premium_lock", dialogsUnreadBgMutedActive, point(4px, 0px) }};
+dialogsSendingIcon: ThreeStateIcon {
+	icon: icon {{ "dialogs/dialogs_sending", dialogsSendingIconFg, point(8px, 4px) }};
+	over: icon {{ "dialogs/dialogs_sending", dialogsSendingIconFgOver, point(8px, 4px) }};
+	active: icon {{ "dialogs/dialogs_sending", dialogsSendingIconFgActive, point(8px, 4px) }};
+}
+dialogsSentIcon: ThreeStateIcon {
+	icon: icon {{ "dialogs/dialogs_sent", dialogsSentIconFg, point(10px, 4px) }};
+	over: icon {{ "dialogs/dialogs_sent", dialogsSentIconFgOver, point(10px, 4px) }};
+	active: icon {{ "dialogs/dialogs_sent", dialogsSentIconFgActive, point(10px, 4px) }};
+}
+dialogsReceivedIcon: ThreeStateIcon {
+	icon: icon {{ "dialogs/dialogs_received", dialogsSentIconFg, point(5px, 4px) }};
+	over: icon {{ "dialogs/dialogs_received", dialogsSentIconFgOver, point(5px, 4px) }};
+	active: icon {{ "dialogs/dialogs_received", dialogsSentIconFgActive, point(5px, 4px) }};
+}
+dialogsPinnedIcon: ThreeStateIcon {
+	icon: icon {{ "dialogs/dialogs_pinned", dialogsUnreadBgMuted }};
+	over: icon {{ "dialogs/dialogs_pinned", dialogsUnreadBgMutedOver }};
+	active: icon {{ "dialogs/dialogs_pinned", dialogsUnreadBgMutedActive }};
+}
+dialogsLockIcon: ThreeStateIcon {
+	icon: icon {{ "emoji/premium_lock", dialogsUnreadBgMuted, point(4px, 0px) }};
+	over: icon {{ "emoji/premium_lock", dialogsUnreadBgMutedOver, point(4px, 0px) }};
+	active: icon {{ "emoji/premium_lock", dialogsUnreadBgMutedActive, point(4px, 0px) }};
+}
 
 dialogsVerifiedIcon: icon {
 	{ "dialogs/dialogs_verified_star", dialogsVerifiedIconBg },
@@ -361,9 +385,11 @@ dialogsVerifiedIconActive: icon {
 	{ "dialogs/dialogs_verified_star", dialogsVerifiedIconBgActive },
 	{ "dialogs/dialogs_verified_check", dialogsVerifiedIconFgActive },
 };
-dialogsPremiumIcon: icon {{ "dialogs/dialogs_premium", dialogsVerifiedIconBg }};
-dialogsPremiumIconOver: icon {{ "dialogs/dialogs_premium", dialogsVerifiedIconBgOver }};
-dialogsPremiumIconActive: icon {{ "dialogs/dialogs_premium", dialogsVerifiedIconBgActive }};
+dialogsPremiumIcon: ThreeStateIcon {
+	icon: icon {{ "dialogs/dialogs_premium", dialogsVerifiedIconBg }};
+	over: icon {{ "dialogs/dialogs_premium", dialogsVerifiedIconBgOver }};
+	active: icon {{ "dialogs/dialogs_premium", dialogsVerifiedIconBgActive }};
+}
 
 historySendingIcon: icon {{ "dialogs/dialogs_sending", historySendingOutIconFg, point(5px, 5px) }};
 historySendingInvertedIcon: icon {{ "dialogs/dialogs_sending", historySendingInvertedIconFg, point(5px, 5px) }};
@@ -436,17 +462,23 @@ dialogsMiniPreviewSkip: 2px;
 dialogsMiniPreviewRight: 3px;
 dialogsMiniPlay: icon{{ "dialogs/dialogs_mini_play", videoPlayIconFg }};
 
-dialogsMiniForwardIcon: icon {{ "mini_forward", dialogsTextFg, point(0px, 1px) }};
-dialogsMiniForwardIconOver: icon {{ "mini_forward", dialogsTextFgOver, point(0px, 1px) }};
-dialogsMiniForwardIconActive: icon {{ "mini_forward", dialogsTextFgActive, point(0px, 1px) }};
+dialogsMiniForwardIcon: ThreeStateIcon {
+	icon: icon {{ "mini_forward", dialogsTextFg, point(0px, 1px) }};
+	over: icon {{ "mini_forward", dialogsTextFgOver, point(0px, 1px) }};
+	active: icon {{ "mini_forward", dialogsTextFgActive, point(0px, 1px) }};
+}
 dialogsMiniForwardIconSkip: 2px;
 
-dialogsUnreadMention: icon{{ "dialogs/dialogs_mention", dialogsUnreadFg }};
-dialogsUnreadMentionOver: icon{{ "dialogs/dialogs_mention", dialogsUnreadFgOver }};
-dialogsUnreadMentionActive: icon{{ "dialogs/dialogs_mention", dialogsUnreadFgActive }};
-dialogsUnreadReaction: icon{{ "dialogs/dialogs_reaction", dialogsUnreadFg }};
-dialogsUnreadReactionOver: icon{{ "dialogs/dialogs_reaction", dialogsUnreadFgOver }};
-dialogsUnreadReactionActive: icon{{ "dialogs/dialogs_reaction", dialogsUnreadFgActive }};
+dialogsUnreadMention: ThreeStateIcon {
+	icon: icon{{ "dialogs/dialogs_mention", dialogsUnreadFg }};
+	over: icon{{ "dialogs/dialogs_mention", dialogsUnreadFgOver }};
+	active: icon{{ "dialogs/dialogs_mention", dialogsUnreadFgActive }};
+}
+dialogsUnreadReaction: ThreeStateIcon {
+	icon: icon{{ "dialogs/dialogs_reaction", dialogsUnreadFg }};
+	over: icon{{ "dialogs/dialogs_reaction", dialogsUnreadFgOver }};
+	active: icon{{ "dialogs/dialogs_reaction", dialogsUnreadFgActive }};
+}
 
 downloadBarHeight: 46px;
 downloadArrow: icon{{ "fast_to_original", menuIconFg }};
diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
index 697862166..eadcc89ee 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "dialogs/dialogs_inner_widget.h"
 
+#include "dialogs/dialogs_three_state_icon.h"
 #include "dialogs/ui/dialogs_layout.h"
 #include "dialogs/ui/dialogs_stories_content.h"
 #include "dialogs/ui/dialogs_stories_list.h"
@@ -1010,11 +1011,10 @@ void InnerWidget::paintPeerSearchResult(
 				: context.selected
 				? &st::dialogsVerifiedIconOver
 				: &st::dialogsVerifiedIcon),
-			.premium = (context.active
-				? &st::dialogsPremiumIconActive
-				: context.selected
-				? &st::dialogsPremiumIconOver
-				: &st::dialogsPremiumIcon),
+			.premium = &ThreeStateIcon(
+				st::dialogsPremiumIcon,
+				context.active,
+				context.selected),
 			.scam = (context.active
 				? &st::dialogsScamFgActive
 				: context.selected
diff --git a/Telegram/SourceFiles/dialogs/dialogs_three_state_icon.h b/Telegram/SourceFiles/dialogs/dialogs_three_state_icon.h
new file mode 100644
index 000000000..e150fc669
--- /dev/null
+++ b/Telegram/SourceFiles/dialogs/dialogs_three_state_icon.h
@@ -0,0 +1,21 @@
+/*
+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 "styles/style_dialogs.h"
+
+namespace Dialogs {
+
+[[nodiscard]] inline const style::icon &ThreeStateIcon(
+		const style::ThreeStateIcon &icons,
+		bool active,
+		bool over) {
+	return active ? icons.active : over ? icons.over : icons.icon;
+}
+
+} // namespace Dialogs
diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp
index 91674bfed..3ef60a4fe 100644
--- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp
+++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_forum_topic.h"
 #include "data/data_session.h"
 #include "dialogs/dialogs_list.h"
+#include "dialogs/dialogs_three_state_icon.h"
 #include "dialogs/ui/dialogs_video_userpic.h"
 #include "styles/style_dialogs.h"
 #include "styles/style_window.h"
@@ -147,11 +148,10 @@ int PaintBadges(
 		const auto badge = PaintUnreadBadge(p, counter, right, top, st);
 		right -= badge.width() + st.padding;
 	} else if (displayPinnedIcon) {
-		const auto &icon = context.active
-			? st::dialogsPinnedIconActive
-			: context.selected
-			? st::dialogsPinnedIconOver
-			: st::dialogsPinnedIcon;
+		const auto &icon = ThreeStateIcon(
+			st::dialogsPinnedIcon,
+			context.active,
+			context.selected);
 		icon.paint(p, right - icon.width(), pinnedIconTop, context.width);
 		right -= icon.width() + st::dialogsUnreadPadding;
 	}
@@ -169,17 +169,12 @@ int PaintBadges(
 		st.textTop = 0;
 		const auto counter = QString();
 		const auto badge = PaintUnreadBadge(p, counter, right, top, st);
-		(badgesState.mention
-			? (st.active
-				? st::dialogsUnreadMentionActive
-				: st.selected
-				? st::dialogsUnreadMentionOver
-				: st::dialogsUnreadMention)
-			: (st.active
-				? st::dialogsUnreadReactionActive
-				: st.selected
-				? st::dialogsUnreadReactionOver
-				: st::dialogsUnreadReaction)).paintInCenter(p, badge);
+		ThreeStateIcon(
+			badgesState.mention
+				? st::dialogsUnreadMention
+				: st::dialogsUnreadReaction,
+			st.active,
+			st.selected).paintInCenter(p, badge);
 		right -= badge.width() + st.padding + st::dialogsUnreadPadding;
 	}
 	return (initial - right);
@@ -437,11 +432,10 @@ void PaintRow(
 		auto availableWidth = namewidth;
 		if (entry->isPinnedDialog(context.filter)
 			&& (context.filter || !entry->fixedOnTopIndex())) {
-			auto &icon = context.active
-				? st::dialogsPinnedIconActive
-				: context.selected
-				? st::dialogsPinnedIconOver
-				: st::dialogsPinnedIcon;
+			auto &icon = ThreeStateIcon(
+				st::dialogsPinnedIcon,
+				context.active,
+				context.selected);
 			icon.paint(
 				p,
 				context.width - context.st->padding.right() - icon.width(),
@@ -527,11 +521,10 @@ void PaintRow(
 		auto availableWidth = namewidth;
 		if (entry->isPinnedDialog(context.filter)
 			&& (context.filter || !entry->fixedOnTopIndex())) {
-			auto &icon = context.active
-				? st::dialogsPinnedIconActive
-				: context.selected
-				? st::dialogsPinnedIconOver
-				: st::dialogsPinnedIcon;
+			auto &icon = ThreeStateIcon(
+				st::dialogsPinnedIcon,
+				context.active,
+				context.selected);
 			icon.paint(p, context.width - context.st->padding.right() - icon.width(), texttop, context.width);
 			availableWidth -= icon.width() + st::dialogsUnreadPadding;
 		}
@@ -561,51 +554,49 @@ void PaintRow(
 		paintItemCallback(nameleft, namewidth);
 	} else if (entry->isPinnedDialog(context.filter)
 		&& (context.filter || !entry->fixedOnTopIndex())) {
-		auto &icon = context.active
-			? st::dialogsPinnedIconActive
-			: context.selected
-			? st::dialogsPinnedIconOver
-			: st::dialogsPinnedIcon;
-		icon.paint(p, context.width - context.st->padding.right() - icon.width(), texttop, context.width);
+		auto &icon = ThreeStateIcon(
+			st::dialogsPinnedIcon,
+			context.active,
+			context.selected);
+		icon.paint(
+			p,
+			context.width - context.st->padding.right() - icon.width(),
+			texttop,
+			context.width);
 	}
 	const auto sendStateIcon = [&]() -> const style::icon* {
 		if (!thread) {
 			return nullptr;
 		} else if (const auto topic = thread->asTopic()
 			; !context.search && topic && topic->closed()) {
-			return &(context.active
-				? st::dialogsLockIconActive
-				: context.selected
-				? st::dialogsLockIconOver
-				: st::dialogsLockIcon);
+			return &ThreeStateIcon(
+				st::dialogsLockIcon,
+				context.active,
+				context.selected);
 		} else if (draft) {
 			if (draft->saveRequestId) {
-				return &(context.active
-					? st::dialogsSendingIconActive
-					: context.selected
-					? st::dialogsSendingIconOver
-					: st::dialogsSendingIcon);
+				return &ThreeStateIcon(
+					st::dialogsSendingIcon,
+					context.active,
+					context.selected);
 			}
 		} else if (item && !item->isEmpty() && item->needCheck()) {
 			if (!item->isSending() && !item->hasFailed()) {
 				if (item->unread(thread)) {
-					return &(context.active
-						? st::dialogsSentIconActive
-						: context.selected
-						? st::dialogsSentIconOver
-						: st::dialogsSentIcon);
+					return &ThreeStateIcon(
+						st::dialogsSentIcon,
+						context.active,
+						context.selected);
 				}
-				return &(context.active
-					? st::dialogsReceivedIconActive
-					: context.selected
-					? st::dialogsReceivedIconOver
-					: st::dialogsReceivedIcon);
+				return &ThreeStateIcon(
+					st::dialogsReceivedIcon,
+					context.active,
+					context.selected);
 			}
-			return &(context.active
-				? st::dialogsSendingIconActive
-				: context.selected
-				? st::dialogsSendingIconOver
-				: st::dialogsSendingIcon);
+			return &ThreeStateIcon(
+				st::dialogsSendingIcon,
+				context.active,
+				context.selected);
 		}
 		return nullptr;
 	}();
@@ -643,11 +634,10 @@ void PaintRow(
 						: context.selected
 						? &st::dialogsVerifiedIconOver
 						: &st::dialogsVerifiedIcon),
-					.premium = (context.active
-						? &st::dialogsPremiumIconActive
-						: context.selected
-						? &st::dialogsPremiumIconOver
-						: &st::dialogsPremiumIcon),
+					.premium = &ThreeStateIcon(
+						st::dialogsPremiumIcon,
+						context.active,
+						context.selected),
 					.scam = (context.active
 						? &st::dialogsScamFgActive
 						: context.selected
@@ -710,30 +700,26 @@ const style::icon *ChatTypeIcon(
 		const PaintContext &context) {
 	if (const auto user = peer->asUser()) {
 		if (ShowUserBotIcon(user)) {
-			return &(context.active
-				? st::dialogsBotIconActive
-				: context.selected
-				? st::dialogsBotIconOver
-				: st::dialogsBotIcon);
+			return &ThreeStateIcon(
+				st::dialogsBotIcon,
+				context.active,
+				context.selected);
 		}
 	} else if (peer->isBroadcast()) {
-		return &(context.active
-			? st::dialogsChannelIconActive
-			: context.selected
-			? st::dialogsChannelIconOver
-			: st::dialogsChannelIcon);
+		return &ThreeStateIcon(
+			st::dialogsChannelIcon,
+			context.active,
+			context.selected);
 	} else if (peer->isForum()) {
-		return &(context.active
-			? st::dialogsForumIconActive
-			: context.selected
-			? st::dialogsForumIconOver
-			: st::dialogsForumIcon);
+		return &ThreeStateIcon(
+			st::dialogsForumIcon,
+			context.active,
+			context.selected);
 	} else {
-		return &(context.active
-			? st::dialogsChatIconActive
-			: context.selected
-			? st::dialogsChatIconOver
-			: st::dialogsChatIcon);
+		return &ThreeStateIcon(
+			st::dialogsChatIcon,
+			context.active,
+			context.selected);
 	}
 	return nullptr;
 }
diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp
index af4d0d3c3..85081ed0d 100644
--- a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp
+++ b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "history/history_item.h"
 #include "history/view/history_view_item_preview.h"
 #include "main/main_session.h"
+#include "dialogs/dialogs_three_state_icon.h"
 #include "dialogs/ui/dialogs_layout.h"
 #include "dialogs/ui/dialogs_topics_view.h"
 #include "ui/effects/spoiler_mess.h"
@@ -316,11 +317,10 @@ void MessageView::paint(
 	}
 
 	if (_displayMiniForwardIcon) {
-		const auto &icon = context.active
-			? st::dialogsMiniForwardIconActive
-			: context.selected
-			? st::dialogsMiniForwardIconOver
-			: st::dialogsMiniForwardIcon;
+		const auto &icon = ThreeStateIcon(
+			st::dialogsMiniForwardIcon,
+			context.active,
+			context.selected);
 		icon.paint(p, rect.topLeft(), rect.width());
 		rect.setLeft(rect.x()
 			+ icon.width()
diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp
index e6f7fbcad..aa453c225 100644
--- a/Telegram/SourceFiles/history/view/history_view_message.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_message.cpp
@@ -759,7 +759,9 @@ QSize Message::performCountOptimalSize() {
 					: item->hiddenSenderInfo()->nameText();
 				auto namew = st::msgPadding.left()
 					+ name.maxWidth()
-					+ (_fromNameStatus ? st::dialogsPremiumIcon.width() : 0)
+					+ (_fromNameStatus
+						? st::dialogsPremiumIcon.icon.width()
+						: 0)
 					+ st::msgPadding.right();
 				if (via && !displayForwardedFrom()) {
 					namew += st::msgServiceFont->spacew + via->maxWidth
@@ -1358,7 +1360,7 @@ void Message::paintFromName(
 		return &info->nameText();
 	}();
 	const auto statusWidth = _fromNameStatus
-		? st::dialogsPremiumIcon.width()
+		? st::dialogsPremiumIcon.icon.width()
 		: 0;
 	if (statusWidth && availableWidth > statusWidth) {
 		const auto x = availableLeft
@@ -1398,7 +1400,7 @@ void Message::paintFromName(
 				.paused = context.paused || On(PowerSaving::kEmojiStatus),
 			});
 		} else {
-			st::dialogsPremiumIcon.paint(p, x, y, width(), color);
+			st::dialogsPremiumIcon.icon.paint(p, x, y, width(), color);
 		}
 		availableWidth -= statusWidth;
 	}
@@ -1407,7 +1409,8 @@ void Message::paintFromName(
 	nameText->drawElided(p, availableLeft, trect.top(), availableWidth);
 	const auto skipWidth = nameText->maxWidth()
 		+ (_fromNameStatus
-			? (st::dialogsPremiumIcon.width() + st::msgServiceFont->spacew)
+			? (st::dialogsPremiumIcon.icon.width()
+				+ st::msgServiceFont->spacew)
 			: 0)
 		+ st::msgServiceFont->spacew;
 	availableLeft += skipWidth;
@@ -3525,7 +3528,7 @@ void Message::fromNameUpdated(int width) const {
 				- st::msgPadding.right()
 				- nameText->maxWidth()
 				+ (_fromNameStatus
-					? (st::dialogsPremiumIcon.width()
+					? (st::dialogsPremiumIcon.icon.width()
 						+ st::msgServiceFont->spacew)
 					: 0)
 				- st::msgServiceFont->spacew);
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 fa0d6ed7f..b752fe2f3 100644
--- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp
@@ -561,7 +561,7 @@ void TopBarWidget::paintTopBar(Painter &p) {
 			{
 				.peer = peer,
 				.verified = &st::dialogsVerifiedIcon,
-				.premium = &st::dialogsPremiumIcon,
+				.premium = &st::dialogsPremiumIcon.icon,
 				.scam = &st::attentionButtonFg,
 				.premiumFg = &st::dialogsVerifiedIconBg,
 				.customEmojiRepaint = [=] { update(); },
diff --git a/Telegram/SourceFiles/window/themes/window_theme_preview.cpp b/Telegram/SourceFiles/window/themes/window_theme_preview.cpp
index 8db74f0f2..11878fd98 100644
--- a/Telegram/SourceFiles/window/themes/window_theme_preview.cpp
+++ b/Telegram/SourceFiles/window/themes/window_theme_preview.cpp
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "window/themes/window_theme_preview.h"
 
+#include "dialogs/dialogs_three_state_icon.h"
 #include "lang/lang_keys.h"
 #include "platform/platform_window_title.h"
 #include "ui/text/text_options.h"
@@ -695,9 +696,15 @@ void Generator::paintRow(const Row &row) {
 
 	auto chatTypeIcon = ([&row]() -> const style::icon * {
 		if (row.type == Row::Type::Group) {
-			return &(row.active ? st::dialogsChatIconActive : (row.selected ? st::dialogsChatIconOver : st::dialogsChatIcon));
+			return &Dialogs::ThreeStateIcon(
+				st::dialogsChatIcon,
+				row.active,
+				row.selected);
 		} else if (row.type == Row::Type::Channel) {
-			return &(row.active ? st::dialogsChannelIconActive : (row.selected ? st::dialogsChannelIconOver : st::dialogsChannelIcon));
+			return &Dialogs::ThreeStateIcon(
+				st::dialogsChannelIcon,
+				row.active,
+				row.selected);
 		}
 		return nullptr;
 	})();
@@ -750,7 +757,10 @@ void Generator::paintRow(const Row &row) {
 		_p->setPen(row.active ? st::dialogsUnreadFgActive[_palette] : (row.selected ? st::dialogsUnreadFgOver[_palette] : st::dialogsUnreadFg[_palette]));
 		_p->drawText(unreadRectLeft + (unreadRectWidth - unreadWidth) / 2, unreadRectTop + textTop + st::dialogsUnreadFont->ascent, counter);
 	} else if (row.pinned) {
-		auto icon = (row.active ? st::dialogsPinnedIconActive[_palette] : (row.selected ? st::dialogsPinnedIconOver[_palette] : st::dialogsPinnedIcon[_palette]));
+		auto icon = Dialogs::ThreeStateIcon(
+			st::dialogsPinnedIcon,
+			row.active,
+			row.selected)[_palette];
 		icon.paint(*_p, x + fullWidth - st.padding.right() - icon.width(), texttop, fullWidth);
 		availableWidth -= icon.width() + st::dialogsUnreadPadding;
 	}
@@ -763,9 +773,15 @@ void Generator::paintRow(const Row &row) {
 
 	auto sendStateIcon = ([&row]() -> const style::icon* {
 		if (row.status == Status::Sent) {
-			return &(row.active ? st::dialogsSentIconActive : (row.selected ? st::dialogsSentIconOver : st::dialogsSentIcon));
+			return &Dialogs::ThreeStateIcon(
+				st::dialogsSentIcon,
+				row.active,
+				row.selected);
 		} else if (row.status == Status::Received) {
-			return &(row.active ? st::dialogsReceivedIconActive : (row.selected ? st::dialogsReceivedIconOver : st::dialogsReceivedIcon));
+			return &Dialogs::ThreeStateIcon(
+				st::dialogsReceivedIcon,
+				row.active,
+				row.selected);
 		}
 		return nullptr;
 	})();
diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake
index d5ea92dcb..d75465473 100644
--- a/Telegram/cmake/td_ui.cmake
+++ b/Telegram/cmake/td_ui.cmake
@@ -70,6 +70,7 @@ PRIVATE
 
     data/data_subscription_option.h
 
+    dialogs/dialogs_three_state_icon.h
     dialogs/ui/dialogs_stories_list.cpp
     dialogs/ui/dialogs_stories_list.h
 

From 70e298cfe49482b7897751a861c59ee266b79fbe Mon Sep 17 00:00:00 2001
From: 23rd <23rd@vivaldi.net>
Date: Wed, 30 Aug 2023 15:17:39 +0300
Subject: [PATCH 23/29] Added icon for replies to stories in dialogs list.

---
 Telegram/Resources/icons/mini_reply_story.png   | Bin 0 -> 563 bytes
 .../Resources/icons/mini_reply_story@2x.png     | Bin 0 -> 1025 bytes
 .../Resources/icons/mini_reply_story@3x.png     | Bin 0 -> 1438 bytes
 Telegram/SourceFiles/dialogs/dialogs.style      |   7 ++++++-
 .../dialogs/ui/dialogs_message_view.cpp         |  16 +++++++++-------
 .../dialogs/ui/dialogs_message_view.h           |   3 ++-
 Telegram/SourceFiles/history/history_item.cpp   |  13 +++++--------
 .../history/view/history_view_item_preview.h    |   8 ++++++--
 8 files changed, 28 insertions(+), 19 deletions(-)
 create mode 100644 Telegram/Resources/icons/mini_reply_story.png
 create mode 100644 Telegram/Resources/icons/mini_reply_story@2x.png
 create mode 100644 Telegram/Resources/icons/mini_reply_story@3x.png

diff --git a/Telegram/Resources/icons/mini_reply_story.png b/Telegram/Resources/icons/mini_reply_story.png
new file mode 100644
index 0000000000000000000000000000000000000000..bfcbc0c48a1fb238c1057d4f2a82d8802eaa1f48
GIT binary patch
literal 563
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|jKx9jP7LeL$-D$|Tv8)E(|mmy
zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uuz(rC1}QWNE&K#jHrLa|F+@Xj
z?!?op851RrwRfISO+A&W-O0N&@<!wt6)zo8?*ELDzNZd_opOuRj@;SOs`DnLdH;b_
zXHBo7$c4r8GOdg+=lDIZ>0W>Tx83>QXFs2H7pRX}a%lba*OAkDk0!nU{=33PD<pO1
z*);v>r(c%L@==@U!SboXX5H1StgTU*s-8s_GVjW)+Y+BdX^3QPzwNd-@ZOW1AzEw0
zw3`nqaA-~SdRSl~6LkFf=ev34>#u8X(GfeHW48Hb&ZK6AbI&&mOxj<fH@(_M?rW87
z&i32OFKhZ<Za<v3|Ni~=<%v8iO{C5i?R;{&ZhyRv7^BtPyuESte|2^UceWTPy!-QY
zYt-A%KUwy~tv{Nim}Rl`W=@buYv0Wrw;~4n4R`Y<GJg13V>Hue@kNH?k3W`J?Y)_^
z=%U7{8oT(FAq;=G+7JKu9=1AEV`H&Q`)X!^OYgrkbG15YO%>vGS$*|YiB-^F?T0os
zwFVLvbNzNo@<*)=TWU~rHpNKsFVLyIZcmGDKK|%Xx-;hUPa6rIF2ASUY)p+QMlZ{D
i@4lP2{<?7O|MvXGM^kru_WddWMVP0npUXO@geCyKvgM)x

literal 0
HcmV?d00001

diff --git a/Telegram/Resources/icons/mini_reply_story@2x.png b/Telegram/Resources/icons/mini_reply_story@2x.png
new file mode 100644
index 0000000000000000000000000000000000000000..a3c75e8bf1bb250fa42999282a3172d482ae713d
GIT binary patch
literal 1025
zcmV+c1pfPpP)<h;3K|Lk000e1NJLTq001BW001Be0ssI2{21+{00001b5ch_0Itp)
z=>Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@
z1ONa40RR91AfN*P1ONa40RR91AOHXW0IY^$^8f$?Ye_^wR7ef&R!b;!VHh6cl1oAw
zH6iz!T$0FGOv+^}WXdI3h?LO`MzS}X*_xuHS<I9u%0d#eMQB(kGDKOp)?_iw!X?7|
z|MUCIH{ZEAlm6A;e-?Az_j#W8{l0U)_kGVO6#p-oHxMPmD-?x=g$9G6zP>&yD@(0b
zUtV6;*4Dngy~zg33y^4cclQ_fdU|?FOG_icy}iBr`}@Pg!!a>2^66MbYHI4r$_mYV
zetvGZ+xz?bo12?6GBT*r?RFE*(a}*-QWC{wjN;<ri;D{qxwf`eQBffkke{Ewv$I2#
zm6erJG7g@TlXHK6kC;Fp(9+Vv;UpZrUf<EtAwhEC<mBX&lM`gWy}d0eD&mrV!<(3x
zK=y}+hw}1r(R^`naiTZ{QF3*4b=2h;d3kxSudi6ZY&LU<kfYUV{eFL0Sy>1WxnEsf
zAwy?prwBJSH3bf@C^|Y?NH;Vz;K?AUuC5lc6gfCJfCjZok+5-neGP7CXebPYthTnc
zmzNi?xV~S&XfzTT?Uk<_c<CU5!614yl=kc1*Vp&{{!YU1dv|v?H#Y|`At8aMgo}=I
zI-S$g(@{~}8PVR}j$k|lFJ^3P46D-YtZ{gFh(6%yC0&cff?$uw!zy$-9h*^TqgP5w
zO4tygtt1d>^ylYiR^jz}acC^UvoTR-W@dPxxZBgygAJaZp7Ln4-sa|}aPKVA+S<xS
z)zs8XPEPXA64MwlkB^VBv9YWgc6@yN>gp=Z=x{g&1_qD<6Oe!|mx~QKJ39kDK0eNZ
z3ha~=VpCHShxl|*8}sw?BpoeN)Cq>G&*#Hrq#*?b1w_`>)kVQCV|{%c97>Q+eELX-
z;)8%LKxDbOxm#OXv^O#Mpg~!ur>FDtpuY)MT3X8ILVkC5H=Z@*iQMt5R;v|7FT!Du
zXmPB85idLmk$@jSC;)tHvBvD|><_{t3Ne{XSOC|;!a`zVVk9w2rE+9sWN~pZGcz+1
zy~1X*ku{Ewj~g2szmLI@;E;(P<@>!j%;GT7)g)|tdwXzjFgrUtOfwetPGlP!8!0I%
z3?pZ1G@9k*WfDw&(S|5_v=uuk@eN;ES{fZ4#fy?lDaKb-RiXUxXN#`%@$sQjsl>G6
v7<X!mYyO~^%;cZl&CSi;-k!AZ-(!9QvfeH}VP{H^00000NkvXXu0mjfYCXy(

literal 0
HcmV?d00001

diff --git a/Telegram/Resources/icons/mini_reply_story@3x.png b/Telegram/Resources/icons/mini_reply_story@3x.png
new file mode 100644
index 0000000000000000000000000000000000000000..471cfa57d7bfbbe0b810b3b4ee69442d57f5d47d
GIT binary patch
literal 1438
zcmV;P1!4M$P)<h;3K|Lk000e1NJLTq001xm001xu0ssI2*kEqZ00001b5ch_0Itp)
z=>Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@
z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NH2uVaiR9Fe^SXn5wT@-#z8Tv9$
zKV^y%LL@GTLgGS6BCaSyawA;0;RcySNGK%AkSiH7e4kvPhzsE>8KVrD$^8FM-*&cr
z-uFG{yyqN!{TI%~Icu-|tY@vW-@Vsb`{lUr&-V&ww*oranA_Xi|0DZ6Jv|K!47|L&
zXhW!pt)Zb|Y;0_6YwN+m!N2mKrluxM7&W#rH#e`SsJOkol@H@{G&VNIk~=y&j*N^<
zPEJNeMX~xXkWEZXii?XcFE2lxGt%4J+sVnv{QSJXzd!4&r>BRaMn^|IJw4f3&1H9Y
z_x1I4N=<~#&CTlS>WGL4EQ7wjz9@TVXD4fQe}C`g<t2(#Lm3wr$1cqK`#W~7L|!{P
zyYcaHHm1D1JT-g^p;=j3cpQ{*c6OHcP=qEz4Nkzu#s)~v&(B?5U4;OtnwXfF*Vk7X
z4YsqcuC7#Lc^nJ_0s_p;%;eSTjC_22uCK32G>?yuiHV8oSbsGZnPz)?o32f2YU)>e
zP?N8ysE9^WQc@ztTHVLt=H^ygTbrGot(x50+WPA1iUjoQ*Duuu99K|KfYTQm8Y%@$
zFu#BQCN6MpR9H1MG!Q6!U?-|Tb6i<j8NnwcB&Y-vfR2vN-Q69r+S=N}P@$oop5E!{
zDS>u(cPqsGnZVgWMI&H*OAiRvU0q$&5XV`eqww%>iVF-3REXm^WaqxVJ_?4QRzO&L
zY6~8Pjv5;q32uLXUm=L&jE#*^n<yg@T3T9ANvy1_lo+y8KY#uth8-OptX@t=g(4vA
zB)JJ@U}|bQH#bLM!f#~usi~=?q$I{dTy%DJ?(OYuYim>XALw37Nl6i<v9YmPSy>Ur
zA=J#w%s4qYiIPZFB9~C!u&^+pnXRoY4h6-i6wtwWcz6hTq-s#ZsShMBp;>-@J_Se=
zc6WEx&6%HmLqbBR8>Gk237Lc_kB^TjMxuZ=O-7*RuZxQd^?~9lYK@q7c6KOCihu;`
z?d>hfrb3CDN*yRkRt^ph!UmEWiVA2QMSUZ$K0G`O4-Xp{7>EM6v$He8n3<U=j`*Xs
zw6t7WTH^U$US391+uYndJw4642%t4X4d4+51qD$CG;zGyk0Ba$0z%Z{2$}ZBj~{SM
z86mKl+Q`T#Jw1JGZH)qggM($JAHSugrF0hCmpA({EG#S}AT;Di$KoKF7LORW97@Ut
zB!h71rEYF+fXT_pp-LhwEG!V)i;D~4`h9@0XQ=}zT_T8RnG_8OyeM06Szlkz286<l
zJ1+4xI5@~o@nyQs5TrEGXdXz%qU1J+^e9YhbQi|Q$Mb_=$9VR1kuowe*eNA4E`iG0
z8Zk=?3ya*`+{nmCu}MaURasqKB{3llie^X#{#QgqO&1C04x9I{xK-+eZN8K>P)bR@
zauSJTadDBZ)6mcm5|5S+l9Q8>vZJG;HRQp~d~a`$#)NW#w+@<dA+saX5~}CtXA~dJ
zcsQIL_Qr^8Sy@>LcD3mI{QQoNj_4nQpr~?7qQ&8yn3!PcaBIWWNBUa_UiZz*kdlJn
zfshEw%gbqErNJE@9=5l)quQjUrQu*IYFO!OD_pQt<UpQd<wI<s{{DV6H#8ugfGH$8
zAqm#FrlzK@t`2WVg|pgfe0_cKkI_!>q9H@ne{CITW{;YRn|xJO6<%TC=0x5F;I*)#
syNYIBw6a34qOx7l%E7ns|9A!d1I1E6H3|;h@c;k-07*qoM6N<$f|BQexc~qF

literal 0
HcmV?d00001

diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style
index 6c5b4e68a..854faef97 100644
--- a/Telegram/SourceFiles/dialogs/dialogs.style
+++ b/Telegram/SourceFiles/dialogs/dialogs.style
@@ -467,7 +467,12 @@ dialogsMiniForwardIcon: ThreeStateIcon {
 	over: icon {{ "mini_forward", dialogsTextFgOver, point(0px, 1px) }};
 	active: icon {{ "mini_forward", dialogsTextFgActive, point(0px, 1px) }};
 }
-dialogsMiniForwardIconSkip: 2px;
+dialogsMiniIconSkip: 2px;
+dialogsMiniReplyStoryIcon: ThreeStateIcon {
+	icon: icon {{ "mini_reply_story", dialogsTextFg, point(0px, 1px) }};
+	over: icon {{ "mini_reply_story", dialogsTextFgOver, point(0px, 1px) }};
+	active: icon {{ "mini_reply_story", dialogsTextFgActive, point(0px, 1px) }};
+}
 
 dialogsUnreadMention: ThreeStateIcon {
 	icon: icon{{ "dialogs/dialogs_mention", dialogsUnreadFg }};
diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp
index 85081ed0d..253bdc967 100644
--- a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp
+++ b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp
@@ -159,7 +159,11 @@ void MessageView::prepare(
 	options.ignoreTopic = true;
 	options.spoilerLoginCode = true;
 	auto preview = item->toPreview(options);
-	_displayMiniForwardIcon = preview.forwardedMessage;
+	_leftIcon = (preview.icon == ItemPreview::Icon::ForwardedMessage)
+		? &st::dialogsMiniForwardIcon
+		: (preview.icon == ItemPreview::Icon::ReplyToStory)
+		? &st::dialogsMiniReplyStoryIcon
+		: nullptr;
 	const auto hasImages = !preview.images.empty();
 	const auto history = item->history();
 	const auto context = Core::MarkedTextContext{
@@ -170,7 +174,7 @@ void MessageView::prepare(
 	const auto senderTill = (preview.arrowInTextPosition > 0)
 		? preview.arrowInTextPosition
 		: preview.imagesInTextPosition;
-	if ((hasImages || _displayMiniForwardIcon) && senderTill > 0) {
+	if ((hasImages || _leftIcon) && senderTill > 0) {
 		auto sender = Text::Mid(preview.text, 0, senderTill);
 		TextUtilities::Trim(sender);
 		_senderCache.setMarkedText(
@@ -316,15 +320,13 @@ void MessageView::paint(
 		}
 	}
 
-	if (_displayMiniForwardIcon) {
+	if (_leftIcon) {
 		const auto &icon = ThreeStateIcon(
-			st::dialogsMiniForwardIcon,
+			*_leftIcon,
 			context.active,
 			context.selected);
 		icon.paint(p, rect.topLeft(), rect.width());
-		rect.setLeft(rect.x()
-			+ icon.width()
-			+ st::dialogsMiniForwardIconSkip);
+		rect.setLeft(rect.x() + icon.width() + st::dialogsMiniIconSkip);
 	}
 	for (const auto &image : _imagesCache) {
 		if (rect.width() < st::dialogsMiniPreview) {
diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h
index a23dbea49..14f677536 100644
--- a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h
+++ b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h
@@ -15,6 +15,7 @@ enum class ImageRoundRadius;
 
 namespace style {
 struct DialogRow;
+struct ThreeStateIcon;
 } // namespace style
 
 namespace Ui {
@@ -92,7 +93,7 @@ private:
 	mutable std::vector<ItemPreviewImage> _imagesCache;
 	mutable std::unique_ptr<SpoilerAnimation> _spoiler;
 	mutable std::unique_ptr<LoadingContext> _loadingContext;
-	mutable bool _displayMiniForwardIcon = false;
+	mutable const style::ThreeStateIcon *_leftIcon = nullptr;
 
 };
 
diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp
index 3357ed3ba..ff5aa329b 100644
--- a/Telegram/SourceFiles/history/history_item.cpp
+++ b/Telegram/SourceFiles/history/history_item.cpp
@@ -15,17 +15,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "history/view/history_view_message.h"
 #include "history/view/history_view_service_message.h"
 #include "history/view/media/history_view_media_grouped.h"
-#include "history/history_item.h"
 #include "history/history_item_components.h"
 #include "history/history_item_helpers.h"
 #include "history/history_unread_things.h"
 #include "history/history.h"
 #include "mtproto/mtproto_config.h"
 #include "media/clip/media_clip_reader.h"
-#include "ui/effects/ripple_animation.h"
 #include "ui/text/format_values.h"
 #include "ui/text/text_isolated_emoji.h"
-#include "ui/text/text_options.h"
 #include "ui/text/text_utilities.h"
 #include "storage/file_upload.h"
 #include "storage/storage_facade.h"
@@ -41,7 +38,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "mainwindow.h"
 #include "window/window_controller.h"
 #include "window/window_session_controller.h"
-#include "core/crash_reports.h"
 #include "core/click_handler_types.h"
 #include "base/unixtime.h"
 #include "base/timer_rpl.h"
@@ -72,7 +68,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "chat_helpers/stickers_gift_box_pack.h"
 #include "payments/payments_checkout_process.h" // CheckoutProcess::Start.
 #include "styles/style_dialogs.h"
-#include "styles/style_chat.h"
 
 namespace {
 
@@ -2958,9 +2953,11 @@ ItemPreview HistoryItem::toPreview(ToPreviewOptions options) const {
 			? tr::lng_from_you(tr::now)
 			: sender->shortName();
 	};
-	if (!options.ignoreForwardedMessage) {
-		result.forwardedMessage = Get<HistoryMessageForwarded>() != nullptr;
-	}
+	result.icon = (Get<HistoryMessageForwarded>() != nullptr)
+		? ItemPreview::Icon::ForwardedMessage
+		: replyToStory().valid()
+		? ItemPreview::Icon::ReplyToStory
+		: ItemPreview::Icon::None;
 	const auto fromForwarded = [&]() -> std::optional<QString> {
 		if (const auto forwarded = Get<HistoryMessageForwarded>()) {
 			return forwarded->originalSender
diff --git a/Telegram/SourceFiles/history/view/history_view_item_preview.h b/Telegram/SourceFiles/history/view/history_view_item_preview.h
index cb15afb6e..f2304c49e 100644
--- a/Telegram/SourceFiles/history/view/history_view_item_preview.h
+++ b/Telegram/SourceFiles/history/view/history_view_item_preview.h
@@ -23,12 +23,17 @@ struct ItemPreviewImage {
 };
 
 struct ItemPreview {
+	enum class Icon {
+		None,
+		ForwardedMessage,
+		ReplyToStory,
+	};
 	TextWithEntities text;
 	std::vector<ItemPreviewImage> images;
 	int arrowInTextPosition = -1;
 	int imagesInTextPosition = 0;
 	std::any loadingContext;
-	bool forwardedMessage = false;
+	Icon icon = Icon::None;
 };
 
 struct ToPreviewOptions {
@@ -38,7 +43,6 @@ struct ToPreviewOptions {
 	bool generateImages = true;
 	bool ignoreGroup = false;
 	bool ignoreTopic = true;
-	bool ignoreForwardedMessage = false;
 	bool spoilerLoginCode = false;
 	bool translated = false;
 };

From 1493b2357459ac0c87c334e3ed43d0345bde0609 Mon Sep 17 00:00:00 2001
From: 23rd <23rd@vivaldi.net>
Date: Wed, 30 Aug 2023 16:45:12 +0300
Subject: [PATCH 24/29] Added mini icon to messages with reply to story.

---
 .../history/history_item_components.cpp       | 35 +++++++++++++++----
 1 file changed, 28 insertions(+), 7 deletions(-)

diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp
index f9e1c403f..3dc030307 100644
--- a/Telegram/SourceFiles/history/history_item_components.cpp
+++ b/Telegram/SourceFiles/history/history_item_components.cpp
@@ -43,6 +43,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "api/api_bot.h"
 #include "styles/style_widgets.h"
 #include "styles/style_chat.h"
+#include "styles/style_dialogs.h" // dialogsMiniReplyStoryIcon.
 
 #include <QtGui/QGuiApplication>
 
@@ -460,7 +461,14 @@ void HistoryMessageReply::updateName(
 			w += st::msgServiceFont->spacew + replyToVia->maxWidth;
 		}
 
-		maxReplyWidth = previewSkip + qMax(w, qMin(replyToText.maxWidth(), int32(st::maxSignatureSize)));
+		maxReplyWidth = previewSkip
+			+ std::max(
+				w,
+				std::min(replyToText.maxWidth(), st::maxSignatureSize))
+			+ (storyReply
+				? (st::dialogsMiniIconSkip
+					+ st::dialogsMiniReplyStoryIcon.icon.width())
+				: 0);
 	} else {
 		maxReplyWidth = st::msgDateFont->width(statePhrase());
 	}
@@ -596,14 +604,27 @@ void HistoryMessageReply::paint(
 					? stm->historyTextFg
 					: st->msgImgReplyBarColor());
 				holder->prepareCustomEmojiPaint(p, context, replyToText);
+				auto replyToTextPosition = QPoint(
+					x + st::msgReplyBarSkip + previewSkip,
+					y + st::msgReplyPadding.top() + st::msgServiceNameFont->height);
+				const auto replyToTextPalette = &(inBubble
+					? stm->replyTextPalette
+					: st->imgReplyTextPalette());
+				if (storyReply) {
+					st::dialogsMiniReplyStoryIcon.icon.paint(
+						p,
+						replyToTextPosition,
+						w - st::msgReplyBarSkip - previewSkip,
+						replyToTextPalette->linkFg->c);
+					replyToTextPosition += QPoint(
+						st::dialogsMiniIconSkip
+							+ st::dialogsMiniReplyStoryIcon.icon.width(),
+						0);
+				}
 				replyToText.draw(p, {
-					.position = QPoint(
-						x + st::msgReplyBarSkip + previewSkip,
-						y + st::msgReplyPadding.top() + st::msgServiceNameFont->height),
+					.position = replyToTextPosition,
 					.availableWidth = w - st::msgReplyBarSkip - previewSkip,
-					.palette = &(inBubble
-						? stm->replyTextPalette
-						: st->imgReplyTextPalette()),
+					.palette = replyToTextPalette,
 					.spoiler = Ui::Text::DefaultSpoilerCache(),
 					.now = context.now,
 					.pausedEmoji = (context.paused

From 3dc0e3818bbb6a8c81ab9beb7d0f5f98f38fc734 Mon Sep 17 00:00:00 2001
From: 23rd <23rd@vivaldi.net>
Date: Wed, 30 Aug 2023 17:18:55 +0300
Subject: [PATCH 25/29] Fixed redundant peer adding to always/never lists in
 filter from menu.

---
 .../SourceFiles/boxes/choose_filter_box.cpp   | 29 +++++++++++++------
 1 file changed, 20 insertions(+), 9 deletions(-)

diff --git a/Telegram/SourceFiles/boxes/choose_filter_box.cpp b/Telegram/SourceFiles/boxes/choose_filter_box.cpp
index 181dccb1e..12d58b538 100644
--- a/Telegram/SourceFiles/boxes/choose_filter_box.cpp
+++ b/Telegram/SourceFiles/boxes/choose_filter_box.cpp
@@ -15,14 +15,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "history/history.h"
 #include "lang/lang_keys.h"
 #include "main/main_session.h"
-#include "ui/filter_icons.h"
 #include "ui/text/text_utilities.h" // Ui::Text::Bold
 #include "ui/widgets/buttons.h"
 #include "ui/widgets/popup_menu.h"
 #include "window/window_controller.h"
 #include "window/window_session_controller.h"
-#include "styles/style_settings.h"
-#include "styles/style_payments.h" // paymentsSectionButton
 #include "styles/style_media_player.h" // mediaPlayerMenuCheck
 
 namespace {
@@ -32,16 +29,30 @@ Data::ChatFilter ChangedFilter(
 		not_null<History*> history,
 		bool add) {
 	auto always = base::duplicate(filter.always());
-	if (add) {
-		always.insert(history);
-	} else {
-		always.remove(history);
-	}
 	auto never = base::duplicate(filter.never());
 	if (add) {
 		never.remove(history);
+		const auto result = Data::ChatFilter(
+			filter.id(),
+			filter.title(),
+			filter.iconEmoji(),
+			filter.flags(),
+			filter.always(),
+			filter.pinned(),
+			std::move(never));
+		if (result.contains(history)) {
+			return result;
+		} else {
+			never = base::duplicate(result.never());
+			always.insert(history);
+		}
 	} else {
-		never.insert(history);
+		const auto alwaysIt = always.find(history);
+		if (alwaysIt != end(always)) {
+			always.erase(alwaysIt);
+		} else {
+			never.insert(history);
+		}
 	}
 	return Data::ChatFilter(
 		filter.id(),

From f10da51517268387b1d10a2a4a278a14e3b807a3 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Wed, 30 Aug 2023 21:49:22 +0400
Subject: [PATCH 26/29] Improve phrases in SendFilesBox drag areas.

---
 Telegram/SourceFiles/boxes/edit_caption_box.cpp | 5 +++--
 Telegram/SourceFiles/boxes/send_files_box.cpp   | 6 ++++--
 2 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.cpp b/Telegram/SourceFiles/boxes/edit_caption_box.cpp
index 90a989ce7..4a2b53c5f 100644
--- a/Telegram/SourceFiles/boxes/edit_caption_box.cpp
+++ b/Telegram/SourceFiles/boxes/edit_caption_box.cpp
@@ -622,9 +622,10 @@ void EditCaptionBox::setupDragArea() {
 	};
 	// Avoid both drag areas appearing at one time.
 	auto computeState = [=](const QMimeData *data) {
+		using DragState = Storage::MimeDataState;
 		const auto state = Storage::ComputeMimeDataState(data);
-		return (state == Storage::MimeDataState::PhotoFiles)
-			? Storage::MimeDataState::Image
+		return (state == DragState::PhotoFiles || state == DragState::Image)
+			? (_asFile ? DragState::Files : DragState::Image)
 			: state;
 	};
 	const auto areas = DragArea::SetupDragAreaToContainer(
diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp
index dcab2b97c..46ac4b111 100644
--- a/Telegram/SourceFiles/boxes/send_files_box.cpp
+++ b/Telegram/SourceFiles/boxes/send_files_box.cpp
@@ -442,8 +442,10 @@ void SendFilesBox::setupDragArea() {
 	auto computeState = [=](const QMimeData *data) {
 		using DragState = Storage::MimeDataState;
 		const auto state = Storage::ComputeMimeDataState(data);
-		return (state == DragState::PhotoFiles)
-			? DragState::Image
+		return (state == DragState::PhotoFiles || state == DragState::Image)
+			? (_sendWay.current().sendImagesAsPhotos()
+				? DragState::Image
+				: DragState::Files)
 			: state;
 	};
 	const auto areas = DragArea::SetupDragAreaToContainer(

From d8f53d5f6096ad84177ff2ab3001ac918eed017b Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Wed, 30 Aug 2023 23:33:40 +0400
Subject: [PATCH 27/29] Version 4.9.4.

- Default private chats / groups / channels notification settings.
- Forwarded / reply-to-a-story icon in chats list message preview.
- Bug fixes and other minor improvements.
---
 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 15007bceb..3b5ae67a8 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.9.3.0" />
+    Version="4.9.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 1f7799c44..17d4cbf11 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,9,3,0
- PRODUCTVERSION 4,9,3,0
+ FILEVERSION 4,9,4,0
+ PRODUCTVERSION 4,9,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.9.3.0"
+            VALUE "FileVersion", "4.9.4.0"
             VALUE "LegalCopyright", "Copyright (C) 2014-2023"
             VALUE "ProductName", "Telegram Desktop"
-            VALUE "ProductVersion", "4.9.3.0"
+            VALUE "ProductVersion", "4.9.4.0"
         END
     END
     BLOCK "VarFileInfo"
diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc
index a5c085ccb..6a05c580e 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,9,3,0
- PRODUCTVERSION 4,9,3,0
+ FILEVERSION 4,9,4,0
+ PRODUCTVERSION 4,9,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.9.3.0"
+            VALUE "FileVersion", "4.9.4.0"
             VALUE "LegalCopyright", "Copyright (C) 2014-2023"
             VALUE "ProductName", "Telegram Desktop"
-            VALUE "ProductVersion", "4.9.3.0"
+            VALUE "ProductVersion", "4.9.4.0"
         END
     END
     BLOCK "VarFileInfo"
diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h
index 1ec86fab3..feb1dfd8b 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 = 4009003;
-constexpr auto AppVersionStr = "4.9.3";
+constexpr auto AppVersion = 4009004;
+constexpr auto AppVersionStr = "4.9.4";
 constexpr auto AppBetaVersion = false;
 constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
diff --git a/Telegram/build/version b/Telegram/build/version
index 112140bad..1b6e6f9ec 100644
--- a/Telegram/build/version
+++ b/Telegram/build/version
@@ -1,7 +1,7 @@
-AppVersion         4009003
+AppVersion         4009004
 AppVersionStrMajor 4.9
-AppVersionStrSmall 4.9.3
-AppVersionStr      4.9.3
+AppVersionStrSmall 4.9.4
+AppVersionStr      4.9.4
 BetaChannel        0
 AlphaVersion       0
-AppVersionOriginal 4.9.3
+AppVersionOriginal 4.9.4
diff --git a/changelog.txt b/changelog.txt
index 0fc4f1217..0c1ef6979 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,3 +1,9 @@
+4.9.4 (30.08.23)
+
+- Default private chats / groups / channels notification settings.
+- Forwarded / reply-to-a-story icon in chats list message preview.
+- Bug fixes and other minor improvements.
+
 4.9.3 (22.08.23)
 
 - Fix audio output on macOS.

From b39bf11d9e13c26d28e72e8ea15a83f0535edd66 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Thu, 31 Aug 2023 22:37:29 +0400
Subject: [PATCH 28/29] Fix build with GCC.

---
 Telegram/SourceFiles/menu/menu_mute.cpp | 1 -
 1 file changed, 1 deletion(-)

diff --git a/Telegram/SourceFiles/menu/menu_mute.cpp b/Telegram/SourceFiles/menu/menu_mute.cpp
index ac48e2740..dd58f6fc0 100644
--- a/Telegram/SourceFiles/menu/menu_mute.cpp
+++ b/Telegram/SourceFiles/menu/menu_mute.cpp
@@ -311,7 +311,6 @@ void FillMuteMenu(
 		soundSelect,
 		&st::menuIconSoundSelect);
 
-	const auto notifySettings = &session->data().notifySettings();
 	const auto soundIsNone = descriptor.currentSound().value_or(
 		Data::NotifySound()
 	).none;

From dff168c62e275204e680a8075f1b667e82bdce8b Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Thu, 31 Aug 2023 22:43:26 +0400
Subject: [PATCH 29/29] Version 4.9.4: Workaround MSVC optimization bug.

When adding some de-optimizing code, like logging etc,
the issue disappears. This volatile workaround looks like it works.
---
 Telegram/SourceFiles/dialogs/dialogs_main_list.cpp | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp b/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp
index b9c7b2c7b..9e643555a 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp
@@ -194,6 +194,11 @@ UnreadState MainList::unreadState() const {
 		result.chatsMuted = result.chats;
 		result.marksMuted = result.marks;
 	}
+	volatile auto touch = _unreadState.marks + _unreadState.marksMuted
+		+ _unreadState.messages + _unreadState.messagesMuted
+		+ _unreadState.chats + _unreadState.chatsMuted
+		+ _unreadState.reactions + _unreadState.reactionsMuted
+		+ _unreadState.mentions;
 	return result;
 }