From e27a8fe05889bf18a43e84afb4f8a988395dea7c Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Tue, 17 Mar 2020 17:04:30 +0400
Subject: [PATCH] Support pinned locally in filters.

---
 Telegram/SourceFiles/apiwrap.cpp              |  8 +-
 .../boxes/filters/edit_filter_box.cpp         | 13 ++-
 .../boxes/filters/edit_filter_chats_list.cpp  |  8 +-
 Telegram/SourceFiles/boxes/peer_list_box.cpp  |  5 +-
 Telegram/SourceFiles/boxes/peer_list_box.h    | 11 ++-
 .../SourceFiles/data/data_chat_filters.cpp    | 59 ++++++++++---
 Telegram/SourceFiles/data/data_chat_filters.h |  5 ++
 Telegram/SourceFiles/data/data_folder.cpp     |  2 +-
 Telegram/SourceFiles/data/data_session.cpp    | 53 +++++++++---
 Telegram/SourceFiles/data/data_session.h      | 15 ++--
 .../SourceFiles/dialogs/dialogs_entry.cpp     | 63 ++++++++++----
 Telegram/SourceFiles/dialogs/dialogs_entry.h  | 28 +++---
 .../dialogs/dialogs_indexed_list.cpp          | 26 +++---
 .../dialogs/dialogs_indexed_list.h            |  3 +-
 .../dialogs/dialogs_inner_widget.cpp          | 32 ++++---
 .../SourceFiles/dialogs/dialogs_layout.cpp    | 16 ++--
 Telegram/SourceFiles/dialogs/dialogs_layout.h |  2 +-
 Telegram/SourceFiles/dialogs/dialogs_list.cpp | 14 +--
 Telegram/SourceFiles/dialogs/dialogs_list.h   |  3 +-
 .../SourceFiles/dialogs/dialogs_main_list.cpp |  4 +-
 .../dialogs/dialogs_pinned_list.cpp           | 54 +++++++++---
 .../SourceFiles/dialogs/dialogs_pinned_list.h | 11 ++-
 Telegram/SourceFiles/dialogs/dialogs_row.cpp  |  6 +-
 Telegram/SourceFiles/dialogs/dialogs_row.h    |  2 +-
 Telegram/SourceFiles/history/history.cpp      | 28 ++++--
 Telegram/SourceFiles/history/history.h        |  5 ++
 .../view/history_view_top_bar_widget.cpp      |  1 +
 .../SourceFiles/info/info_wrap_widget.cpp     |  1 +
 .../SourceFiles/window/window_peer_menu.cpp   | 85 ++++++++++++++-----
 .../SourceFiles/window/window_peer_menu.h     |  1 +
 30 files changed, 409 insertions(+), 155 deletions(-)

diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp
index ac5ba4ede..2b91cbe5b 100644
--- a/Telegram/SourceFiles/apiwrap.cpp
+++ b/Telegram/SourceFiles/apiwrap.cpp
@@ -484,7 +484,9 @@ void ApiWrap::applyUpdates(
 }
 
 void ApiWrap::savePinnedOrder(Data::Folder *folder) {
-	const auto &order = _session->data().pinnedChatsOrder(folder);
+	const auto &order = _session->data().pinnedChatsOrder(
+		folder,
+		FilterId());
 	const auto input = [](const Dialogs::Key &key) {
 		if (const auto history = key.history()) {
 			return MTP_inputDialogPeer(history->peer->input);
@@ -513,7 +515,7 @@ void ApiWrap::toggleHistoryArchived(
 	if (const auto already = _historyArchivedRequests.take(history)) {
 		request(already->first).cancel();
 	}
-	const auto isPinned = history->isPinnedDialog();
+	const auto isPinned = history->isPinnedDialog(0);
 	const auto archiveId = Data::Folder::kId;
 	const auto requestId = request(MTPfolders_EditPeerFolders(
 		MTP_vector<MTPInputFolderPeer>(
@@ -980,7 +982,7 @@ void ApiWrap::requestPinnedDialogs(Data::Folder *folder) {
 		result.match([&](const MTPDmessages_peerDialogs &data) {
 			_session->data().processUsers(data.vusers());
 			_session->data().processChats(data.vchats());
-			_session->data().clearPinnedChats(folder);
+			_session->data().clearPinnedChats(folder, FilterId());
 			_session->data().applyDialogs(
 				folder,
 				data.vmessages().v,
diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp
index 9360b4732..558c96b82 100644
--- a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp
+++ b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp
@@ -106,20 +106,24 @@ not_null<FilterChatsPreview*> SetupChatsPreview(
 			data->title(),
 			(data->flags() & ~flag),
 			data->always(),
+			data->pinned(),
 			data->never());
 	}, preview->lifetime());
 
 	preview->peerRemoved(
 	) | rpl::start_with_next([=](not_null<History*> history) {
 		auto always = data->always();
+		auto pinned = data->pinned();
 		auto never = data->never();
 		always.remove(history);
+		pinned.erase(ranges::remove(pinned, history), end(pinned));
 		never.remove(history);
 		*data = Data::ChatFilter(
 			data->id(),
 			data->title(),
 			data->flags(),
 			std::move(always),
+			std::move(pinned),
 			std::move(never));
 	}, preview->lifetime());
 
@@ -272,7 +276,7 @@ void EditExceptions(
 			const auto peers = box->peerListCollectSelectedRows();
 			auto &&histories = ranges::view::all(
 				peers
-			) | ranges::view::transform([=](not_null<PeerData*> peer) {
+				) | ranges::view::transform([=](not_null<PeerData*> peer) {
 				return window->session().data().history(peer);
 			});
 			auto changed = base::flat_set<not_null<History*>>{
@@ -283,11 +287,17 @@ void EditExceptions(
 			for (const auto &history : changed) {
 				removeFrom.remove(history);
 			}
+			auto pinned = data->pinned();
+			pinned.erase(ranges::remove_if(pinned, [&](not_null<History*> history) {
+				const auto contains = changed.contains(history);
+				return include ? !contains : contains;
+			}), end(pinned));
 			*data = Data::ChatFilter(
 				data->id(),
 				data->title(),
 				(data->flags() & ~options) | rawController->chosenOptions(),
 				include ? std::move(changed) : std::move(removeFrom),
+				std::move(pinned),
 				include ? std::move(removeFrom) : std::move(changed));
 			refresh();
 			box->closeBox();
@@ -407,6 +417,7 @@ void EditFilterBox(
 			title,
 			data->flags(),
 			data->always(),
+			data->pinned(),
 			data->never());
 		box->closeBox();
 
diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp
index 5740dc545..7650c00bf 100644
--- a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp
+++ b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp
@@ -380,7 +380,10 @@ object_ptr<Ui::RpWidget> EditFilterChatsListController::prepareTypesList() {
 		if (_selected & flag) {
 			if (const auto row = delegate->peerListFindRow(TypeId(flag))) {
 				content->changeCheckState(row, true, anim::type::instant);
-				this->delegate()->peerListSetForeignRowChecked(row, true);
+				this->delegate()->peerListSetForeignRowChecked(
+					row,
+					true,
+					anim::type::instant);
 			}
 		}
 	}
@@ -397,7 +400,8 @@ object_ptr<Ui::RpWidget> EditFilterChatsListController::prepareTypesList() {
 	) | rpl::start_with_next([=](RowSelectionChange update) {
 		this->delegate()->peerListSetForeignRowChecked(
 			update.row,
-			update.checked);
+			update.checked,
+			anim::type::normal);
 	}, _lifetime);
 
 	_deselectOption = [=](PeerListRowId itemId) {
diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp
index 3e1042993..2f86bf5e1 100644
--- a/Telegram/SourceFiles/boxes/peer_list_box.cpp
+++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp
@@ -203,9 +203,10 @@ void PeerListBox::peerListSetRowChecked(
 
 void PeerListBox::peerListSetForeignRowChecked(
 		not_null<PeerListRow*> row,
-		bool checked) {
+		bool checked,
+		anim::type animated) {
 	if (checked) {
-		addSelectItem(row, anim::type::normal);
+		addSelectItem(row, animated);
 
 		// This call deletes row from _searchRows.
 		_select->entity()->clearQuery();
diff --git a/Telegram/SourceFiles/boxes/peer_list_box.h b/Telegram/SourceFiles/boxes/peer_list_box.h
index f3ab5df0e..005fa563a 100644
--- a/Telegram/SourceFiles/boxes/peer_list_box.h
+++ b/Telegram/SourceFiles/boxes/peer_list_box.h
@@ -271,7 +271,10 @@ public:
 	virtual void peerListConvertRowToSearchResult(not_null<PeerListRow*> row) = 0;
 	virtual bool peerListIsRowChecked(not_null<PeerListRow*> row) = 0;
 	virtual void peerListSetRowChecked(not_null<PeerListRow*> row, bool checked) = 0;
-	virtual void peerListSetForeignRowChecked(not_null<PeerListRow*> row, bool checked) = 0;
+	virtual void peerListSetForeignRowChecked(
+		not_null<PeerListRow*> row,
+		bool checked,
+		anim::type animated) = 0;
 	virtual not_null<PeerListRow*> peerListRowAt(int index) = 0;
 	virtual void peerListRefreshRows() = 0;
 	virtual void peerListScrollToTop() = 0;
@@ -724,7 +727,8 @@ public:
 	}
 	void peerListSetForeignRowChecked(
 		not_null<PeerListRow*> row,
-		bool checked) override {
+		bool checked,
+		anim::type animated) override {
 	}
 	int peerListFullRowsCount() override {
 		return _content->fullRowsCount();
@@ -816,7 +820,8 @@ public:
 		bool checked) override;
 	void peerListSetForeignRowChecked(
 		not_null<PeerListRow*> row,
-		bool checked) override;
+		bool checked,
+		anim::type animated) override;
 	bool peerListIsRowChecked(not_null<PeerListRow*> row) override;
 	int peerListSelectedRowsCount() override;
 	std::vector<not_null<PeerData*>> peerListCollectSelectedRows() override;
diff --git a/Telegram/SourceFiles/data/data_chat_filters.cpp b/Telegram/SourceFiles/data/data_chat_filters.cpp
index 97beb7e81..d5e1c6aad 100644
--- a/Telegram/SourceFiles/data/data_chat_filters.cpp
+++ b/Telegram/SourceFiles/data/data_chat_filters.cpp
@@ -25,10 +25,12 @@ ChatFilter::ChatFilter(
 	const QString &title,
 	Flags flags,
 	base::flat_set<not_null<History*>> always,
+	std::vector<not_null<History*>> pinned,
 	base::flat_set<not_null<History*>> never)
 : _id(id)
 , _title(title)
 , _always(std::move(always))
+, _pinned(std::move(pinned))
 , _never(std::move(never))
 , _flags(flags) {
 }
@@ -66,17 +68,26 @@ ChatFilter ChatFilter::FromTL(
 		}) | ranges::view::transform([](History *history) {
 			return not_null<History*>(history);
 		});
-		auto &&always = ranges::view::all(
+		auto &&always = ranges::view::concat(
 			data.vinclude_peers().v
 		) | to_histories;
+		auto pinned = ranges::view::all(
+			data.vpinned_peers().v
+		) | to_histories | ranges::to_vector;
 		auto &&never = ranges::view::all(
 			data.vexclude_peers().v
 		) | to_histories;
+		auto &&all = ranges::view::concat(always, pinned);
+		auto list = base::flat_set<not_null<History*>>{
+			all.begin(),
+			all.end()
+		};
 		return ChatFilter(
 			data.vid().v,
 			qs(data.vtitle()),
 			flags,
-			{ always.begin(), always.end() },
+			std::move(list),
+			std::move(pinned),
 			{ never.begin(), never.end() });
 	});
 }
@@ -94,10 +105,17 @@ MTPDialogFilter ChatFilter::tl() const {
 		| ((_flags & Flag::NoArchived)
 			? TLFlag::f_exclude_archived
 			: TLFlag(0));
-	auto always = QVector<MTPInputPeer>();
-	always.reserve(_always.size());
-	for (const auto history : _always) {
-		always.push_back(history->peer->input);
+	auto always = _always;
+	auto pinned = QVector<MTPInputPeer>();
+	pinned.reserve(_pinned.size());
+	for (const auto history : _pinned) {
+		pinned.push_back(history->peer->input);
+		always.remove(history);
+	}
+	auto include = QVector<MTPInputPeer>();
+	include.reserve(always.size());
+	for (const auto history : always) {
+		include.push_back(history->peer->input);
 	}
 	auto never = QVector<MTPInputPeer>();
 	never.reserve(_never.size());
@@ -109,8 +127,8 @@ MTPDialogFilter ChatFilter::tl() const {
 		MTP_int(_id),
 		MTP_string(_title),
 		MTPstring(), // emoticon
-		MTP_vector<MTPInputPeer>(),
-		MTP_vector<MTPInputPeer>(always),
+		MTP_vector<MTPInputPeer>(pinned),
+		MTP_vector<MTPInputPeer>(include),
 		MTP_vector<MTPInputPeer>(never));
 }
 
@@ -130,6 +148,10 @@ const base::flat_set<not_null<History*>> &ChatFilter::always() const {
 	return _always;
 }
 
+const std::vector<not_null<History*>> &ChatFilter::pinned() const {
+	return _pinned;
+}
+
 const base::flat_set<not_null<History*>> &ChatFilter::never() const {
 	return _never;
 }
@@ -196,7 +218,7 @@ not_null<Dialogs::MainList*> ChatFilters::chatsList(FilterId filterId) {
 	if (!pointer) {
 		pointer = std::make_unique<Dialogs::MainList>(
 			filterId,
-			rpl::single(1));
+			rpl::single(ChatFilter::kPinnedLimit));
 	}
 	return pointer.get();
 }
@@ -284,7 +306,7 @@ void ChatFilters::applyInsert(ChatFilter filter, int position) {
 
 	_list.insert(
 		begin(_list) + position,
-		ChatFilter(filter.id(), {}, {}, {}, {}));
+		ChatFilter(filter.id(), {}, {}, {}, {}, {}));
 	applyChange(*(begin(_list) + position), std::move(filter));
 }
 
@@ -301,7 +323,7 @@ void ChatFilters::applyRemove(int position) {
 	Expects(position >= 0 && position < _list.size());
 
 	const auto i = begin(_list) + position;
-	applyChange(*i, ChatFilter(i->id(), {}, {}, {}, {}));
+	applyChange(*i, ChatFilter(i->id(), {}, {}, {}, {}, {}));
 	_list.erase(i);
 }
 
@@ -309,12 +331,21 @@ bool ChatFilters::applyChange(ChatFilter &filter, ChatFilter &&updated) {
 	const auto rulesChanged = (filter.flags() != updated.flags())
 		|| (filter.always() != updated.always())
 		|| (filter.never() != updated.never());
+	const auto pinnedChanged = (filter.pinned() != updated.pinned());
+	if (!rulesChanged
+		&& !pinnedChanged
+		&& filter.title() == updated.title()) {
+		return false;
+	}
 	if (rulesChanged) {
 		const auto id = filter.id();
 		const auto filterList = _owner->chatsFilters().chatsList(id);
 		const auto feedHistory = [&](not_null<History*> history) {
 			const auto now = updated.contains(history);
 			const auto was = filter.contains(history);
+			if (now) {
+				history->applyFilterPinnedIndex(id, updated);
+			}
 			if (now != was) {
 				if (now) {
 					history->addToChatList(id, filterList);
@@ -334,8 +365,10 @@ bool ChatFilters::applyChange(ChatFilter &filter, ChatFilter &&updated) {
 		if (const auto folder = _owner->folderLoaded(Data::Folder::kId)) {
 			feedList(folder->chatsList());
 		}
-	} else if (filter.title() == updated.title()) {
-		return false;
+	} else if (pinnedChanged) {
+		const auto id = filter.id();
+		const auto filterList = _owner->chatsFilters().chatsList(id);
+		filterList->pinned()->applyList(updated.pinned());
 	}
 	filter = std::move(updated);
 	return true;
diff --git a/Telegram/SourceFiles/data/data_chat_filters.h b/Telegram/SourceFiles/data/data_chat_filters.h
index 7e6b2775d..b5a6efbc2 100644
--- a/Telegram/SourceFiles/data/data_chat_filters.h
+++ b/Telegram/SourceFiles/data/data_chat_filters.h
@@ -34,12 +34,15 @@ public:
 	friend constexpr inline bool is_flag_type(Flag) { return true; };
 	using Flags = base::flags<Flag>;
 
+	static constexpr int kPinnedLimit = 100;
+
 	ChatFilter() = default;
 	ChatFilter(
 		FilterId id,
 		const QString &title,
 		Flags flags,
 		base::flat_set<not_null<History*>> always,
+		std::vector<not_null<History*>> pinned,
 		base::flat_set<not_null<History*>> never);
 
 	[[nodiscard]] static ChatFilter FromTL(
@@ -51,6 +54,7 @@ public:
 	[[nodiscard]] QString title() const;
 	[[nodiscard]] Flags flags() const;
 	[[nodiscard]] const base::flat_set<not_null<History*>> &always() const;
+	[[nodiscard]] const std::vector<not_null<History*>> &pinned() const;
 	[[nodiscard]] const base::flat_set<not_null<History*>> &never() const;
 
 	[[nodiscard]] bool contains(not_null<History*> history) const;
@@ -59,6 +63,7 @@ private:
 	FilterId _id = 0;
 	QString _title;
 	base::flat_set<not_null<History*>> _always;
+	std::vector<not_null<History*>> _pinned;
 	base::flat_set<not_null<History*>> _never;
 	Flags _flags;
 
diff --git a/Telegram/SourceFiles/data/data_folder.cpp b/Telegram/SourceFiles/data/data_folder.cpp
index 4cbcbb214..0d07bd833 100644
--- a/Telegram/SourceFiles/data/data_folder.cpp
+++ b/Telegram/SourceFiles/data/data_folder.cpp
@@ -347,7 +347,7 @@ void Folder::applyPinnedUpdate(const MTPDupdateDialogPinned &data) {
 	if (folderId != 0) {
 		LOG(("API Error: Nested folders detected."));
 	}
-	owner().setChatPinned(this, data.is_pinned());
+	owner().setChatPinned(this, FilterId(), data.is_pinned());
 }
 
 // #feed
diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp
index b36cb50cc..20e9df135 100644
--- a/Telegram/SourceFiles/data/data_session.cpp
+++ b/Telegram/SourceFiles/data/data_session.cpp
@@ -780,7 +780,7 @@ void Session::deleteConversationLocally(not_null<PeerData*> peer) {
 	const auto history = historyLoaded(peer);
 	if (history) {
 		if (history->folderKnown()) {
-			setChatPinned(history, false);
+			setChatPinned(history, FilterId(), false);
 		}
 		App::main()->removeDialog(history);
 		history->clear(peer->isChannel()
@@ -1459,11 +1459,16 @@ MessageIdsList Session::itemOrItsGroup(not_null<HistoryItem*> item) const {
 	return { 1, item->fullId() };
 }
 
-void Session::setChatPinned(const Dialogs::Key &key, bool pinned) {
+void Session::setChatPinned(
+		const Dialogs::Key &key,
+		FilterId filterId,
+		bool pinned) {
 	Expects(key.entry()->folderKnown());
 
-	const auto list = chatsList(key.entry()->folder())->pinned();
-	list->setPinned(key, pinned);
+	const auto list = filterId
+		? chatsFilters().chatsList(filterId)
+		: chatsList(key.entry()->folder());
+	list->pinned()->setPinned(key, pinned);
 	notifyPinnedDialogsOrderUpdated();
 }
 
@@ -1549,32 +1554,53 @@ void Session::applyDialog(
 	setPinnedFromDialog(folder, data.is_pinned());
 }
 
-int Session::pinnedChatsCount(Data::Folder *folder) const {
-	return pinnedChatsOrder(folder).size();
+int Session::pinnedChatsCount(
+		Data::Folder *folder,
+		FilterId filterId) const {
+	Expects(!folder || !filterId);
+
+	if (!filterId) {
+		return pinnedChatsOrder(folder, filterId).size();
+	}
+	const auto &list = chatsFilters().list();
+	const auto i = ranges::find(list, filterId, &Data::ChatFilter::id);
+	return (i != end(list)) ? i->pinned().size() : 0;
 }
 
-int Session::pinnedChatsLimit(Data::Folder *folder) const {
-	return folder
+int Session::pinnedChatsLimit(
+		Data::Folder *folder,
+		FilterId filterId) const {
+	return filterId
+		? Data::ChatFilter::kPinnedLimit
+		: folder
 		? Global::PinnedDialogsInFolderMax()
 		: Global::PinnedDialogsCountMax();
 }
 
 const std::vector<Dialogs::Key> &Session::pinnedChatsOrder(
-		Data::Folder *folder) const {
-	return chatsList(folder)->pinned()->order();
+		Data::Folder *folder,
+		FilterId filterId) const {
+	const auto list = filterId
+		? chatsFilters().chatsList(filterId)
+		: chatsList(folder);
+	return list->pinned()->order();
 }
 
-void Session::clearPinnedChats(Data::Folder *folder) {
+void Session::clearPinnedChats(Data::Folder *folder, FilterId filterId) {
 	chatsList(folder)->pinned()->clear();
 }
 
 void Session::reorderTwoPinnedChats(
+		FilterId filterId,
 		const Dialogs::Key &key1,
 		const Dialogs::Key &key2) {
 	Expects(key1.entry()->folderKnown() && key2.entry()->folderKnown());
-	Expects(key1.entry()->folder() == key2.entry()->folder());
+	Expects(filterId || (key1.entry()->folder() == key2.entry()->folder()));
 
-	chatsList(key1.entry()->folder())->pinned()->reorder(key1, key2);
+	const auto list = filterId
+		? chatsFilters().chatsList(filterId)
+		: chatsList(key1.entry()->folder());
+	list->pinned()->reorder(key1, key2);
 	notifyPinnedDialogsOrderUpdated();
 }
 
@@ -3349,6 +3375,7 @@ auto Session::refreshChatListEntry(
 		const auto filterList = chatsFilters().chatsList(id);
 		auto filterResult = RefreshChatListEntryResult();
 		if (filter.contains(history)) {
+			history->applyFilterPinnedIndex(id, filter);
 			filterResult.changed = !entry->inChatList(id);
 			if (filterResult.changed) {
 				entry->addToChatList(id, filterList);
diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h
index 8ff99c45e..fecc1fc87 100644
--- a/Telegram/SourceFiles/data/data_session.h
+++ b/Telegram/SourceFiles/data/data_session.h
@@ -362,16 +362,21 @@ public:
 		const QVector<MTPDialog> &dialogs,
 		std::optional<int> count = std::nullopt);
 
-	int pinnedChatsCount(Data::Folder *folder) const;
-	int pinnedChatsLimit(Data::Folder *folder) const;
+	int pinnedChatsCount(Data::Folder *folder, FilterId filterId) const;
+	int pinnedChatsLimit(Data::Folder *folder, FilterId filterId) const;
 	const std::vector<Dialogs::Key> &pinnedChatsOrder(
-		Data::Folder *folder) const;
-	void setChatPinned(const Dialogs::Key &key, bool pinned);
-	void clearPinnedChats(Data::Folder *folder);
+		Data::Folder *folder,
+		FilterId filterId) const;
+	void setChatPinned(
+		const Dialogs::Key &key,
+		FilterId filterId,
+		bool pinned);
+	void clearPinnedChats(Data::Folder *folder, FilterId filterId);
 	void applyPinnedChats(
 		Data::Folder *folder,
 		const QVector<MTPDialogPeer> &list);
 	void reorderTwoPinnedChats(
+		FilterId filterId,
 		const Dialogs::Key &key1,
 		const Dialogs::Key &key2);
 
diff --git a/Telegram/SourceFiles/dialogs/dialogs_entry.cpp b/Telegram/SourceFiles/dialogs/dialogs_entry.cpp
index c94c07814..ecf99794b 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_entry.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_entry.cpp
@@ -55,19 +55,34 @@ Main::Session &Entry::session() const {
 	return _owner->session();
 }
 
-void Entry::cachePinnedIndex(int index) {
-	if (_pinnedIndex != index) {
-		const auto wasPinned = isPinnedDialog();
-		_pinnedIndex = index;
-		if (session().supportMode()) {
-			// Force reorder in support mode.
-			_sortKeyInChatList = 0;
-		}
-		updateChatListSortPosition();
-		updateChatListEntry();
-		if (wasPinned != isPinnedDialog()) {
-			changedChatListPinHook();
+void Entry::pinnedIndexChanged(int was, int now) {
+	if (session().supportMode()) {
+		// Force reorder in support mode.
+		_sortKeyInChatList = 0;
+	}
+	updateChatListSortPosition();
+	updateChatListEntry();
+	if ((was != 0) != (now != 0)) {
+		changedChatListPinHook();
+	}
+}
+
+void Entry::cachePinnedIndex(FilterId filterId, int index) {
+	const auto i = _pinnedIndex.find(filterId);
+	const auto was = (i != end(_pinnedIndex)) ? i->second : 0;
+	if (index == was) {
+		return;
+	}
+	if (!index) {
+		_pinnedIndex.erase(i);
+		pinnedIndexChanged(was, index);
+	} else {
+		if (!was) {
+			_pinnedIndex.emplace(filterId, index);
+		} else {
+			i->second = index;
 		}
+		pinnedIndexChanged(was, index);
 	}
 }
 
@@ -97,9 +112,7 @@ void Entry::updateChatListSortPosition() {
 	const auto fixedIndex = fixedOnTopIndex();
 	_sortKeyInChatList = fixedIndex
 		? FixedOnTopDialogPos(fixedIndex)
-		: isPinnedDialog()
-		? PinnedDialogPos(_pinnedIndex)
-		: _sortKeyByDate;
+		: computeSortPosition(0);
 	if (needUpdateInChatList()) {
 		setChatListExistence(true);
 	} else {
@@ -107,6 +120,23 @@ void Entry::updateChatListSortPosition() {
 	}
 }
 
+int Entry::lookupPinnedIndex(FilterId filterId) const {
+	if (filterId) {
+		const auto i = _pinnedIndex.find(filterId);
+		return (i != end(_pinnedIndex)) ? i->second : 0;
+	} else if (!_pinnedIndex.empty()) {
+		return _pinnedIndex.front().first
+			? 0
+			: _pinnedIndex.front().second;
+	}
+	return 0;
+}
+
+uint64 Entry::computeSortPosition(FilterId filterId) const {
+	const auto index = lookupPinnedIndex(filterId);
+	return index ? PinnedDialogPos(index) : _sortKeyByDate;
+}
+
 void Entry::updateChatListExistence() {
 	setChatListExistence(shouldBeInChatList());
 }
@@ -205,6 +235,9 @@ void Entry::removeFromChatList(
 		return;
 	}
 	_chatListLinks.erase(i);
+	if (isPinnedDialog(filterId)) {
+		owner().setChatPinned(_key, filterId, false);
+	}
 	list->removeEntry(_key);
 }
 
diff --git a/Telegram/SourceFiles/dialogs/dialogs_entry.h b/Telegram/SourceFiles/dialogs/dialogs_entry.h
index eb0bfe194..b9e21a369 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_entry.h
+++ b/Telegram/SourceFiles/dialogs/dialogs_entry.h
@@ -32,10 +32,9 @@ struct RowsByLetter {
 };
 
 enum class SortMode {
-	Complex = 0x00,
-	Date    = 0x01,
-	Name    = 0x02,
-	Add     = 0x04,
+	Date    = 0x00,
+	Name    = 0x01,
+	Add     = 0x02,
 };
 
 struct PositionChange {
@@ -117,19 +116,18 @@ public:
 		QChar letter,
 		not_null<Row*> row);
 	void updateChatListEntry() const;
-	bool isPinnedDialog() const {
-		return _pinnedIndex > 0;
+	[[nodiscard]] bool isPinnedDialog(FilterId filterId) const {
+		return lookupPinnedIndex(filterId) != 0;
 	}
-	void cachePinnedIndex(int index);
+	void cachePinnedIndex(FilterId filterId, int index);
 	bool isProxyPromoted() const {
 		return _isProxyPromoted;
 	}
 	void cacheProxyPromoted(bool promoted);
-	uint64 sortKeyInChatList() const {
-		return _sortKeyInChatList;
-	}
-	uint64 sortKeyByDate() const {
-		return _sortKeyByDate;
+	[[nodiscard]] uint64 sortKeyInChatList(FilterId filterId) const {
+		return filterId
+			? computeSortPosition(filterId)
+			: _sortKeyInChatList;
 	}
 	void updateChatListSortPosition();
 	void setChatListTimeId(TimeId date);
@@ -194,8 +192,12 @@ protected:
 		});
 	}
 
+	[[nodiscard]] int lookupPinnedIndex(FilterId filterId) const;
+
 private:
 	virtual void changedChatListPinHook();
+	void pinnedIndexChanged(int was, int now);
+	[[nodiscard]] uint64 computeSortPosition(FilterId filterId) const;
 
 	void setChatListExistence(bool exists);
 	RowsByLetter *chatListLinks(FilterId filterId);
@@ -208,7 +210,7 @@ private:
 	base::flat_map<FilterId, RowsByLetter> _chatListLinks;
 	uint64 _sortKeyInChatList = 0;
 	uint64 _sortKeyByDate = 0;
-	int _pinnedIndex = 0;
+	base::flat_map<FilterId, int> _pinnedIndex;
 	bool _isProxyPromoted = false;
 	TimeId _timeId = 0;
 
diff --git a/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp b/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp
index 5af4ca39c..19a4957d1 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp
@@ -13,10 +13,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 namespace Dialogs {
 
-IndexedList::IndexedList(SortMode sortMode)
+IndexedList::IndexedList(SortMode sortMode, FilterId filterId)
 : _sortMode(sortMode)
-, _list(sortMode)
-, _empty(sortMode) {
+, _filterId(filterId)
+, _list(sortMode, filterId)
+, _empty(sortMode, filterId) {
 }
 
 RowsByLetter IndexedList::addToEnd(Key key) {
@@ -28,7 +29,7 @@ RowsByLetter IndexedList::addToEnd(Key key) {
 	for (const auto ch : key.entry()->chatListFirstLetters()) {
 		auto j = _index.find(ch);
 		if (j == _index.cend()) {
-			j = _index.emplace(ch, _sortMode).first;
+			j = _index.emplace(ch, _sortMode, _filterId).first;
 		}
 		result.letters.emplace(ch, j->second.addToEnd(key));
 	}
@@ -44,7 +45,7 @@ Row *IndexedList::addByName(Key key) {
 	for (const auto ch : key.entry()->chatListFirstLetters()) {
 		auto j = _index.find(ch);
 		if (j == _index.cend()) {
-			j = _index.emplace(ch, _sortMode).first;
+			j = _index.emplace(ch, _sortMode, _filterId).first;
 		}
 		j->second.addByName(key);
 	}
@@ -79,7 +80,8 @@ void IndexedList::movePinned(Row *row, int deltaSign) {
 		Assert(swapPinnedIndexWith != cbegin());
 		--swapPinnedIndexWith;
 	}
-	Auth().data().reorderTwoPinnedChats(
+	row->key().entry()->owner().reorderTwoPinnedChats(
+		_filterId,
 		row->key(),
 		(*swapPinnedIndexWith)->key());
 }
@@ -87,7 +89,7 @@ void IndexedList::movePinned(Row *row, int deltaSign) {
 void IndexedList::peerNameChanged(
 		not_null<PeerData*> peer,
 		const base::flat_set<QChar> &oldLetters) {
-	Expects(_sortMode != SortMode::Date && _sortMode != SortMode::Complex);
+	Expects(_sortMode != SortMode::Date);
 
 	if (const auto history = peer->owner().historyLoaded(peer)) {
 		if (_sortMode == SortMode::Name) {
@@ -102,7 +104,7 @@ void IndexedList::peerNameChanged(
 		FilterId filterId,
 		not_null<PeerData*> peer,
 		const base::flat_set<QChar> &oldLetters) {
-	Expects(_sortMode == SortMode::Date || _sortMode == SortMode::Complex);
+	Expects(_sortMode == SortMode::Date);
 
 	if (const auto history = peer->owner().historyLoaded(peer)) {
 		adjustNames(filterId, history, oldLetters);
@@ -139,7 +141,7 @@ void IndexedList::adjustByName(
 		for (auto ch : toAdd) {
 			auto j = _index.find(ch);
 			if (j == _index.cend()) {
-				j = _index.emplace(ch, _sortMode).first;
+				j = _index.emplace(ch, _sortMode, _filterId).first;
 			}
 			j->second.addByName(key);
 		}
@@ -165,7 +167,7 @@ void IndexedList::adjustNames(
 		}
 	}
 	for (auto ch : toRemove) {
-		if (_sortMode == SortMode::Date || _sortMode == SortMode::Complex) {
+		if (_sortMode == SortMode::Date) {
 			history->removeChatListEntryByLetter(filterId, ch);
 		}
 		if (auto it = _index.find(ch); it != _index.cend()) {
@@ -175,10 +177,10 @@ void IndexedList::adjustNames(
 	for (auto ch : toAdd) {
 		auto j = _index.find(ch);
 		if (j == _index.cend()) {
-			j = _index.emplace(ch, _sortMode).first;
+			j = _index.emplace(ch, _sortMode, _filterId).first;
 		}
 		auto row = j->second.addToEnd(key);
-		if (_sortMode == SortMode::Date || _sortMode == SortMode::Complex) {
+		if (_sortMode == SortMode::Date) {
 			history->addChatListEntryByLetter(filterId, ch, row);
 		}
 	}
diff --git a/Telegram/SourceFiles/dialogs/dialogs_indexed_list.h b/Telegram/SourceFiles/dialogs/dialogs_indexed_list.h
index e2ae1346f..99c362571 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_indexed_list.h
+++ b/Telegram/SourceFiles/dialogs/dialogs_indexed_list.h
@@ -16,7 +16,7 @@ namespace Dialogs {
 
 class IndexedList {
 public:
-	IndexedList(SortMode sortMode);
+	IndexedList(SortMode sortMode, FilterId filterId = 0);
 
 	RowsByLetter addToEnd(Key key);
 	Row *addByName(Key key);
@@ -81,6 +81,7 @@ private:
 		const base::flat_set<QChar> &oldChars);
 
 	SortMode _sortMode = SortMode();
+	FilterId _filterId = 0;
 	List _list, _empty;
 	base::flat_map<QChar, List> _index;
 
diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
index 5fe6aa593..f3bea9083 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
@@ -66,12 +66,14 @@ int FixedOnTopDialogsCount(not_null<Dialogs::IndexedList*> list) {
 	return result;
 }
 
-int PinnedDialogsCount(not_null<Dialogs::IndexedList*> list) {
+int PinnedDialogsCount(
+		FilterId filterId,
+		not_null<Dialogs::IndexedList*> list) {
 	auto result = 0;
 	for (const auto row : *list) {
 		if (row->entry()->fixedOnTopIndex()) {
 			continue;
-		} else if (!row->entry()->isPinnedDialog()) {
+		} else if (!row->entry()->isPinnedDialog(filterId)) {
 			break;
 		}
 		++result;
@@ -1075,7 +1077,7 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) {
 		});
 	}
 	if (anim::Disabled()
-		&& (!_pressed || !_pressed->entry()->isPinnedDialog())) {
+		&& (!_pressed || !_pressed->entry()->isPinnedDialog(_filterId))) {
 		mousePressReleased(e->globalPos(), e->button());
 	}
 }
@@ -1091,7 +1093,9 @@ void InnerWidget::checkReorderPinnedStart(QPoint localPosition) {
 	if (updateReorderIndexGetCount() < 2) {
 		_dragging = nullptr;
 	} else {
-		const auto &order = session().data().pinnedChatsOrder(_openedFolder);
+		const auto &order = session().data().pinnedChatsOrder(
+			_openedFolder,
+			_filterId);
 		_pinnedOnDragStart = base::flat_set<Key>{
 			order.begin(),
 			order.end()
@@ -1103,14 +1107,14 @@ void InnerWidget::checkReorderPinnedStart(QPoint localPosition) {
 }
 
 int InnerWidget::countPinnedIndex(Row *ofRow) {
-	if (!ofRow || !ofRow->entry()->isPinnedDialog()) {
+	if (!ofRow || !ofRow->entry()->isPinnedDialog(_filterId)) {
 		return -1;
 	}
 	auto result = 0;
 	for (const auto row : *shownDialogs()) {
 		if (row->entry()->fixedOnTopIndex()) {
 			continue;
-		} else if (!row->entry()->isPinnedDialog()) {
+		} else if (!row->entry()->isPinnedDialog(_filterId)) {
 			break;
 		} else if (row == ofRow) {
 			return result;
@@ -1121,7 +1125,9 @@ int InnerWidget::countPinnedIndex(Row *ofRow) {
 }
 
 void InnerWidget::savePinnedOrder() {
-	const auto &newOrder = session().data().pinnedChatsOrder(_openedFolder);
+	const auto &newOrder = session().data().pinnedChatsOrder(
+		_openedFolder,
+		_filterId);
 	if (newOrder.size() != _pinnedOnDragStart.size()) {
 		return; // Something has changed in the set of pinned chats.
 	}
@@ -1130,7 +1136,11 @@ void InnerWidget::savePinnedOrder() {
 			return; // Something has changed in the set of pinned chats.
 		}
 	}
-	session().api().savePinnedOrder(_openedFolder);
+	if (_filterId) {
+		// #TODO pinned reorder data and to server
+	} else {
+		session().api().savePinnedOrder(_openedFolder);
+	}
 }
 
 void InnerWidget::finishReorderPinned() {
@@ -1162,7 +1172,7 @@ int InnerWidget::updateReorderIndexGetCount() {
 		return 0;
 	}
 
-	const auto count = Dialogs::PinnedDialogsCount(shownDialogs());
+	const auto count = Dialogs::PinnedDialogsCount(_filterId, shownDialogs());
 	Assert(index < count);
 	if (count < 2) {
 		stopReorderPinned();
@@ -1789,6 +1799,7 @@ void InnerWidget::contextMenuEvent(QContextMenuEvent *e) {
 		Window::FillPeerMenu(
 			_controller,
 			history->peer,
+			_filterId,
 			[&](const QString &text, Fn<void()> callback) {
 				return _menu->addAction(text, std::move(callback));
 			},
@@ -2541,6 +2552,7 @@ void InnerWidget::switchToFilter(FilterId filterId) {
 			&Data::ChatFilter::id)) {
 		filterId = 0;
 	}
+	stopReorderPinned();
 	_filterId = filterId;
 	refreshWithCollapsedRows(true);
 	_collapsedSelected = 0;
@@ -2983,7 +2995,7 @@ void InnerWidget::setupShortcuts() {
 		for (const auto [command, index] : pinned) {
 			request->check(command) && request->handle([=, index = index] {
 				const auto list = session().data().chatsList()->indexed();
-				const auto count = Dialogs::PinnedDialogsCount(list);
+				const auto count = Dialogs::PinnedDialogsCount(_filterId, list);
 				if (index >= count) {
 					return false;
 				}
diff --git a/Telegram/SourceFiles/dialogs/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/dialogs_layout.cpp
index 879cf8e3c..f5e681e25 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_layout.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_layout.cpp
@@ -218,6 +218,7 @@ void paintRow(
 		not_null<const BasicRow*> row,
 		not_null<Entry*> entry,
 		Dialogs::Key chat,
+		FilterId filterId,
 		PeerData *from,
 		const HiddenSenderInfo *hiddenSenderInfo,
 		HistoryItem *item,
@@ -322,7 +323,7 @@ void paintRow(
 		}
 
 		auto availableWidth = namewidth;
-		if (entry->isPinnedDialog() && !entry->fixedOnTopIndex()) {
+		if (entry->isPinnedDialog(filterId) && (filterId || !entry->fixedOnTopIndex())) {
 			auto &icon = (active ? st::dialogsPinnedIconActive : (selected ? st::dialogsPinnedIconOver : st::dialogsPinnedIcon));
 			icon.paint(p, fullWidth - st::dialogsPadding.x() - icon.width(), texttop, fullWidth);
 			availableWidth -= icon.width() + st::dialogsUnreadPadding;
@@ -349,7 +350,7 @@ void paintRow(
 		}
 	} else if (!item) {
 		auto availableWidth = namewidth;
-		if (entry->isPinnedDialog() && !entry->fixedOnTopIndex()) {
+		if (entry->isPinnedDialog(filterId) && (filterId || !entry->fixedOnTopIndex())) {
 			auto &icon = (active ? st::dialogsPinnedIconActive : (selected ? st::dialogsPinnedIconOver : st::dialogsPinnedIcon));
 			icon.paint(p, fullWidth - st::dialogsPadding.x() - icon.width(), texttop, fullWidth);
 			availableWidth -= icon.width() + st::dialogsUnreadPadding;
@@ -366,7 +367,7 @@ void paintRow(
 		}
 
 		paintItemCallback(nameleft, namewidth);
-	} else if (entry->isPinnedDialog() && !entry->fixedOnTopIndex()) {
+	} else if (entry->isPinnedDialog(filterId) && (filterId || !entry->fixedOnTopIndex())) {
 		auto availableWidth = namewidth;
 		auto &icon = (active ? st::dialogsPinnedIconActive : (selected ? st::dialogsPinnedIconOver : st::dialogsPinnedIcon));
 		icon.paint(p, fullWidth - st::dialogsPadding.x() - icon.width(), texttop, fullWidth);
@@ -613,7 +614,7 @@ void paintUnreadCount(
 void RowPainter::paint(
 		Painter &p,
 		not_null<const Row*> row,
-		int filterId,
+		FilterId filterId,
 		int fullWidth,
 		bool active,
 		bool selected,
@@ -669,9 +670,8 @@ void RowPainter::paint(
 	const auto displayPinnedIcon = !displayUnreadCounter
 		&& !displayMentionBadge
 		&& !displayUnreadMark
-		&& !filterId
-		&& entry->isPinnedDialog()
-		&& !entry->fixedOnTopIndex();
+		&& entry->isPinnedDialog(filterId)
+		&& (filterId || !entry->fixedOnTopIndex());
 
 	const auto from = history
 		? (history->peer->migrateTo()
@@ -749,6 +749,7 @@ void RowPainter::paint(
 		row,
 		entry,
 		row->key(),
+		filterId,
 		from,
 		nullptr,
 		item,
@@ -872,6 +873,7 @@ void RowPainter::paint(
 		row,
 		history,
 		history,
+		FilterId(),
 		from,
 		hiddenSenderInfo,
 		item,
diff --git a/Telegram/SourceFiles/dialogs/dialogs_layout.h b/Telegram/SourceFiles/dialogs/dialogs_layout.h
index 6745569de..2ef8f29e9 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_layout.h
+++ b/Telegram/SourceFiles/dialogs/dialogs_layout.h
@@ -29,7 +29,7 @@ public:
 	static void paint(
 		Painter &p,
 		not_null<const Row*> row,
-		int filterId,
+		FilterId filterId,
 		int fullWidth,
 		bool active,
 		bool selected,
diff --git a/Telegram/SourceFiles/dialogs/dialogs_list.cpp b/Telegram/SourceFiles/dialogs/dialogs_list.cpp
index 7d3c196b4..51d8f6734 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_list.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_list.cpp
@@ -14,7 +14,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 namespace Dialogs {
 
-List::List(SortMode sortMode) : _sortMode(sortMode) {
+List::List(SortMode sortMode, FilterId filterId)
+: _sortMode(sortMode)
+, _filterId(filterId) {
 }
 
 List::const_iterator List::cfind(Row *value) const {
@@ -32,7 +34,7 @@ not_null<Row*> List::addToEnd(Key key) {
 		std::make_unique<Row>(key, _rows.size())
 	).first->second.get();
 	_rows.emplace_back(result);
-	if (_sortMode == SortMode::Date || _sortMode == SortMode::Complex) {
+	if (_sortMode == SortMode::Date) {
 		adjustByDate(result);
 	}
 	return result;
@@ -82,20 +84,20 @@ void List::adjustByName(not_null<Row*> row) {
 }
 
 void List::adjustByDate(not_null<Row*> row) {
-	Expects(_sortMode == SortMode::Date || _sortMode == SortMode::Complex);
+	Expects(_sortMode == SortMode::Date);
 
-	const auto key = row->sortKey(_sortMode);
+	const auto key = row->sortKey(_filterId);
 	const auto index = row->pos();
 	const auto i = _rows.begin() + index;
 	const auto before = std::find_if(i + 1, _rows.end(), [&](Row *row) {
-		return (row->sortKey(_sortMode) <= key);
+		return (row->sortKey(_filterId) <= key);
 	});
 	if (before != i + 1) {
 		rotate(i, i + 1, before);
 	} else {
 		const auto from = std::make_reverse_iterator(i);
 		const auto after = std::find_if(from, _rows.rend(), [&](Row *row) {
-			return (row->sortKey(_sortMode) >= key);
+			return (row->sortKey(_filterId) >= key);
 		}).base();
 		if (after != i) {
 			rotate(after, i, i + 1);
diff --git a/Telegram/SourceFiles/dialogs/dialogs_list.h b/Telegram/SourceFiles/dialogs/dialogs_list.h
index 34fb53d14..00e4985e6 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_list.h
+++ b/Telegram/SourceFiles/dialogs/dialogs_list.h
@@ -16,7 +16,7 @@ enum class SortMode;
 
 class List final {
 public:
-	List(SortMode sortMode);
+	List(SortMode sortMode, FilterId filterId = 0);
 	List(const List &other) = delete;
 	List &operator=(const List &other) = delete;
 	List(List &&other) = default;
@@ -78,6 +78,7 @@ private:
 		std::vector<not_null<Row*>>::iterator last);
 
 	SortMode _sortMode = SortMode();
+	FilterId _filterId = 0;
 	std::vector<not_null<Row*>> _rows;
 	std::map<Key, std::unique_ptr<Row>> _rowByKey;
 
diff --git a/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp b/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp
index d24dcf722..ee552a265 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp
@@ -14,8 +14,8 @@ namespace Dialogs {
 
 MainList::MainList(FilterId filterId, rpl::producer<int> pinnedLimit)
 : _filterId(filterId)
-, _all(filterId ? SortMode::Date : SortMode::Complex)
-, _pinned(1) {
+, _all(SortMode::Date, filterId)
+, _pinned(filterId, 1) {
 	_unreadState.known = true;
 
 	std::move(
diff --git a/Telegram/SourceFiles/dialogs/dialogs_pinned_list.cpp b/Telegram/SourceFiles/dialogs/dialogs_pinned_list.cpp
index 6b6e1bdd7..5b79fa9b8 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_pinned_list.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_pinned_list.cpp
@@ -9,11 +9,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 #include "dialogs/dialogs_key.h"
 #include "dialogs/dialogs_entry.h"
+#include "history/history.h"
 #include "data/data_session.h"
 
 namespace Dialogs {
 
-PinnedList::PinnedList(int limit) : _limit(limit) {
+PinnedList::PinnedList(FilterId filterId, int limit)
+: _filterId(filterId)
+, _limit(limit) {
 	Expects(limit > 0);
 }
 
@@ -41,7 +44,7 @@ int PinnedList::addPinnedGetPosition(const Key &key) {
 	applyLimit(_limit - 1);
 	const auto position = int(_data.size());
 	_data.push_back(key);
-	key.entry()->cachePinnedIndex(position + 1);
+	key.entry()->cachePinnedIndex(_filterId, position + 1);
 	return position;
 }
 
@@ -54,19 +57,34 @@ void PinnedList::setPinned(const Key &key, bool pinned) {
 			const auto begin = _data.begin();
 			std::rotate(begin, begin + position, begin + position + 1);
 			for (auto i = 0; i != position + 1; ++i) {
-				_data[i].entry()->cachePinnedIndex(i + 1);
+				_data[i].entry()->cachePinnedIndex(_filterId, i + 1);
 			}
 		}
 	} else if (const auto it = ranges::find(_data, key); it != end(_data)) {
 		const auto index = int(it - begin(_data));
 		_data.erase(it);
-		key.entry()->cachePinnedIndex(0);
+		key.entry()->cachePinnedIndex(_filterId, 0);
 		for (auto i = index, count = int(size(_data)); i != count; ++i) {
-			_data[i].entry()->cachePinnedIndex(i + 1);
+			_data[i].entry()->cachePinnedIndex(_filterId, i + 1);
 		}
 	}
 }
 
+void PinnedList::applyFilterPinned(
+		FilterId filterId,
+		not_null<History*> history,
+		int index) {
+	Expects(index > 0);
+
+	history->cachePinnedIndex(filterId, index);
+
+	const auto key = Key{ history };
+	if (ranges::find(_data, key) == end(_data)) {
+		_data.push_back(key);
+		// #TODO pinned
+	}
+}
+
 void PinnedList::applyLimit(int limit) {
 	Expects(limit >= 0);
 
@@ -83,18 +101,32 @@ void PinnedList::applyList(
 		not_null<Data::Session*> owner,
 		const QVector<MTPDialogPeer> &list) {
 	clear();
-	for (const auto &peer : ranges::view::reverse(list)) {
+	for (const auto &peer : list) {
 		peer.match([&](const MTPDdialogPeer &data) {
 			if (const auto peerId = peerFromMTP(data.vpeer())) {
-				setPinned(owner->history(peerId), true);
+				addPinned(owner->history(peerId));
 			}
 		}, [&](const MTPDdialogPeerFolder &data) {
-			const auto folderId = data.vfolder_id().v;
-			setPinned(owner->folder(folderId), true);
+			addPinned(owner->folder(data.vfolder_id().v));
 		});
 	}
 }
 
+void PinnedList::applyList(const std::vector<not_null<History*>> &list) {
+	Expects(_filterId != 0);
+
+	clear();
+	const auto count = int(list.size());
+	_data.reserve(count);
+	for (auto i = 0; i != count; ++i) {
+		const auto history = list[i];
+		if (history->inChatList()) {
+			_data.emplace_back(history);
+			history->cachePinnedIndex(_filterId, i + 1);
+		}
+	}
+}
+
 void PinnedList::reorder(const Key &key1, const Key &key2) {
 	const auto index1 = ranges::find(_data, key1) - begin(_data);
 	const auto index2 = ranges::find(_data, key2) - begin(_data);
@@ -102,8 +134,8 @@ void PinnedList::reorder(const Key &key1, const Key &key2) {
 	Assert(index2 >= 0 && index2 < _data.size());
 	Assert(index1 != index2);
 	std::swap(_data[index1], _data[index2]);
-	key1.entry()->cachePinnedIndex(index2 + 1);
-	key2.entry()->cachePinnedIndex(index1 + 1);
+	key1.entry()->cachePinnedIndex(_filterId, index2 + 1);
+	key2.entry()->cachePinnedIndex(_filterId, index1 + 1);
 }
 
 } // namespace Dialogs
diff --git a/Telegram/SourceFiles/dialogs/dialogs_pinned_list.h b/Telegram/SourceFiles/dialogs/dialogs_pinned_list.h
index c7910fc2d..9e064da1a 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_pinned_list.h
+++ b/Telegram/SourceFiles/dialogs/dialogs_pinned_list.h
@@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #pragma once
 
+class History;
+
 namespace Data {
 class Session;
 } // namespace Data
@@ -17,7 +19,7 @@ class Key;
 
 class PinnedList final {
 public:
-	explicit PinnedList(int limit);
+	PinnedList(FilterId filterId, int limit);
 
 	void setLimit(int limit);
 
@@ -28,11 +30,17 @@ public:
 	// if (pinned) places on the first place in the list.
 	void setPinned(const Key &key, bool pinned);
 
+	void applyFilterPinned(
+		FilterId filterId,
+		not_null<History*> history,
+		int index);
+
 	void clear();
 
 	void applyList(
 		not_null<Data::Session*> owner,
 		const QVector<MTPDialogPeer> &list);
+	void applyList(const std::vector<not_null<History*>> &list);
 	void reorder(const Key &key1, const Key &key2);
 
 	const std::vector<Key> &order() const {
@@ -43,6 +51,7 @@ private:
 	int addPinnedGetPosition(const Key &key);
 	void applyLimit(int limit);
 
+	FilterId _filterId = 0;
 	int _limit = 0;
 	std::vector<Key> _data;
 
diff --git a/Telegram/SourceFiles/dialogs/dialogs_row.cpp b/Telegram/SourceFiles/dialogs/dialogs_row.cpp
index 00b31be44..f08568d9c 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_row.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_row.cpp
@@ -220,10 +220,8 @@ Row::Row(Key key, int pos) : _id(key), _pos(pos) {
 	}
 }
 
-uint64 Row::sortKey(SortMode mode) const {
-	return (mode == SortMode::Complex)
-		? _id.entry()->sortKeyInChatList()
-		: _id.entry()->sortKeyByDate();
+uint64 Row::sortKey(FilterId filterId) const {
+	return _id.entry()->sortKeyInChatList(filterId);
 }
 
 void Row::validateListEntryCache() const {
diff --git a/Telegram/SourceFiles/dialogs/dialogs_row.h b/Telegram/SourceFiles/dialogs/dialogs_row.h
index 8fc58f74a..b43b07af2 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_row.h
+++ b/Telegram/SourceFiles/dialogs/dialogs_row.h
@@ -90,7 +90,7 @@ public:
 	int pos() const {
 		return _pos;
 	}
-	uint64 sortKey(SortMode mode) const;
+	uint64 sortKey(FilterId filterId) const;
 
 	void validateListEntryCache() const;
 	const Ui::Text::String &listEntryCache() const {
diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp
index 0d9680c30..b6cf3ed74 100644
--- a/Telegram/SourceFiles/history/history.cpp
+++ b/Telegram/SourceFiles/history/history.cpp
@@ -1921,9 +1921,6 @@ void History::setFolderPointer(Data::Folder *folder) {
 	if (_folder == folder) {
 		return;
 	}
-	if (isPinnedDialog()) {
-		owner().setChatPinned(this, false);
-	}
 	auto &filters = owner().chatsFilters();
 	const auto wasKnown = folderKnown();
 	const auto wasInList = inChatList();
@@ -1946,6 +1943,7 @@ void History::setFolderPointer(Data::Folder *folder) {
 		for (const auto &filter : filters.list()) {
 			if (filter.contains(this)) {
 				const auto id = filter.id();
+				applyFilterPinnedIndex(id, filter);
 				addToChatList(id, filters.chatsList(id));
 			}
 		}
@@ -1959,6 +1957,24 @@ void History::setFolderPointer(Data::Folder *folder) {
 	}
 }
 
+void History::applyFilterPinnedIndex(
+		FilterId filterId,
+		const Data::ChatFilter &filter) {
+	const auto &pinned = filter.pinned();
+	const auto i = ranges::find(pinned, this);
+	if (i == end(pinned)) {
+		return;
+	}
+	const auto index = (i - begin(pinned)) + 1;
+	if (index == lookupPinnedIndex(filterId)) {
+		return;
+	}
+	owner().chatsFilters().chatsList(filterId)->pinned()->applyFilterPinned(
+		filterId,
+		this,
+		index);
+}
+
 void History::applyPinnedUpdate(const MTPDupdateDialogPinned &data) {
 	const auto folderId = data.vfolder_id().value_or_empty();
 	if (!folderKnown()) {
@@ -1968,7 +1984,7 @@ void History::applyPinnedUpdate(const MTPDupdateDialogPinned &data) {
 			clearFolder();
 		}
 	}
-	owner().setChatPinned(this, data.is_pinned());
+	owner().setChatPinned(this, FilterId(), data.is_pinned());
 }
 
 TimeId History::adjustedChatListTimeId() const {
@@ -2576,7 +2592,7 @@ bool History::useProxyPromotion() const {
 	if (!isProxyPromoted()) {
 		return false;
 	} else if (const auto channel = peer->asChannel()) {
-		return !isPinnedDialog() && !channel->amIn();
+		return !isPinnedDialog(FilterId()) && !channel->amIn();
 	}
 	return false;
 }
@@ -2595,7 +2611,7 @@ bool History::trackUnreadMessages() const {
 bool History::shouldBeInChatList() const {
 	if (peer->migrateTo() || !folderKnown()) {
 		return false;
-	} else if (isPinnedDialog()) {
+	} else if (isPinnedDialog(FilterId())) {
 		return true;
 	} else if (const auto channel = peer->asChannel()) {
 		if (!channel->amIn()) {
diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h
index 9c321efaf..2e6a06e40 100644
--- a/Telegram/SourceFiles/history/history.h
+++ b/Telegram/SourceFiles/history/history.h
@@ -31,6 +31,7 @@ namespace Data {
 struct Draft;
 class Session;
 class Folder;
+class ChatFilter;
 } // namespace Data
 
 namespace Dialogs {
@@ -368,6 +369,10 @@ public:
 		HistoryItem *folderDialogItem = nullptr);
 	void clearFolder();
 
+	void applyFilterPinnedIndex(
+		FilterId filterId,
+		const Data::ChatFilter &filter);
+
 	// Interface for Data::Histories.
 	void setInboxReadTill(MsgId upTo);
 	std::optional<int> countStillUnreadLocal(MsgId readTillId) const;
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 412f90afd..c184251d6 100644
--- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp
@@ -226,6 +226,7 @@ void TopBarWidget::showMenu() {
 		Window::FillPeerMenu(
 			_controller,
 			peer,
+			FilterId(),
 			addAction,
 			Window::PeerMenuSource::History);
 	} else if (const auto folder = _activeChat.folder()) {
diff --git a/Telegram/SourceFiles/info/info_wrap_widget.cpp b/Telegram/SourceFiles/info/info_wrap_widget.cpp
index 0e50252db..188ae0261 100644
--- a/Telegram/SourceFiles/info/info_wrap_widget.cpp
+++ b/Telegram/SourceFiles/info/info_wrap_widget.cpp
@@ -574,6 +574,7 @@ void WrapWidget::showTopBarMenu() {
 		Window::FillPeerMenu(
 			_controller->parentController(),
 			peer,
+			FilterId(),
 			addAction,
 			Window::PeerMenuSource::Profile);
 	//} else if (const auto feed = key().feed()) { // #feed
diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp
index fa85d2984..c268daa6d 100644
--- a/Telegram/SourceFiles/window/window_peer_menu.cpp
+++ b/Telegram/SourceFiles/window/window_peer_menu.cpp
@@ -64,6 +64,7 @@ public:
 	Filler(
 		not_null<SessionController*> controller,
 		not_null<PeerData*> peer,
+		FilterId filterId,
 		const PeerMenuCallback &addAction,
 		PeerMenuSource source);
 	void fill();
@@ -84,6 +85,7 @@ private:
 
 	not_null<SessionController*> _controller;
 	not_null<PeerData*> _peer;
+	FilterId _filterId = 0;
 	const PeerMenuCallback &_addAction;
 	PeerMenuSource _source;
 
@@ -115,7 +117,7 @@ private:
 };
 
 History *FindWastedPin(not_null<Data::Session*> data, Data::Folder *folder) {
-	const auto &order = data->pinnedChatsOrder(folder);
+	const auto &order = data->pinnedChatsOrder(folder, FilterId());
 	for (const auto &pinned : order) {
 		if (const auto history = pinned.history()) {
 			if (history->peer->isChat()
@@ -134,24 +136,25 @@ void AddChatMembers(
 	AddParticipantsBoxController::Start(navigation, chat);
 }
 
-bool PinnedLimitReached(Dialogs::Key key) {
+bool PinnedLimitReached(Dialogs::Key key, FilterId filterId) {
 	Expects(key.entry()->folderKnown());
 
 	const auto entry = key.entry();
 	const auto owner = &entry->owner();
 	const auto folder = entry->folder();
-	const auto pinnedCount = owner->pinnedChatsCount(folder);
-	const auto pinnedMax = owner->pinnedChatsLimit(folder);
+	const auto pinnedCount = owner->pinnedChatsCount(folder, filterId);
+	const auto pinnedMax = owner->pinnedChatsLimit(folder, filterId);
 	if (pinnedCount < pinnedMax) {
 		return false;
 	}
 	// Some old chat, that was converted, maybe is still pinned.
-	if (const auto wasted = FindWastedPin(owner, folder)) {
-		owner->setChatPinned(wasted, false);
-		owner->setChatPinned(key, true);
+	const auto wasted = filterId ? nullptr : FindWastedPin(owner, folder);
+	if (wasted) {
+		owner->setChatPinned(wasted, FilterId(), false);
+		owner->setChatPinned(key, FilterId(), true);
 		entry->session().api().savePinnedOrder(folder);
 	} else {
-		auto errorText = tr::lng_error_pinned_max(
+		const auto errorText = tr::lng_error_pinned_max(
 			tr::now,
 			lt_count,
 			pinnedMax);
@@ -165,12 +168,12 @@ void TogglePinnedDialog(Dialogs::Key key) {
 		return;
 	}
 	const auto owner = &key.entry()->owner();
-	const auto isPinned = !key.entry()->isPinnedDialog();
-	if (isPinned && PinnedLimitReached(key)) {
+	const auto isPinned = !key.entry()->isPinnedDialog(0);
+	if (isPinned && PinnedLimitReached(key, 0)) {
 		return;
 	}
 
-	owner->setChatPinned(key, isPinned);
+	owner->setChatPinned(key, FilterId(), isPinned);
 	const auto flags = isPinned
 		? MTPmessages_ToggleDialogPin::Flag::f_pinned
 		: MTPmessages_ToggleDialogPin::Flag(0);
@@ -194,13 +197,50 @@ void TogglePinnedDialog(Dialogs::Key key) {
 	}
 }
 
+void TogglePinnedDialog(Dialogs::Key key, FilterId filterId) {
+	if (!filterId) {
+		return TogglePinnedDialog(key);
+	}
+	const auto owner = &key.entry()->owner();
+	const auto isPinned = !key.entry()->isPinnedDialog(filterId);
+	if (isPinned && PinnedLimitReached(key, filterId)) {
+		return;
+	}
+
+	owner->setChatPinned(key, filterId, isPinned);
+	// #TODO pinned save data and to server
+	//const auto flags = isPinned
+	//	? MTPmessages_ToggleDialogPin::Flag::f_pinned
+	//	: MTPmessages_ToggleDialogPin::Flag(0);
+	//if (const auto history = key.history()) {
+	//	history->session().api().request(MTPmessages_ToggleDialogPin(
+	//		MTP_flags(flags),
+	//		MTP_inputDialogPeer(key.history()->peer->input)
+	//	)).done([=](const MTPBool &result) {
+	//		owner->notifyPinnedDialogsOrderUpdated();
+	//	}).send();
+	//} else if (const auto folder = key.folder()) {
+	//	folder->session().api().request(MTPmessages_ToggleDialogPin(
+	//		MTP_flags(flags),
+	//		MTP_inputDialogPeerFolder(MTP_int(folder->id()))
+	//	)).send();
+	//}
+	if (isPinned) {
+		if (const auto main = App::main()) {
+			main->dialogsToUp();
+		}
+	}
+}
+
 Filler::Filler(
 	not_null<SessionController*> controller,
 	not_null<PeerData*> peer,
+	FilterId filterId,
 	const PeerMenuCallback &addAction,
 	PeerMenuSource source)
 : _controller(controller)
 , _peer(peer)
+, _filterId(filterId)
 , _addAction(addAction)
 , _source(source) {
 }
@@ -242,27 +282,29 @@ bool Filler::showTogglePin() {
 }
 
 void Filler::addTogglePin() {
-	auto peer = _peer;
+	const auto filterId = _filterId;
+	const auto peer = _peer;
 	auto isPinned = false;
-	if (auto history = peer->owner().historyLoaded(peer)) {
-		isPinned = history->isPinnedDialog();
+	if (const auto history = peer->owner().historyLoaded(peer)) {
+		isPinned = history->isPinnedDialog(filterId);
 	}
-	auto pinText = [](bool isPinned) {
+	const auto pinText = [](bool isPinned) {
 		return isPinned
 			? tr::lng_context_unpin_from_top(tr::now)
 			: tr::lng_context_pin_to_top(tr::now);
 	};
-	auto pinToggle = [=] {
-		TogglePinnedDialog(peer->owner().history(peer));
+	const auto pinToggle = [=] {
+		TogglePinnedDialog(peer->owner().history(peer), filterId);
 	};
-	auto pinAction = _addAction(pinText(isPinned), pinToggle);
+	const auto pinAction = _addAction(pinText(isPinned), pinToggle);
 
 	const auto lifetime = Ui::CreateChild<rpl::lifetime>(pinAction);
 	Notify::PeerUpdateViewer(
 		peer,
 		Notify::PeerUpdate::Flag::ChatPinnedChanged
-	) | rpl::start_with_next([peer, pinAction, pinText] {
-		auto isPinned = peer->owner().history(peer)->isPinnedDialog();
+	) | rpl::start_with_next([=] {
+		const auto history = peer->owner().history(peer);
+		const auto isPinned = history->isPinnedDialog(filterId);
 		pinAction->setText(pinText(isPinned));
 	}, *lifetime);
 }
@@ -1041,9 +1083,10 @@ Fn<void()> DeleteAndLeaveHandler(not_null<PeerData*> peer) {
 void FillPeerMenu(
 		not_null<SessionController*> controller,
 		not_null<PeerData*> peer,
+		FilterId filterId,
 		const PeerMenuCallback &callback,
 		PeerMenuSource source) {
-	Filler filler(controller, peer, callback, source);
+	Filler filler(controller, peer, filterId, callback, source);
 	filler.fill();
 }
 
diff --git a/Telegram/SourceFiles/window/window_peer_menu.h b/Telegram/SourceFiles/window/window_peer_menu.h
index 2b2a96dd0..cfdeecd8c 100644
--- a/Telegram/SourceFiles/window/window_peer_menu.h
+++ b/Telegram/SourceFiles/window/window_peer_menu.h
@@ -39,6 +39,7 @@ using PeerMenuCallback = Fn<QAction*(
 void FillPeerMenu(
 	not_null<SessionController*> controller,
 	not_null<PeerData*> peer,
+	FilterId filterId,
 	const PeerMenuCallback &addAction,
 	PeerMenuSource source);
 void FillFolderMenu(