From 13fe0b6272cff90d93f7c9fe63e5eac1e821bd22 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Fri, 13 Mar 2020 15:07:27 +0400
Subject: [PATCH] Allow adding chats to filter exceptions.

---
 Telegram/CMakeLists.txt                       |   8 +-
 .../boxes/filters/edit_filter_box.cpp         | 466 ++++++++++++++++++
 .../boxes/filters/edit_filter_box.h           |  26 +
 .../boxes/filters/edit_filter_chats_list.cpp  |  66 +++
 .../boxes/filters/edit_filter_chats_list.h    |  55 +++
 .../{ => filters}/manage_filters_box.cpp      | 372 +-------------
 .../boxes/{ => filters}/manage_filters_box.h  |   5 -
 .../window/window_filters_menu.cpp            |   2 +-
 8 files changed, 624 insertions(+), 376 deletions(-)
 create mode 100644 Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp
 create mode 100644 Telegram/SourceFiles/boxes/filters/edit_filter_box.h
 create mode 100644 Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp
 create mode 100644 Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.h
 rename Telegram/SourceFiles/boxes/{ => filters}/manage_filters_box.cpp (60%)
 rename Telegram/SourceFiles/boxes/{ => filters}/manage_filters_box.h (84%)

diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt
index 6cee0790b..b8c24968d 100644
--- a/Telegram/CMakeLists.txt
+++ b/Telegram/CMakeLists.txt
@@ -146,6 +146,12 @@ PRIVATE
     api/api_single_message_search.h
     api/api_text_entities.cpp
     api/api_text_entities.h
+    boxes/filters/edit_filter_box.cpp
+    boxes/filters/edit_filter_box.h
+    boxes/filters/edit_filter_chats_list.cpp
+    boxes/filters/edit_filter_chats_list.h
+    boxes/filters/manage_filters_box.cpp
+    boxes/filters/manage_filters_box.h
     boxes/peers/add_participants_box.cpp
     boxes/peers/add_participants_box.h
     boxes/peers/edit_contact_box.cpp
@@ -204,8 +210,6 @@ PRIVATE
     boxes/language_box.h
     boxes/local_storage_box.cpp
     boxes/local_storage_box.h
-    boxes/manage_filters_box.cpp
-    boxes/manage_filters_box.h
     boxes/mute_settings_box.cpp
     boxes/mute_settings_box.h
     boxes/peer_list_box.cpp
diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp
new file mode 100644
index 000000000..027968e11
--- /dev/null
+++ b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp
@@ -0,0 +1,466 @@
+/*
+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 "boxes/filters/edit_filter_box.h"
+
+#include "boxes/filters/edit_filter_chats_list.h"
+#include "ui/layers/generic_box.h"
+#include "ui/text/text_utilities.h"
+#include "ui/widgets/buttons.h"
+#include "ui/widgets/input_fields.h"
+#include "data/data_chat_filters.h"
+#include "data/data_peer.h"
+#include "data/data_session.h"
+#include "settings/settings_common.h"
+#include "lang/lang_keys.h"
+#include "history/history.h"
+#include "main/main_session.h"
+#include "window/window_session_controller.h"
+#include "window/window_controller.h"
+#include "styles/style_settings.h"
+#include "styles/style_boxes.h"
+#include "styles/style_layers.h"
+#include "styles/style_window.h"
+
+namespace {
+
+using namespace Settings;
+
+constexpr auto kMaxFilterTitleLength = 20;
+
+using Flag = Data::ChatFilter::Flag;
+using Flags = Data::ChatFilter::Flags;
+using ExceptionPeersRef = const base::flat_set<not_null<History*>> &;
+using ExceptionPeersGetter = ExceptionPeersRef(Data::ChatFilter::*)() const;
+
+constexpr auto kAllTypes = {
+	Flag::Contacts,
+	Flag::NonContacts,
+	Flag::Groups,
+	Flag::Channels,
+	Flag::Bots,
+	Flag::NoMuted,
+	Flag::NoArchived,
+	Flag::NoRead
+};
+
+class FilterChatsPreview final : public Ui::RpWidget {
+public:
+	FilterChatsPreview(
+		not_null<QWidget*> parent,
+		Flags flags,
+		const base::flat_set<not_null<History*>> &peers);
+
+	[[nodiscard]] rpl::producer<Flag> flagRemoved() const;
+	[[nodiscard]] rpl::producer<not_null<History*>> peerRemoved() const;
+
+	void updateData(
+		Flags flags,
+		const base::flat_set<not_null<History*>> &peers);
+
+	int resizeGetHeight(int newWidth) override;
+
+private:
+	using Button = base::unique_qptr<Ui::IconButton>;
+	struct FlagButton {
+		Flag flag = Flag();
+		Button button;
+	};
+	struct PeerButton {
+		not_null<History*> history;
+		Button button;
+	};
+
+	void paintEvent(QPaintEvent *e) override;
+
+	void setup(
+		Flags flags,
+		const base::flat_set<not_null<History*>> &peers);
+	void refresh();
+	void removeFlag(Flag flag);
+	void removePeer(not_null<History*> history);
+	void paintFlagIcon(QPainter &p, int left, int top, Flag flag) const;
+
+	std::vector<FlagButton> _removeFlag;
+	std::vector<PeerButton> _removePeer;
+
+	rpl::event_stream<Flag> _flagRemoved;
+	rpl::event_stream<not_null<History*>> _peerRemoved;
+
+};
+
+not_null<FilterChatsPreview*> SetupChatsPreview(
+		not_null<Ui::VerticalLayout*> content,
+		not_null<Data::ChatFilter*> data,
+		Flags flags,
+		ExceptionPeersGetter peers) {
+	const auto preview = content->add(object_ptr<FilterChatsPreview>(
+		content,
+		data->flags() & flags,
+		(data->*peers)()));
+
+	preview->flagRemoved(
+	) | rpl::start_with_next([=](Flag flag) {
+		*data = Data::ChatFilter(
+			data->id(),
+			data->title(),
+			(data->flags() & ~flag),
+			data->always(),
+			data->never());
+	}, preview->lifetime());
+
+	preview->peerRemoved(
+	) | rpl::start_with_next([=](not_null<History*> history) {
+		auto always = data->always();
+		auto never = data->never();
+		always.remove(history);
+		never.remove(history);
+		*data = Data::ChatFilter(
+			data->id(),
+			data->title(),
+			data->flags(),
+			std::move(always),
+			std::move(never));
+	}, preview->lifetime());
+
+	return preview;
+}
+
+FilterChatsPreview::FilterChatsPreview(
+	not_null<QWidget*> parent,
+	Flags flags,
+	const base::flat_set<not_null<History*>> &peers)
+: RpWidget(parent) {
+	updateData(flags, peers);
+}
+
+void FilterChatsPreview::refresh() {
+	resizeToWidth(width());
+}
+
+void FilterChatsPreview::updateData(
+		Flags flags,
+		const base::flat_set<not_null<History*>> &peers) {
+	_removeFlag.clear();
+	_removePeer.clear();
+	const auto makeButton = [&](Fn<void()> handler) {
+		auto result = base::make_unique_q<Ui::IconButton>(
+			this,
+			st::windowFilterSmallRemove);
+		result->setClickedCallback(std::move(handler));
+		return result;
+	};
+	for (const auto flag : kAllTypes) {
+		if (flags & flag) {
+			_removeFlag.push_back({
+				flag,
+				makeButton([=] { removeFlag(flag); }) });
+		}
+	}
+	for (const auto history : peers) {
+		_removePeer.push_back({
+			history,
+			makeButton([=] { removePeer(history); }) });
+	}
+	refresh();
+}
+
+int FilterChatsPreview::resizeGetHeight(int newWidth) {
+	const auto right = st::windowFilterSmallRemoveRight;
+	const auto add = (st::windowFilterSmallItem.height
+		- st::windowFilterSmallRemove.height) / 2;
+	auto top = 0;
+	const auto moveNextButton = [&](not_null<Ui::IconButton*> button) {
+		button->moveToRight(right, top + add, newWidth);
+		top += st::windowFilterSmallItem.height;
+	};
+	for (const auto &[flag, button] : _removeFlag) {
+		moveNextButton(button.get());
+	}
+	for (const auto &[history, button] : _removePeer) {
+		moveNextButton(button.get());
+	}
+	return top;
+}
+
+[[nodiscard]] QString TypeName(Flag flag) {
+	switch (flag) {
+	case Flag::Contacts: return tr::lng_filters_type_contacts(tr::now);
+	case Flag::NonContacts:
+		return tr::lng_filters_type_non_contacts(tr::now);
+	case Flag::Groups: return tr::lng_filters_type_groups(tr::now);
+	case Flag::Channels: return tr::lng_filters_type_channels(tr::now);
+	case Flag::Bots: return tr::lng_filters_type_bots(tr::now);
+	case Flag::NoMuted: return tr::lng_filters_type_no_muted(tr::now);
+	case Flag::NoArchived: return tr::lng_filters_type_no_archived(tr::now);
+	case Flag::NoRead: return tr::lng_filters_type_no_read(tr::now);
+	}
+	Unexpected("Flag in TypeName.");
+}
+
+void FilterChatsPreview::paintEvent(QPaintEvent *e) {
+	auto p = Painter(this);
+	auto top = 0;
+	const auto &st = st::windowFilterSmallItem;
+	const auto iconLeft = st.photoPosition.x();
+	const auto iconTop = st.photoPosition.y();
+	const auto nameLeft = st.namePosition.x();
+	p.setFont(st::windowFilterSmallItem.nameStyle.font);
+	const auto nameTop = st.namePosition.y();
+	for (const auto &[flag, button] : _removeFlag) {
+		paintFlagIcon(p, iconLeft, top + iconTop, flag);
+
+		p.setPen(st::contactsNameFg);
+		p.drawTextLeft(nameLeft, top + nameTop, width(), TypeName(flag));
+		top += st.height;
+	}
+	for (const auto &[history, button] : _removePeer) {
+		history->peer->paintUserpicLeft(
+			p,
+			iconLeft,
+			top + iconTop,
+			width(),
+			st.photoSize);
+		p.setPen(st::contactsNameFg);
+		history->peer->nameText().drawLeftElided(
+			p,
+			nameLeft,
+			top + nameTop,
+			button->x() - nameLeft,
+			width());
+		top += st.height;
+	}
+}
+
+void FilterChatsPreview::paintFlagIcon(
+		QPainter &p,
+		int left,
+		int top,
+		Flag flag) const {
+	const auto &color = [&]() -> const style::color& {
+		switch (flag) {
+		case Flag::Contacts: return st::historyPeer4UserpicBg;
+		case Flag::NonContacts: return st::historyPeer7UserpicBg;
+		case Flag::Groups: return st::historyPeer2UserpicBg;
+		case Flag::Channels: return st::historyPeer1UserpicBg;
+		case Flag::Bots: return st::historyPeer6UserpicBg;
+		case Flag::NoMuted: return st::historyPeer6UserpicBg;
+		case Flag::NoArchived: return st::historyPeer4UserpicBg;
+		case Flag::NoRead: return st::historyPeer7UserpicBg;
+		}
+		Unexpected("Flag in color paintFlagIcon.");
+	}();
+	const auto &icon = [&]() -> const style::icon& {
+		switch (flag) {
+		case Flag::Contacts: return st::windowFilterTypeContacts;
+		case Flag::NonContacts: return st::windowFilterTypeNonContacts;
+		case Flag::Groups: return st::windowFilterTypeGroups;
+		case Flag::Channels: return st::windowFilterTypeChannels;
+		case Flag::Bots: return st::windowFilterTypeBots;
+		case Flag::NoMuted: return st::windowFilterTypeNoMuted;
+		case Flag::NoArchived: return st::windowFilterTypeNoArchived;
+		case Flag::NoRead: return st::windowFilterTypeNoRead;
+		}
+		Unexpected("Flag in icon paintFlagIcon.");
+	}();
+	const auto size = st::windowFilterSmallItem.photoSize;
+	const auto rect = QRect(left, top, size, size);
+	auto hq = PainterHighQualityEnabler(p);
+	p.setBrush(color->b);
+	p.setPen(Qt::NoPen);
+	p.drawEllipse(rect);
+	icon.paintInCenter(p, rect);
+}
+
+void FilterChatsPreview::removeFlag(Flag flag) {
+	const auto i = ranges::find(_removeFlag, flag, &FlagButton::flag);
+	Assert(i != end(_removeFlag));
+	_removeFlag.erase(i);
+	refresh();
+	_flagRemoved.fire_copy(flag);
+}
+
+void FilterChatsPreview::removePeer(not_null<History*> history) {
+	const auto i = ranges::find(_removePeer, history, &PeerButton::history);
+	Assert(i != end(_removePeer));
+	_removePeer.erase(i);
+	refresh();
+	_peerRemoved.fire_copy(history);
+}
+
+rpl::producer<Flag> FilterChatsPreview::flagRemoved() const {
+	return _flagRemoved.events();
+}
+
+rpl::producer<not_null<History*>> FilterChatsPreview::peerRemoved() const {
+	return _peerRemoved.events();
+}
+
+void EditExceptions(
+		not_null<Window::SessionController*> window,
+		not_null<QObject*> context,
+		Flags options,
+		not_null<Data::ChatFilter*> data,
+		Fn<void()> refresh) {
+	const auto include = (options & Flag::Contacts) != Flags(0);
+	const auto initBox = [=](not_null<PeerListBox*> box) {
+		box->addButton(tr::lng_settings_save(), crl::guard(context, [=] {
+			const auto peers = box->peerListCollectSelectedRows();
+			auto &&histories = ranges::view::all(
+				peers
+			) | ranges::view::transform([=](not_null<PeerData*> peer) {
+				return window->session().data().history(peer);
+			});
+			auto changed = base::flat_set<not_null<History*>>{
+				histories.begin(),
+				histories.end()
+			};
+			auto removeFrom = include ? data->never() : data->always();
+			for (const auto &history : changed) {
+				removeFrom.remove(history);
+			}
+			*data = Data::ChatFilter(
+				data->id(),
+				data->title(),
+				data->flags(),
+				include ? std::move(changed) : std::move(removeFrom),
+				include ? std::move(removeFrom) : std::move(changed));
+			refresh();
+			box->closeBox();
+		}));
+		box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
+	};
+	window->window().show(
+		Box<PeerListBox>(
+			std::make_unique<EditFilterChatsListController>(
+				window,
+				(include
+					? tr::lng_filters_include_title()
+					: tr::lng_filters_exclude_title()),
+				options,
+				data->flags() & options,
+				include ? data->always() : data->never()),
+			std::move(initBox)),
+		Ui::LayerOption::KeepOther);
+}
+
+} // namespace
+
+void EditFilterBox(
+		not_null<Ui::GenericBox*> box,
+		not_null<Window::SessionController*> window,
+		const Data::ChatFilter &filter,
+		Fn<void(const Data::ChatFilter &)> doneCallback) {
+	const auto creating = filter.title().isEmpty();
+	box->setTitle(creating ? tr::lng_filters_new() : tr::lng_filters_edit());
+
+	const auto content = box->verticalLayout();
+	const auto name = content->add(
+		object_ptr<Ui::InputField>(
+			box,
+			st::defaultInputField,
+			tr::lng_filters_new_name(),
+			filter.title()),
+		st::markdownLinkFieldPadding);
+	name->setMaxLength(kMaxFilterTitleLength);
+
+	const auto data = box->lifetime().make_state<Data::ChatFilter>(filter);
+
+	constexpr auto kTypes = Flag::Contacts
+		| Flag::NonContacts
+		| Flag::Groups
+		| Flag::Channels
+		| Flag::Bots;
+	constexpr auto kExcludeTypes = Flag::NoMuted
+		| Flag::NoArchived
+		| Flag::NoRead;
+
+	box->setFocusCallback([=] {
+		name->setFocusFast();
+	});
+
+	AddSkip(content);
+	AddDivider(content);
+	AddSkip(content);
+	AddSubsectionTitle(content, tr::lng_filters_include());
+
+	const auto include = SetupChatsPreview(
+		content,
+		data,
+		kTypes,
+		&Data::ChatFilter::always);
+
+	const auto includeAdd = AddButton(
+		content,
+		tr::lng_filters_add_chats() | Ui::Text::ToUpper(),
+		st::settingsUpdate);
+
+	AddSkip(content);
+	AddDividerText(content, tr::lng_filters_include_about());
+	AddSkip(content);
+
+	AddSubsectionTitle(content, tr::lng_filters_exclude());
+
+	const auto exclude = SetupChatsPreview(
+		content,
+		data,
+		kExcludeTypes,
+		&Data::ChatFilter::never);
+
+	const auto excludeAdd = AddButton(
+		content,
+		tr::lng_filters_add_chats() | Ui::Text::ToUpper(),
+		st::settingsUpdate);
+
+	AddSkip(content);
+	content->add(
+		object_ptr<Ui::FlatLabel>(
+			content,
+			tr::lng_filters_exclude_about(),
+			st::boxDividerLabel),
+		st::settingsDividerLabelPadding);
+
+	const auto refreshPreviews = [=] {
+		include->updateData(data->flags() & kTypes, data->always());
+		exclude->updateData(data->flags() & kExcludeTypes, data->never());
+	};
+	includeAdd->setClickedCallback([=] {
+		EditExceptions(window, box, kTypes, data, refreshPreviews);
+	});
+	excludeAdd->setClickedCallback([=] {
+		EditExceptions(window, box, kExcludeTypes, data, refreshPreviews);
+	});
+
+	const auto save = [=] {
+		const auto title = name->getLastText().trimmed();
+		if (title.isEmpty()) {
+			name->showError();
+			return;
+		} else if (!(data->flags() & kTypes) && data->always().empty()) {
+			window->window().showToast(tr::lng_filters_empty(tr::now));
+			return;
+		} else if ((data->flags() == (kTypes | Flag::NoArchived))
+			&& data->always().empty()
+			&& data->never().empty()) {
+			window->window().showToast(tr::lng_filters_default(tr::now));
+			return;
+		}
+		const auto result = Data::ChatFilter(
+			data->id(),
+			title,
+			data->flags(),
+			data->always(),
+			data->never());
+		box->closeBox();
+
+		doneCallback(result);
+	};
+	box->addButton(
+		creating ? tr::lng_filters_create_button() : tr::lng_settings_save(),
+		save);
+	box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
+}
\ No newline at end of file
diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_box.h b/Telegram/SourceFiles/boxes/filters/edit_filter_box.h
new file mode 100644
index 000000000..8cd87f8d2
--- /dev/null
+++ b/Telegram/SourceFiles/boxes/filters/edit_filter_box.h
@@ -0,0 +1,26 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+namespace Ui {
+class GenericBox;
+} // namespace Ui
+
+namespace Window {
+class SessionController;
+} // namespace Window
+
+namespace Data {
+class ChatFilter;
+} // namespace Data
+
+void EditFilterBox(
+	not_null<Ui::GenericBox*> box,
+	not_null<Window::SessionController*> window,
+	const Data::ChatFilter &filter,
+	Fn<void(const Data::ChatFilter &)> doneCallback);
diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp
new file mode 100644
index 000000000..c2e8e96d4
--- /dev/null
+++ b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp
@@ -0,0 +1,66 @@
+/*
+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 "boxes/filters/edit_filter_chats_list.h"
+
+#include "history/history.h"
+#include "window/window_session_controller.h"
+#include "lang/lang_keys.h"
+
+namespace {
+
+constexpr auto kMaxExceptions = 100;
+
+} // namespace
+
+EditFilterChatsListController::EditFilterChatsListController(
+	not_null<Window::SessionNavigation*> navigation,
+	rpl::producer<QString> title,
+	Flags options,
+	Flags selected,
+	const base::flat_set<not_null<History*>> &peers)
+: ChatsListBoxController(navigation)
+, _navigation(navigation)
+, _title(std::move(title))
+, _peers(peers) {
+}
+
+Main::Session &EditFilterChatsListController::session() const {
+	return _navigation->session();
+}
+
+void EditFilterChatsListController::rowClicked(not_null<PeerListRow*> row) {
+	const auto count = delegate()->peerListSelectedRowsCount();
+	if (count < kMaxExceptions || row->checked()) {
+		delegate()->peerListSetRowChecked(row, !row->checked());
+		updateTitle();
+	}
+}
+
+void EditFilterChatsListController::itemDeselectedHook(
+		not_null<PeerData*> peer) {
+	updateTitle();
+}
+
+void EditFilterChatsListController::prepareViewHook() {
+	delegate()->peerListSetTitle(std::move(_title));
+	delegate()->peerListAddSelectedRows(
+		_peers | ranges::view::transform(&History::peer));
+}
+
+auto EditFilterChatsListController::createRow(not_null<History*> history)
+-> std::unique_ptr<Row> {
+	return std::make_unique<Row>(history);
+}
+
+void EditFilterChatsListController::updateTitle() {
+	const auto count = delegate()->peerListSelectedRowsCount();
+	const auto additional = qsl("%1 / %2").arg(count).arg(kMaxExceptions);
+	delegate()->peerListSetTitle(tr::lng_profile_add_participant());
+	delegate()->peerListSetAdditionalTitle(rpl::single(additional));
+
+}
diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.h b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.h
new file mode 100644
index 000000000..6840b2b56
--- /dev/null
+++ b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.h
@@ -0,0 +1,55 @@
+/*
+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 "boxes/peer_list_controllers.h"
+#include "data/data_chat_filters.h"
+
+class History;
+
+namespace Ui {
+class GenericBox;
+} // namespace Ui
+
+namespace Window {
+class SessionController;
+} // namespace Window
+
+namespace Main {
+class Session;
+} // namespace Main
+
+class EditFilterChatsListController final : public ChatsListBoxController {
+public:
+	using Flag = Data::ChatFilter::Flag;
+	using Flags = Data::ChatFilter::Flags;
+
+	EditFilterChatsListController(
+		not_null<Window::SessionNavigation*> navigation,
+		rpl::producer<QString> title,
+		Flags options,
+		Flags selected,
+		const base::flat_set<not_null<History*>> &peers);
+
+	Main::Session &session() const override;
+
+	void rowClicked(not_null<PeerListRow*> row) override;
+	void itemDeselectedHook(not_null<PeerData*> peer) override;
+
+protected:
+	void prepareViewHook() override;
+	std::unique_ptr<Row> createRow(not_null<History*> history) override;
+
+private:
+	void updateTitle();
+
+	const not_null<Window::SessionNavigation*> _navigation;
+	rpl::producer<QString> _title;
+	base::flat_set<not_null<History*>> _peers;
+
+};
diff --git a/Telegram/SourceFiles/boxes/manage_filters_box.cpp b/Telegram/SourceFiles/boxes/filters/manage_filters_box.cpp
similarity index 60%
rename from Telegram/SourceFiles/boxes/manage_filters_box.cpp
rename to Telegram/SourceFiles/boxes/filters/manage_filters_box.cpp
index 3f194157d..a6bd98d8e 100644
--- a/Telegram/SourceFiles/boxes/manage_filters_box.cpp
+++ b/Telegram/SourceFiles/boxes/filters/manage_filters_box.cpp
@@ -5,10 +5,10 @@ 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 "boxes/manage_filters_box.h"
+#include "boxes/filters/manage_filters_box.h"
 
+#include "boxes/filters/edit_filter_box.h"
 #include "data/data_session.h"
-#include "data/data_chat_filters.h"
 #include "data/data_folder.h"
 #include "data/data_peer.h"
 #include "history/history.h"
@@ -35,66 +35,11 @@ namespace {
 
 constexpr auto kRefreshSuggestedTimeout = 7200 * crl::time(1000);
 constexpr auto kFiltersLimit = 10;
-constexpr auto kMaxFilterTitleLength = 20;
 
 using namespace Settings;
 
 using Flag = Data::ChatFilter::Flag;
 using Flags = Data::ChatFilter::Flags;
-using ExceptionPeersRef = const base::flat_set<not_null<History*>> &;
-using ExceptionPeersGetter = ExceptionPeersRef(Data::ChatFilter::*)() const;
-
-constexpr auto kAllTypes = {
-	Flag::Contacts,
-	Flag::NonContacts,
-	Flag::Groups,
-	Flag::Channels,
-	Flag::Bots,
-	Flag::NoMuted,
-	Flag::NoArchived,
-	Flag::NoRead
-};
-
-class FilterChatsPreview final : public Ui::RpWidget {
-public:
-	FilterChatsPreview(
-		not_null<QWidget*> parent,
-		Flags flags,
-		const base::flat_set<not_null<History*>> &peers);
-
-	[[nodiscard]] rpl::producer<Flag> flagRemoved() const;
-	[[nodiscard]] rpl::producer<not_null<History*>> peerRemoved() const;
-
-	int resizeGetHeight(int newWidth) override;
-
-private:
-	using Button = base::unique_qptr<Ui::IconButton>;
-	struct FlagButton {
-		Flag flag = Flag();
-		Button button;
-	};
-	struct PeerButton {
-		not_null<History*> history;
-		Button button;
-	};
-
-	void paintEvent(QPaintEvent *e) override;
-
-	void setup(
-		Flags flags,
-		const base::flat_set<not_null<History*>> &peers);
-	void refresh();
-	void removeFlag(Flag flag);
-	void removePeer(not_null<History*> history);
-	void paintFlagIcon(QPainter &p, int left, int top, Flag flag) const;
-
-	std::vector<FlagButton> _removeFlag;
-	std::vector<PeerButton> _removePeer;
-
-	rpl::event_stream<Flag> _flagRemoved;
-	rpl::event_stream<not_null<History*>> _peerRemoved;
-
-};
 
 class FilterRowButton final : public Ui::RippleButton {
 public:
@@ -196,173 +141,6 @@ private:
 		: tr::lng_filters_no_chats(tr::now);
 }
 
-FilterChatsPreview::FilterChatsPreview(
-	not_null<QWidget*> parent,
-	Flags flags,
-	const base::flat_set<not_null<History*>> &peers)
-: RpWidget(parent) {
-	setup(flags, peers);
-}
-
-void FilterChatsPreview::setup(
-		Flags flags,
-		const base::flat_set<not_null<History*>> &peers) {
-	const auto makeButton = [&](Fn<void()> handler) {
-		auto result = base::make_unique_q<Ui::IconButton>(
-			this,
-			st::windowFilterSmallRemove);
-		result->setClickedCallback(std::move(handler));
-		return result;
-	};
-	for (const auto flag : kAllTypes) {
-		if (flags & flag) {
-			_removeFlag.push_back({
-				flag,
-				makeButton([=] { removeFlag(flag); }) });
-		}
-	}
-	for (const auto history : peers) {
-		_removePeer.push_back({
-			history,
-			makeButton([=] { removePeer(history); }) });
-	}
-	refresh();
-}
-
-void FilterChatsPreview::refresh() {
-	resizeToWidth(width());
-}
-
-int FilterChatsPreview::resizeGetHeight(int newWidth) {
-	const auto right = st::windowFilterSmallRemoveRight;
-	const auto add = (st::windowFilterSmallItem.height
-		- st::windowFilterSmallRemove.height) / 2;
-	auto top = 0;
-	const auto moveNextButton = [&](not_null<Ui::IconButton*> button) {
-		button->moveToRight(right, top + add, newWidth);
-		top += st::windowFilterSmallItem.height;
-	};
-	for (const auto &[flag, button] : _removeFlag) {
-		moveNextButton(button.get());
-	}
-	for (const auto &[history, button] : _removePeer) {
-		moveNextButton(button.get());
-	}
-	return top;
-}
-
-[[nodiscard]] QString TypeName(Flag flag) {
-	switch (flag) {
-	case Flag::Contacts: return tr::lng_filters_type_contacts(tr::now);
-	case Flag::NonContacts:
-		return tr::lng_filters_type_non_contacts(tr::now);
-	case Flag::Groups: return tr::lng_filters_type_groups(tr::now);
-	case Flag::Channels: return tr::lng_filters_type_channels(tr::now);
-	case Flag::Bots: return tr::lng_filters_type_bots(tr::now);
-	case Flag::NoMuted: return tr::lng_filters_type_no_muted(tr::now);
-	case Flag::NoArchived: return tr::lng_filters_type_no_archived(tr::now);
-	case Flag::NoRead: return tr::lng_filters_type_no_read(tr::now);
-	}
-	Unexpected("Flag in TypeName.");
-}
-
-void FilterChatsPreview::paintEvent(QPaintEvent *e) {
-	auto p = Painter(this);
-	auto top = 0;
-	const auto &st = st::windowFilterSmallItem;
-	const auto iconLeft = st.photoPosition.x();
-	const auto iconTop = st.photoPosition.y();
-	const auto nameLeft = st.namePosition.x();
-	p.setFont(st::windowFilterSmallItem.nameStyle.font);
-	const auto nameTop = st.namePosition.y();
-	for (const auto &[flag, button] : _removeFlag) {
-		paintFlagIcon(p, iconLeft, top + iconTop, flag);
-
-		p.setPen(st::contactsNameFg);
-		p.drawTextLeft(nameLeft, top + nameTop, width(), TypeName(flag));
-		top += st.height;
-	}
-	for (const auto &[history, button] : _removePeer) {
-		history->peer->paintUserpicLeft(
-			p,
-			iconLeft,
-			top + iconTop,
-			width(),
-			st.photoSize);
-		history->peer->nameText().drawLeftElided(
-			p,
-			nameLeft,
-			top + nameTop,
-			button->x() - nameLeft,
-			width());
-		top += st.height;
-	}
-}
-
-void FilterChatsPreview::paintFlagIcon(
-		QPainter &p,
-		int left,
-		int top,
-		Flag flag) const {
-	const auto &color = [&]() -> const style::color& {
-		switch (flag) {
-		case Flag::Contacts: return st::historyPeer4UserpicBg;
-		case Flag::NonContacts: return st::historyPeer7UserpicBg;
-		case Flag::Groups: return st::historyPeer2UserpicBg;
-		case Flag::Channels: return st::historyPeer1UserpicBg;
-		case Flag::Bots: return st::historyPeer6UserpicBg;
-		case Flag::NoMuted: return st::historyPeer6UserpicBg;
-		case Flag::NoArchived: return st::historyPeer4UserpicBg;
-		case Flag::NoRead: return st::historyPeer7UserpicBg;
-		}
-		Unexpected("Flag in color paintFlagIcon.");
-	}();
-	const auto &icon = [&]() -> const style::icon& {
-		switch (flag) {
-		case Flag::Contacts: return st::windowFilterTypeContacts;
-		case Flag::NonContacts: return st::windowFilterTypeNonContacts;
-		case Flag::Groups: return st::windowFilterTypeGroups;
-		case Flag::Channels: return st::windowFilterTypeChannels;
-		case Flag::Bots: return st::windowFilterTypeBots;
-		case Flag::NoMuted: return st::windowFilterTypeNoMuted;
-		case Flag::NoArchived: return st::windowFilterTypeNoArchived;
-		case Flag::NoRead: return st::windowFilterTypeNoRead;
-		}
-		Unexpected("Flag in icon paintFlagIcon.");
-	}();
-	const auto size = st::windowFilterSmallItem.photoSize;
-	const auto rect = QRect(left, top, size, size);
-	auto hq = PainterHighQualityEnabler(p);
-	p.setBrush(color->b);
-	p.setPen(Qt::NoPen);
-	p.drawEllipse(rect);
-	icon.paintInCenter(p, rect);
-}
-
-void FilterChatsPreview::removeFlag(Flag flag) {
-	const auto i = ranges::find(_removeFlag, flag, &FlagButton::flag);
-	Assert(i != end(_removeFlag));
-	_removeFlag.erase(i);
-	refresh();
-	_flagRemoved.fire_copy(flag);
-}
-
-void FilterChatsPreview::removePeer(not_null<History*> history) {
-	const auto i = ranges::find(_removePeer, history, &PeerButton::history);
-	Assert(i != end(_removePeer));
-	_removePeer.erase(i);
-	refresh();
-	_peerRemoved.fire_copy(history);
-}
-
-rpl::producer<Flag> FilterChatsPreview::flagRemoved() const {
-	return _flagRemoved.events();
-}
-
-rpl::producer<not_null<History*>> FilterChatsPreview::peerRemoved() const {
-	return _peerRemoved.events();
-}
-
 FilterRowButton::FilterRowButton(
 	not_null<QWidget*> parent,
 	not_null<Main::Session*> session,
@@ -609,7 +387,7 @@ void ManageFiltersPrepare::SetupBox(
 				button->updateData(result);
 			};
 			window->window().show(Box(
-				EditBox,
+				EditFilterBox,
 				window,
 				found->filter,
 				crl::guard(button, doneCallback)));
@@ -633,7 +411,7 @@ void ManageFiltersPrepare::SetupBox(
 			addFilter(result);
 		};
 		window->window().show(Box(
-			EditBox,
+			EditFilterBox,
 			window,
 			Data::ChatFilter(),
 			crl::guard(box, doneCallback)));
@@ -765,145 +543,3 @@ void ManageFiltersPrepare::SetupBox(
 	box->boxClosing() | rpl::start_with_next(save, box->lifetime());
 	box->addButton(tr::lng_about_done(), [=] { box->closeBox(); });
 }
-
-void SetupChatsPreview(
-		not_null<Ui::VerticalLayout*> content,
-		not_null<Data::ChatFilter*> data,
-		Flags flags,
-		ExceptionPeersGetter peers) {
-	const auto preview = content->add(object_ptr<FilterChatsPreview>(
-		content,
-		data->flags() & flags,
-		(data->*peers)()));
-
-	preview->flagRemoved(
-	) | rpl::start_with_next([=](Flag flag) {
-		*data = Data::ChatFilter(
-			data->id(),
-			data->title(),
-			(data->flags() & ~flag),
-			data->always(),
-			data->never());
-	}, preview->lifetime());
-
-	preview->peerRemoved(
-	) | rpl::start_with_next([=](not_null<History*> history) {
-		auto always = data->always();
-		auto never = data->never();
-		always.remove(history);
-		never.remove(history);
-		*data = Data::ChatFilter(
-			data->id(),
-			data->title(),
-			data->flags(),
-			std::move(always),
-			std::move(never));
-	}, preview->lifetime());
-}
-
-void ManageFiltersPrepare::EditBox(
-		not_null<Ui::GenericBox*> box,
-		not_null<Window::SessionController*> window,
-		const Data::ChatFilter &filter,
-		Fn<void(const Data::ChatFilter &)> doneCallback) {
-	const auto creating = filter.title().isEmpty();
-	box->setTitle(creating ? tr::lng_filters_new() : tr::lng_filters_edit());
-
-	const auto content = box->verticalLayout();
-	const auto name = content->add(
-		object_ptr<Ui::InputField>(
-			box,
-			st::defaultInputField,
-			tr::lng_filters_new_name(),
-			filter.title()),
-		st::markdownLinkFieldPadding);
-	name->setMaxLength(kMaxFilterTitleLength);
-
-	const auto data = box->lifetime().make_state<Data::ChatFilter>(filter);
-
-	constexpr auto kTypes = Flag::Contacts
-		| Flag::NonContacts
-		| Flag::Groups
-		| Flag::Channels
-		| Flag::Bots;
-	constexpr auto kExcludeTypes = Flag::NoMuted
-		| Flag::NoArchived
-		| Flag::NoRead;
-
-	box->setFocusCallback([=] {
-		name->setFocusFast();
-	});
-
-	AddSkip(content);
-	AddDivider(content);
-	AddSkip(content);
-	AddSubsectionTitle(content, tr::lng_filters_include());
-
-	SetupChatsPreview(
-		content,
-		data,
-		kTypes,
-		&Data::ChatFilter::always);
-
-	AddButton(
-		content,
-		tr::lng_filters_add_chats() | Ui::Text::ToUpper(),
-		st::settingsUpdate
-	)->setClickedCallback([=] {
-	});
-
-	AddSkip(content);
-	AddDividerText(content, tr::lng_filters_include_about());
-	AddSkip(content);
-
-	AddSubsectionTitle(content, tr::lng_filters_exclude());
-
-	SetupChatsPreview(
-		content,
-		data,
-		kExcludeTypes,
-		&Data::ChatFilter::never);
-
-	AddButton(
-		content,
-		tr::lng_filters_add_chats() | Ui::Text::ToUpper(),
-		st::settingsUpdate
-	)->setClickedCallback([=] {
-	});
-	AddSkip(content);
-	content->add(
-		object_ptr<Ui::FlatLabel>(
-			content,
-			tr::lng_filters_exclude_about(),
-			st::boxDividerLabel),
-		st::settingsDividerLabelPadding);
-
-	const auto save = [=] {
-		const auto title = name->getLastText().trimmed();
-		if (title.isEmpty()) {
-			name->showError();
-			return;
-		} else if (!(data->flags() & kTypes) && data->always().empty()) {
-			window->window().showToast(tr::lng_filters_empty(tr::now));
-			return;
-		} else if ((data->flags() == (kTypes | Flag::NoArchived))
-			&& data->always().empty()
-			&& data->never().empty()) {
-			window->window().showToast(tr::lng_filters_default(tr::now));
-			return;
-		}
-		const auto result = Data::ChatFilter(
-			data->id(),
-			title,
-			data->flags(),
-			data->always(),
-			data->never());
-		box->closeBox();
-
-		doneCallback(result);
-	};
-	box->addButton(
-		creating ? tr::lng_filters_create_button() : tr::lng_settings_save(),
-		save);
-	box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
-}
\ No newline at end of file
diff --git a/Telegram/SourceFiles/boxes/manage_filters_box.h b/Telegram/SourceFiles/boxes/filters/manage_filters_box.h
similarity index 84%
rename from Telegram/SourceFiles/boxes/manage_filters_box.h
rename to Telegram/SourceFiles/boxes/filters/manage_filters_box.h
index 9900927e5..955ecf311 100644
--- a/Telegram/SourceFiles/boxes/manage_filters_box.h
+++ b/Telegram/SourceFiles/boxes/filters/manage_filters_box.h
@@ -38,11 +38,6 @@ private:
 		not_null<Ui::GenericBox*> box,
 		not_null<Window::SessionController*> window,
 		const std::vector<Suggested> &suggested);
-	static void EditBox(
-		not_null<Ui::GenericBox*> box,
-		not_null<Window::SessionController*> window,
-		const Data::ChatFilter &filter,
-		Fn<void(const Data::ChatFilter &)> doneCallback);
 
 	const not_null<Window::SessionController*> _window;
 	const not_null<ApiWrap*> _api;
diff --git a/Telegram/SourceFiles/window/window_filters_menu.cpp b/Telegram/SourceFiles/window/window_filters_menu.cpp
index 7e9bd6141..1ab31de89 100644
--- a/Telegram/SourceFiles/window/window_filters_menu.cpp
+++ b/Telegram/SourceFiles/window/window_filters_menu.cpp
@@ -13,7 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "main/main_session.h"
 #include "data/data_session.h"
 #include "data/data_chat_filters.h"
-#include "boxes/manage_filters_box.h"
+#include "boxes/filters/manage_filters_box.h"
 #include "lang/lang_keys.h"
 #include "styles/style_widgets.h"
 #include "styles/style_window.h"