From 8fd811517b371fb31f820c1c3aed79390f5665fd Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Mon, 22 Apr 2019 18:22:39 +0400
Subject: [PATCH] Count unread correctly in folders.

---
 Telegram/SourceFiles/apiwrap.cpp              |  93 +++--
 Telegram/SourceFiles/apiwrap.h                |  18 +-
 .../boxes/peer_list_controllers.cpp           |   6 +-
 .../boxes/peers/add_participants_box.cpp      |   2 +-
 Telegram/SourceFiles/boxes/share_box.cpp      |   2 +-
 Telegram/SourceFiles/data/data_folder.cpp     | 379 +++++-------------
 Telegram/SourceFiles/data/data_folder.h       |  68 +---
 Telegram/SourceFiles/data/data_session.cpp    | 264 +++++-------
 Telegram/SourceFiles/data/data_session.h      |  52 +--
 .../SourceFiles/dialogs/dialogs_entry.cpp     |  24 +-
 Telegram/SourceFiles/dialogs/dialogs_entry.h  |  26 +-
 .../dialogs/dialogs_inner_widget.cpp          |  69 ++--
 Telegram/SourceFiles/dialogs/dialogs_list.h   |   2 +-
 .../SourceFiles/dialogs/dialogs_main_list.cpp | 130 ++++++
 .../SourceFiles/dialogs/dialogs_main_list.h   |  49 +++
 .../dialogs/dialogs_pinned_list.cpp           |   2 +-
 .../SourceFiles/dialogs/dialogs_pinned_list.h |   2 +-
 .../SourceFiles/dialogs/dialogs_widget.cpp    |   9 +-
 Telegram/SourceFiles/history/history.cpp      | 222 ++++------
 Telegram/SourceFiles/history/history.h        |   5 +-
 Telegram/SourceFiles/mainwidget.cpp           |  30 +-
 .../SourceFiles/window/window_peer_menu.cpp   |  15 +-
 Telegram/gyp/telegram_sources.txt             |   2 +
 23 files changed, 673 insertions(+), 798 deletions(-)
 create mode 100644 Telegram/SourceFiles/dialogs/dialogs_main_list.cpp
 create mode 100644 Telegram/SourceFiles/dialogs/dialogs_main_list.h

diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp
index 4887f0a8e..2f251066d 100644
--- a/Telegram/SourceFiles/apiwrap.cpp
+++ b/Telegram/SourceFiles/apiwrap.cpp
@@ -433,8 +433,8 @@ void ApiWrap::applyUpdates(
 	App::main()->feedUpdates(updates, sentMessageRandomId);
 }
 
-void ApiWrap::savePinnedOrder(FolderId folderId) {
-	const auto &order = _session->data().pinnedChatsOrder(folderId);
+void ApiWrap::savePinnedOrder(Data::Folder *folder) {
+	const auto &order = _session->data().pinnedChatsOrder(folder);
 	const auto input = [](const Dialogs::Key &key) {
 		if (const auto history = key.history()) {
 			return MTP_inputDialogPeer(history->peer->input);
@@ -451,7 +451,7 @@ void ApiWrap::savePinnedOrder(FolderId folderId) {
 		input);
 	request(MTPmessages_ReorderPinnedDialogs(
 		MTP_flags(MTPmessages_ReorderPinnedDialogs::Flag::f_force),
-		MTP_int(folderId),
+		MTP_int(folder ? folder->id() : 0),
 		MTP_vector(peers)
 	)).send();
 }
@@ -707,17 +707,17 @@ void ApiWrap::requestContacts() {
 	}).send();
 }
 
-void ApiWrap::requestDialogs(FolderId folderId) {
-	if (folderId && !_foldersLoadState.contains(folderId)) {
-		_foldersLoadState.emplace(folderId, DialogsLoadState());
+void ApiWrap::requestDialogs(Data::Folder *folder) {
+	if (folder && !_foldersLoadState.contains(folder)) {
+		_foldersLoadState.emplace(folder, DialogsLoadState());
 	}
-	requestMoreDialogs(folderId);
+	requestMoreDialogs(folder);
 }
 
-void ApiWrap::requestMoreDialogs(FolderId folderId) {
-	const auto state = dialogsLoadState(folderId);
+void ApiWrap::requestMoreDialogs(Data::Folder *folder) {
+	const auto state = dialogsLoadState(folder);
 	if (!state) {
-		if (!folderId) {
+		if (!folder) {
 			_session->data().addAllSavedPeers();
 		}
 		return;
@@ -734,7 +734,7 @@ void ApiWrap::requestMoreDialogs(FolderId folderId) {
 	const auto hash = 0;
 	state->requestId = request(MTPmessages_GetDialogs(
 		MTP_flags(flags),
-		MTP_int(folderId),
+		MTP_int(folder ? folder->id() : 0),
 		MTP_int(state->offsetDate),
 		MTP_int(state->offsetId),
 		(state->offsetPeer
@@ -743,31 +743,31 @@ void ApiWrap::requestMoreDialogs(FolderId folderId) {
 		MTP_int(loadCount),
 		MTP_int(hash)
 	)).done([=](const MTPmessages_Dialogs &result) {
-		const auto state = dialogsLoadState(folderId);
+		const auto state = dialogsLoadState(folder);
 		result.match([](const MTPDmessages_dialogsNotModified & data) {
 			LOG(("API Error: not-modified received for requested dialogs."));
 		}, [&](const auto &data) {
 			if constexpr (data.Is<MTPDmessages_dialogs>()) {
 				if (state) {
 					state->listReceived = true;
-					dialogsLoadFinish(folderId); // may kill 'state'.
+					dialogsLoadFinish(folder); // may kill 'state'.
 				}
 			} else {
 				updateDialogsOffset(
-					folderId,
+					folder,
 					data.vdialogs.v,
 					data.vmessages.v);
 			}
 			_session->data().processUsers(data.vusers);
 			_session->data().processChats(data.vchats);
 			_session->data().applyDialogs(
-				folderId,
+				folder,
 				data.vmessages.v,
 				data.vdialogs.v);
 		});
 
-		if (!folderId) {
-			requestDialogs(folderId);
+		if (!folder) {
+			requestDialogs(folder);
 			requestContacts();
 			if (!_dialogsLoadState
 				|| (!_dialogsLoadState->listReceived
@@ -775,15 +775,15 @@ void ApiWrap::requestMoreDialogs(FolderId folderId) {
 				refreshDialogsLoadBlocked();
 			}
 		}
-		_session->data().chatsListChanged(folderId);
+		_session->data().chatsListChanged(folder);
 	}).fail([=](const RPCError &error) {
-		dialogsLoadState(folderId)->requestId = 0;
+		dialogsLoadState(folder)->requestId = 0;
 	}).send();
 
 	if (!state->pinnedReceived) {
-		requestPinnedDialogs(folderId);
+		requestPinnedDialogs(folder);
 	}
-	if (!folderId) {
+	if (!folder) {
 		refreshDialogsLoadBlocked();
 	}
 }
@@ -801,7 +801,7 @@ void ApiWrap::refreshDialogsLoadBlocked() {
 }
 
 void ApiWrap::updateDialogsOffset(
-		FolderId folderId,
+		Data::Folder *folder,
 		const QVector<MTPDialog> &dialogs,
 		const QVector<MTPMessage> &messages) {
 	auto lastDate = TimeId(0);
@@ -834,7 +834,7 @@ void ApiWrap::updateDialogsOffset(
 			break;
 		}
 	}
-	if (const auto state = dialogsLoadState(folderId)) {
+	if (const auto state = dialogsLoadState(folder)) {
 		if (lastDate) {
 			state->offsetDate = lastDate;
 			state->offsetId = lastMsgId;
@@ -842,31 +842,31 @@ void ApiWrap::updateDialogsOffset(
 			state->requestId = 0;
 		} else {
 			state->listReceived = true;
-			dialogsLoadFinish(folderId);
+			dialogsLoadFinish(folder);
 		}
 	}
 }
 
-auto ApiWrap::dialogsLoadState(FolderId folderId) -> DialogsLoadState* {
-	if (!folderId) {
+auto ApiWrap::dialogsLoadState(Data::Folder *folder) -> DialogsLoadState* {
+	if (!folder) {
 		return _dialogsLoadState.get();
 	}
-	const auto i = _foldersLoadState.find(folderId);
+	const auto i = _foldersLoadState.find(folder);
 	return (i != end(_foldersLoadState)) ? &i->second : nullptr;
 }
 
-void ApiWrap::dialogsLoadFinish(FolderId folderId) {
+void ApiWrap::dialogsLoadFinish(Data::Folder *folder) {
 	const auto notify = [&] {
 		Core::App().postponeCall(crl::guard(_session, [=] {
-			_session->data().chatsListDone(folderId);
+			_session->data().chatsListDone(folder);
 		}));
 	};
-	const auto state = dialogsLoadState(folderId);
+	const auto state = dialogsLoadState(folder);
 	if (!state || !state->listReceived || !state->pinnedReceived) {
 		return;
 	}
-	if (folderId) {
-		_foldersLoadState.remove(folderId);
+	if (folder) {
+		_foldersLoadState.remove(folder);
 		notify();
 	} else {
 		_dialogsLoadState = nullptr;
@@ -874,32 +874,32 @@ void ApiWrap::dialogsLoadFinish(FolderId folderId) {
 	}
 }
 
-void ApiWrap::requestPinnedDialogs(FolderId folderId) {
-	const auto state = dialogsLoadState(folderId);
+void ApiWrap::requestPinnedDialogs(Data::Folder *folder) {
+	const auto state = dialogsLoadState(folder);
 	if (!state || state->pinnedReceived || state->pinnedRequestId) {
 		return;
 	}
 
 	const auto finalize = [=] {
-		if (const auto state = dialogsLoadState(folderId)) {
+		if (const auto state = dialogsLoadState(folder)) {
 			state->pinnedRequestId = 0;
 			state->pinnedReceived = true;
-			dialogsLoadFinish(folderId);
+			dialogsLoadFinish(folder);
 		}
 	};
 	state->pinnedRequestId = request(MTPmessages_GetPinnedDialogs(
-		MTP_int(folderId)
+		MTP_int(folder ? folder->id() : 0)
 	)).done([=](const MTPmessages_PeerDialogs &result) {
 		finalize();
 		result.match([&](const MTPDmessages_peerDialogs &data) {
 			_session->data().processUsers(data.vusers);
 			_session->data().processChats(data.vchats);
-			_session->data().clearPinnedChats(folderId);
+			_session->data().clearPinnedChats(folder);
 			_session->data().applyDialogs(
-				folderId,
+				folder,
 				data.vmessages.v,
 				data.vdialogs.v);
-			_session->data().chatsListChanged(folderId);
+			_session->data().chatsListChanged(folder);
 		});
 	}).fail([=](const RPCError &error) {
 		finalize();
@@ -1258,10 +1258,15 @@ void ApiWrap::gotChatFull(
 		channel->setInviteLink((f.vexported_invite.type() == mtpc_chatInviteExported) ? qs(f.vexported_invite.c_chatInviteExported().vlink) : QString());
 		if (const auto history = _session->data().historyLoaded(channel)) {
 			history->clearUpTill(f.vavailable_min_id.v);
-			history->applyDialogFields(
-				f.vunread_count.v,
-				f.vread_inbox_max_id.v,
-				f.vread_outbox_max_id.v);
+			if (history->folderKnown()) {
+				history->applyDialogFields(
+					history->folder(),
+					f.vunread_count.v,
+					f.vread_inbox_max_id.v,
+					f.vread_outbox_max_id.v);
+			} else {
+				requestDialogEntry(history);
+			}
 		}
 		if (f.has_pinned_msg_id()) {
 			channel->setPinnedMessageId(f.vpinned_msg_id.v);
diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h
index 6e03f71db..24315594a 100644
--- a/Telegram/SourceFiles/apiwrap.h
+++ b/Telegram/SourceFiles/apiwrap.h
@@ -64,7 +64,7 @@ public:
 
 	void applyUpdates(const MTPUpdates &updates, uint64 sentMessageRandomId = 0);
 
-	void savePinnedOrder(FolderId folderId);
+	void savePinnedOrder(Data::Folder *folder);
 	void toggleHistoryArchived(
 		not_null<History*> history,
 		bool archived,
@@ -79,8 +79,8 @@ public:
 	QString exportDirectMessageLink(not_null<HistoryItem*> item);
 
 	void requestContacts();
-	void requestDialogs(FolderId folderId);
-	void requestPinnedDialogs(FolderId folderId);
+	void requestDialogs(Data::Folder *folder = nullptr);
+	void requestPinnedDialogs(Data::Folder *folder = nullptr);
 	void requestMoreBlockedByDateDialogs();
 	rpl::producer<bool> dialogsLoadMayBlockByDate() const;
 	rpl::producer<bool> dialogsLoadBlockedByDate() const;
@@ -467,12 +467,12 @@ private:
 	void setupSupportMode();
 	void refreshDialogsLoadBlocked();
 	void updateDialogsOffset(
-		FolderId folderId,
+		Data::Folder *folder,
 		const QVector<MTPDialog> &dialogs,
 		const QVector<MTPMessage> &messages);
-	void requestMoreDialogs(FolderId folderId);
-	DialogsLoadState *dialogsLoadState(FolderId folderId);
-	void dialogsLoadFinish(FolderId folderId);
+	void requestMoreDialogs(Data::Folder *folder);
+	DialogsLoadState *dialogsLoadState(Data::Folder *folder);
+	void dialogsLoadFinish(Data::Folder *folder);
 
 	void checkQuitPreventFinished();
 
@@ -761,7 +761,9 @@ private:
 	rpl::variable<bool> _dialogsLoadMayBlockByDate = false;
 	rpl::variable<bool> _dialogsLoadBlockedByDate = false;
 
-	base::flat_map<FolderId, DialogsLoadState> _foldersLoadState;
+	base::flat_map<
+		not_null<Data::Folder*>,
+		DialogsLoadState> _foldersLoadState;
 
 	rpl::event_stream<SendOptions> _sendActions;
 
diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp
index 3535f5e37..1d017d2fb 100644
--- a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp
+++ b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp
@@ -19,7 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "mainwidget.h"
 #include "lang/lang_keys.h"
 #include "history/history.h"
-#include "dialogs/dialogs_indexed_list.h"
+#include "dialogs/dialogs_main_list.h"
 #include "styles/style_boxes.h"
 #include "styles/style_profile.h"
 
@@ -266,13 +266,13 @@ void ChatsListBoxController::rebuildRows() {
 			++added;
 		}
 	}
-	added += appendList(Auth().data().chatsList(Dialogs::Mode::All));
+	added += appendList(Auth().data().chatsList()->indexed());
 	added += appendList(Auth().data().contactsNoChatsList());
 	if (!wasEmpty && added > 0) {
 		// Place dialogs list before contactsNoDialogs list.
 		delegate()->peerListPartitionRows([](const PeerListRow &a) {
 			const auto history = static_cast<const Row&>(a).history();
-			return history->inChatList(Dialogs::Mode::All);
+			return history->inChatList();
 		});
 		if (respectSavedMessagesChat()) {
 			delegate()->peerListPartitionRows([](const PeerListRow &a) {
diff --git a/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp b/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp
index 8dad27891..e06904356 100644
--- a/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp
+++ b/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp
@@ -1080,7 +1080,7 @@ void AddSpecialBoxSearchController::addChatsContacts() {
 		return result;
 	};
 	const auto dialogsIndex = getSmallestIndex(
-		_peer->owner().chatsList(Dialogs::Mode::All));
+		_peer->owner().chatsList()->indexed());
 	const auto contactsIndex = getSmallestIndex(
 		_peer->owner().contactsNoChatsList());
 
diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp
index 24e714374..1257cdcfd 100644
--- a/Telegram/SourceFiles/boxes/share_box.cpp
+++ b/Telegram/SourceFiles/boxes/share_box.cpp
@@ -489,7 +489,7 @@ ShareBox::Inner::Inner(
 	_rowHeight = st::shareRowHeight;
 	setAttribute(Qt::WA_OpaquePaintEvent);
 
-	const auto dialogs = Auth().data().chatsList(Dialogs::Mode::All);
+	const auto dialogs = Auth().data().chatsList()->indexed();
 	const auto self = Auth().user();
 	if (_filterCallback(self)) {
 		_chatsIndexed->addToEnd(self->owner().history(self));
diff --git a/Telegram/SourceFiles/data/data_folder.cpp b/Telegram/SourceFiles/data/data_folder.cpp
index a1c546ef9..2fa038f8a 100644
--- a/Telegram/SourceFiles/data/data_folder.cpp
+++ b/Telegram/SourceFiles/data/data_folder.cpp
@@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "history/history_item.h"
 #include "lang/lang_keys.h"
 #include "storage/storage_facade.h"
+#include "core/application.h"
 //#include "storage/storage_feed_messages.h" // #feed
 #include "auth_session.h"
 #include "apiwrap.h"
@@ -25,6 +26,16 @@ namespace {
 
 constexpr auto kLoadedChatsMinCount = 20;
 
+rpl::producer<int> PinnedDialogsInFolderMaxValue() {
+	return rpl::single(
+		rpl::empty_value()
+	) | rpl::then(
+		Core::App().configUpdates()
+	) | rpl::map([=] {
+		return Global::PinnedDialogsInFolderMax();
+	});
+}
+
 } // namespace
 
 // #feed
@@ -40,9 +51,7 @@ constexpr auto kLoadedChatsMinCount = 20;
 Folder::Folder(not_null<Data::Session*> owner, FolderId id)
 : Entry(owner, this)
 , _id(id)
-, _chatsList(Dialogs::SortMode::Date)
-, _importantChatsList(Dialogs::SortMode::Date)
-, _pinnedChatsList(Global::PinnedDialogsInFolderMax())
+, _chatsList(PinnedDialogsInFolderMaxValue())
 , _name(lang(lng_archived_chats)) {
 	indexNameParts();
 }
@@ -78,51 +87,19 @@ void Folder::indexNameParts() {
 }
 
 void Folder::registerOne(not_null<History*> history) {
-	//session().storage().invalidate( // #feed
-	//	Storage::FeedMessagesInvalidate(_id));
-
-	if (unreadCountKnown()) {
-		if (history->unreadCountKnown()) {
-			// If history unreadCount is known that means that we've
-			// already had the channel information and if it was in the
-			// feed already (not yet known) it wouldn't get here.
-			// That means here we get if we add a new channel to feed.
-			if (const auto count = history->unreadCount()) {
-				unreadCountChanged(count, history->mute() ? count : 0);
-			}
-		} else {
-			session().api().requestDialogEntry(this);
-		}
-	}
-	if (_chatsList.size() == 1) {
+	if (_chatsList.indexed()->size() == 1) {
 		updateChatListSortPosition();
 	}
-	owner().notifyFolderUpdated(this, FolderUpdateFlag::List);
 }
 
 void Folder::unregisterOne(not_null<History*> history) {
-	//session().storage().remove( // #feed
-	//	Storage::FeedMessagesRemoveAll(_id, channel->bareId()));
-
-	if (unreadCountKnown()) {
-		if (history->unreadCountKnown()) {
-			if (const auto delta = -history->unreadCount()) {
-				unreadCountChanged(delta, history->mute() ? delta : 0);
-			}
-		} else {
-			session().api().requestDialogEntry(this);
-		}
-	}
 	if (_chatsList.empty()) {
 		updateChatListExistence();
 	}
-	owner().notifyFolderUpdated(this, FolderUpdateFlag::List);
 }
 
-not_null<Dialogs::IndexedList*> Folder::chatsList(Dialogs::Mode list) {
-	return (list == Dialogs::Mode::All)
-		? &_chatsList
-		: &_importantChatsList;
+not_null<Dialogs::MainList*> Folder::chatsList() {
+	return &_chatsList;
 }
 
 void Folder::loadUserpic() {
@@ -163,71 +140,16 @@ void Folder::paintUserpic(
 }
 
 bool Folder::chatsListLoaded() const {
-	return _chatsListLoaded;
+	return _chatsList.loaded();
 }
 
 void Folder::setChatsListLoaded(bool loaded) {
-	if (_chatsListLoaded != loaded) {
-		_chatsListLoaded = loaded;
-		owner().notifyFolderUpdated(this, FolderUpdateFlag::List);
+	if (_chatsList.loaded() == loaded) {
+		return;
 	}
+	const auto notifier = unreadStateChangeNotifier(true);
+	_chatsList.setLoaded(loaded);
 }
-// // #feed
-//int32 Folder::chatsHash() const {
-//	const auto ordered = ranges::view::all(
-//		_histories
-//	) | ranges::view::transform([](not_null<History*> history) {
-//		return history->peer->bareId();
-//	}) | ranges::to_vector | ranges::action::sort;
-//	return Api::CountHash(ordered);
-//}
-//
-//void Folder::setChats(std::vector<not_null<PeerData*>> chats) {
-//	const auto remove = ranges::view::all(
-//		_histories
-//	) | ranges::view::transform([](not_null<History*> history) {
-//		return history->peer;
-//	}) | ranges::view::filter([&](not_null<PeerData*> peer) {
-//		return !base::contains(chats, peer);
-//	}) | ranges::to_vector;
-//
-//	const auto add = ranges::view::all(
-//		chats
-//	) | ranges::view::filter([&](not_null<PeerData*> peer) {
-//		return ranges::find(
-//			_histories,
-//			peer,
-//			[](auto history) { return history->peer; }
-//		) == end(_histories);
-//	}) | ranges::view::transform([](PeerData *peer) {
-//		return not_null<PeerData*>(peer);
-//	}) | ranges::to_vector;
-//
-//	changeChatsList(add, remove);
-//
-//	setChatsLoaded(true);
-//}
-//
-//void Folder::changeChatsList(
-//		const std::vector<not_null<PeerData*>> &add,
-//		const std::vector<not_null<PeerData*>> &remove) {
-//	_settingChats = true;
-//	const auto restore = gsl::finally([&] { _settingChats = false; });
-//
-//	for (const auto channel : remove) {
-//		channel->clearFeed();
-//	}
-//
-//	//// We assume the last message was correct before requesting the list.
-//	//// So we save it and don't allow channels from the list to change it.
-//	//// After that we restore it.
-//	const auto oldChatListMessage = base::take(_chatListMessage);
-//	for (const auto channel : add) {
-//		_chatListMessage = std::nullopt;
-//		channel->setFeed(this);
-//	}
-//	_chatListMessage = oldChatListMessage;
-//}
 
 void Folder::requestChatListMessage() {
 	if (!chatListMessageKnown()) {
@@ -235,66 +157,14 @@ void Folder::requestChatListMessage() {
 	}
 }
 
-void Folder::setPinnedChatsLimit(int limit) {
-	_pinnedChatsList.setLimit(limit);
-}
-
-void Folder::setChatPinned(const Dialogs::Key &key, bool pinned) {
-	_pinnedChatsList.setPinned(key, pinned);
-}
-
-void Folder::addPinnedChat(const Dialogs::Key &key) {
-	_pinnedChatsList.addPinned(key);
-}
-
-void Folder::applyPinnedChats(const QVector<MTPDialogPeer> &list) {
-	_pinnedChatsList.applyList(&owner(), list);
-}
-
-const std::vector<Dialogs::Key> &Folder::pinnedChatsOrder() const {
-	return _pinnedChatsList.order();
-}
-
-void Folder::clearPinnedChats() {
-	_pinnedChatsList.clear();
-}
-
-void Folder::reorderTwoPinnedChats(
-		const Dialogs::Key &key1,
-		const Dialogs::Key &key2) {
-	_pinnedChatsList.reorder(key1, key2);
-}
-
 TimeId Folder::adjustedChatListTimeId() const {
-	return _chatsList.empty()
+	const auto list = _chatsList.indexed();
+	return list->empty()
 		? TimeId(0)
-		: (*_chatsList.begin())->entry()->adjustedChatListTimeId();
-}
-
-int Folder::unreadCount() const {
-	return _unreadCount.value_or(0);
-}
-
-rpl::producer<int> Folder::unreadCountValue() const {
-	return rpl::single(
-		unreadCount()
-	) | rpl::then(_unreadCountChanges.events());
-}
-
-bool Folder::unreadCountKnown() const {
-	return !!_unreadCount;
+		: (*list->begin())->entry()->adjustedChatListTimeId();
 }
 
 void Folder::applyDialog(const MTPDdialogFolder &data) {
-	//const auto addChannel = [&](ChannelId channelId) { // #feed
-	//	if (const auto channel = owner().channelLoaded(channelId)) {
-	//		channel->setFeed(this);
-	//	}
-	//};
-	//for (const auto &channelId : data.vfeed_other_channels.v) {
-	//	addChannel(channelId.v);
-	//}
-
 	if (const auto peerId = peerFromMTP(data.vpeer)) {
 		const auto history = owner().history(peerId);
 		const auto fullId = FullMsgId(
@@ -303,24 +173,36 @@ void Folder::applyDialog(const MTPDdialogFolder &data) {
 		history->setFolder(this, App::histItemById(fullId));
 	} else {
 		_chatsList.clear();
-		_importantChatsList.clear();
 		updateChatListExistence();
 	}
-	setUnreadCounts(
-		data.vunread_unmuted_messages_count.v,
-		data.vunread_muted_messages_count.v);
-	//setUnreadMark(data.is_unread_mark());
-	//setUnreadMentionsCount(data.vunread_mentions_count.v);
-
-	//if (data.has_read_max_position()) { // #feed
-	//	setUnreadPosition(FeedPositionFromMTP(data.vread_max_position));
-	//}
-
-	if (_chatsList.size() < kLoadedChatsMinCount) {
-		session().api().requestDialogs(_id);
+	updateCloudUnread(data);
+	if (_chatsList.indexed()->size() < kLoadedChatsMinCount) {
+		session().api().requestDialogs(this);
 	}
 }
 
+void Folder::updateCloudUnread(const MTPDdialogFolder &data) {
+	const auto notifier = unreadStateChangeNotifier(!_chatsList.loaded());
+
+	_cloudUnread.messagesCountMuted = data.vunread_muted_messages_count.v;
+	_cloudUnread.messagesCount = _cloudUnread.messagesCountMuted
+		+ data.vunread_unmuted_messages_count.v;
+	_cloudUnread.chatsCountMuted = data.vunread_muted_peers_count.v;
+	_cloudUnread.chatsCount = _cloudUnread.chatsCountMuted
+		+ data.vunread_unmuted_peers_count.v;
+}
+
+Dialogs::UnreadState Folder::chatListUnreadState() const {
+	const auto state = _chatsList.loaded()
+		? _chatsList.unreadState()
+		: _cloudUnread;
+	auto result = Dialogs::UnreadState();
+	result.messagesCount = state.messagesCount;
+	result.messagesCountMuted = result.messagesCount.value_or(0);
+	result.chatsCount = result.chatsCountMuted = state.chatsCount;
+	return result;
+}
+
 void Folder::applyPinnedUpdate(const MTPDupdateDialogPinned &data) {
 	const auto folderId = data.has_folder_id() ? data.vfolder_id.v : 0;
 	if (folderId != 0) {
@@ -329,129 +211,50 @@ void Folder::applyPinnedUpdate(const MTPDupdateDialogPinned &data) {
 	owner().setChatPinned(this, data.is_pinned());
 }
 
-void Folder::changedInChatListHook(Dialogs::Mode list, bool added) {
-	if (list != Dialogs::Mode::All) {
+void Folder::unreadStateChanged(
+		const Dialogs::UnreadState &wasState,
+		const Dialogs::UnreadState &nowState) {
+	const auto updateCloudUnread = _cloudUnread.messagesCount.has_value()
+		&& wasState.messagesCount.has_value();
+	const auto notify = _chatsList.loaded() || updateCloudUnread;
+	const auto notifier = unreadStateChangeNotifier(notify);
+
+	_chatsList.unreadStateChanged(wasState, nowState);
+	if (!_cloudUnread.messagesCount.has_value()
+		|| !wasState.messagesCount.has_value()) {
 		return;
 	}
-	if (const auto count = unreadCount()) {
-		const auto mutedCount = _unreadMutedCount;
-		const auto nonMutedCount = count - mutedCount;
-		const auto mutedDelta = added ? mutedCount : -mutedCount;
-		const auto nonMutedDelta = added ? nonMutedCount : -nonMutedCount;
-		owner().unreadIncrement(nonMutedDelta, false);
-		owner().unreadIncrement(mutedDelta, true);
+	Assert(nowState.messagesCount.has_value());
 
-		const auto fullMuted = (nonMutedCount == 0);
-		const auto entriesWithUnreadDelta = added ? 1 : -1;
-		const auto mutedEntriesWithUnreadDelta = fullMuted
-			? entriesWithUnreadDelta
-			: 0;
-		owner().unreadEntriesChanged(
-			entriesWithUnreadDelta,
-			mutedEntriesWithUnreadDelta);
-	}
+	*_cloudUnread.messagesCount += *nowState.messagesCount
+		- *wasState.messagesCount;
+	_cloudUnread.messagesCountMuted += nowState.messagesCountMuted
+		- wasState.messagesCountMuted;
+	_cloudUnread.chatsCount += nowState.chatsCount - wasState.chatsCount;
+	_cloudUnread.chatsCountMuted += nowState.chatsCountMuted
+		- wasState.chatsCountMuted;
 }
 
-template <typename PerformUpdate>
-void Folder::updateUnreadCounts(PerformUpdate &&performUpdate) {
-	const auto wasUnreadCount = _unreadCount  ? *_unreadCount : 0;
-	const auto wasUnreadMutedCount = _unreadMutedCount;
-	const auto wasFullMuted = (wasUnreadMutedCount > 0)
-		&& (wasUnreadCount == wasUnreadMutedCount);
+void Folder::unreadEntryChanged(
+		const Dialogs::UnreadState &state,
+		bool added) {
+	const auto updateCloudUnread = _cloudUnread.messagesCount.has_value()
+		&& state.messagesCount.has_value();
+	const auto notify = _chatsList.loaded() || updateCloudUnread;
+	const auto notifier = unreadStateChangeNotifier(notify);
 
-	performUpdate();
-	Assert(_unreadCount.has_value());
-
-	_unreadCountChanges.fire(unreadCount());
-	updateChatListEntry();
-
-	if (inChatList(Dialogs::Mode::All)) {
-		const auto nowUnreadCount = *_unreadCount;
-		const auto nowUnreadMutedCount = _unreadMutedCount;
-		const auto nowFullMuted = (nowUnreadMutedCount > 0)
-			&& (nowUnreadCount == nowUnreadMutedCount);
-
-		owner().unreadIncrement(
-			(nowUnreadCount - nowUnreadMutedCount)
-			- (wasUnreadCount - wasUnreadMutedCount),
-			false);
-		owner().unreadIncrement(
-			nowUnreadMutedCount - wasUnreadMutedCount,
-			true);
-
-		const auto entriesDelta = (nowUnreadCount && !wasUnreadCount)
-			? 1
-			: (wasUnreadCount && !nowUnreadCount)
-			? -1
-			: 0;
-		const auto mutedEntriesDelta = (!wasFullMuted && nowFullMuted)
-			? 1
-			: (wasFullMuted && !nowFullMuted)
-			? -1
-			: 0;
-		owner().unreadEntriesChanged(
-			entriesDelta,
-			mutedEntriesDelta);
-	}
-}
-
-void Folder::setUnreadCounts(int unreadNonMutedCount, int unreadMutedCount) {
-	if (unreadCountKnown()
-		&& (*_unreadCount == unreadNonMutedCount + unreadMutedCount)
-		&& (_unreadMutedCount == unreadMutedCount)) {
+	_chatsList.unreadEntryChanged(state, added);
+	if (!_cloudUnread.messagesCount.has_value()
+		|| !state.messagesCount.has_value()) {
 		return;
 	}
-	updateUnreadCounts([&] {
-		_unreadCount = unreadNonMutedCount + unreadMutedCount;
-		_unreadMutedCount = unreadMutedCount;
-	});
+	const auto delta = (added ? 1 : -1);
+	*_cloudUnread.messagesCount += delta * *state.messagesCount;
+	_cloudUnread.messagesCountMuted += delta * state.messagesCountMuted;
+	_cloudUnread.chatsCount += delta * state.chatsCount;
+	_cloudUnread.chatsCountMuted += delta * state.chatsCountMuted;
 }
-// #feed
-//void Folder::setUnreadPosition(const MessagePosition &position) {
-//	if (_unreadPosition.current() < position) {
-//		_unreadPosition = position;
-//	}
-//}
 
-void Folder::unreadCountChanged(int unreadCountDelta, int mutedCountDelta) {
-	if (!unreadCountKnown()) {
-		return;
-	}
-	updateUnreadCounts([&] {
-		accumulate_max(unreadCountDelta, -*_unreadCount);
-		*_unreadCount += unreadCountDelta;
-
-		mutedCountDelta = snap(
-			mutedCountDelta,
-			-_unreadMutedCount,
-			*_unreadCount - _unreadMutedCount);
-		_unreadMutedCount += mutedCountDelta;
-	});
-}
-//
-//void Folder::setUnreadMark(bool unread) {
-//	if (_unreadMark != unread) {
-//		_unreadMark = unread;
-//		if (!_unreadCount || !*_unreadCount) {
-//			if (inChatList(Dialogs::Mode::All)) {
-//				const auto delta = _unreadMark ? 1 : -1;
-//				owner().unreadIncrement(delta, mute());
-//				owner().unreadEntriesChanged(
-//					delta,
-//					mute() ? delta : 0);
-//
-//				updateChatListEntry();
-//			}
-//		}
-//		Notify::peerUpdatedDelayed(
-//			peer,
-//			Notify::PeerUpdate::Flag::UnreadViewChanged);
-//	}
-//}
-//
-//bool Folder::unreadMark() const {
-//	return _unreadMark;
-//}
 // #feed
 //MessagePosition Folder::unreadPosition() const {
 //	return _unreadPosition.current();
@@ -462,7 +265,7 @@ void Folder::unreadCountChanged(int unreadCountDelta, int mutedCountDelta) {
 //}
 
 bool Folder::toImportant() const {
-	return !_importantChatsList.empty();
+	return false;
 }
 
 int Folder::fixedOnTopIndex() const {
@@ -474,7 +277,9 @@ bool Folder::shouldBeInChatList() const {
 }
 
 int Folder::chatListUnreadCount() const {
-	return unreadCount();
+	return session().settings().countUnreadMessages()
+		? chatListUnreadState().messagesCount.value_or(0)
+		: chatListUnreadState().chatsCount;
 }
 
 bool Folder::chatListUnreadMark() const {
@@ -482,18 +287,20 @@ bool Folder::chatListUnreadMark() const {
 }
 
 bool Folder::chatListMutedBadge() const {
-	return _unreadCount ? (*_unreadCount <= _unreadMutedCount) : false;
+	return true;
 }
 
 HistoryItem *Folder::chatListMessage() const {
-	return _chatsList.empty()
+	const auto list = _chatsList.indexed();
+	return list->empty()
 		? nullptr
-		: (*_chatsList.begin())->key().entry()->chatListMessage();
+		: (*list->begin())->key().entry()->chatListMessage();
 }
 
 bool Folder::chatListMessageKnown() const {
-	return _chatsList.empty()
-		|| (*_chatsList.begin())->key().entry()->chatListMessageKnown();
+	const auto list = _chatsList.indexed();
+	return list->empty()
+		|| (*list->begin())->key().entry()->chatListMessageKnown();
 }
 
 const QString &Folder::chatListName() const {
diff --git a/Telegram/SourceFiles/data/data_folder.h b/Telegram/SourceFiles/data/data_folder.h
index a734006fa..01f146d44 100644
--- a/Telegram/SourceFiles/data/data_folder.h
+++ b/Telegram/SourceFiles/data/data_folder.h
@@ -8,8 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #pragma once
 
 #include "dialogs/dialogs_entry.h"
-#include "dialogs/dialogs_indexed_list.h"
-#include "dialogs/dialogs_pinned_list.h"
+#include "dialogs/dialogs_main_list.h"
 #include "data/data_messages.h"
 
 class ChannelData;
@@ -20,15 +19,6 @@ namespace Data {
 class Session;
 class Folder;
 
-enum class FolderUpdateFlag {
-	List,
-};
-
-struct FolderUpdate {
-	not_null<Data::Folder*> folder;
-	FolderUpdateFlag flag;
-};
-
 //MessagePosition FeedPositionFromMTP(const MTPFeedPosition &position); // #feed
 
 class Folder final : public Dialogs::Entry {
@@ -43,43 +33,21 @@ public:
 	void registerOne(not_null<History*> history);
 	void unregisterOne(not_null<History*> history);
 
-	not_null<Dialogs::IndexedList*> chatsList(Dialogs::Mode list);
+	not_null<Dialogs::MainList*> chatsList();
 
 	void applyDialog(const MTPDdialogFolder &data);
 	void applyPinnedUpdate(const MTPDupdateDialogPinned &data);
 
-	void setUnreadCounts(int unreadNonMutedCount, int unreadMutedCount);
-	//void setUnreadPosition(const MessagePosition &position); // #feed
-	void unreadCountChanged(
-		int unreadCountDelta,
-		int mutedCountDelta);
-	rpl::producer<int> unreadCountValue() const;
 	//MessagePosition unreadPosition() const; // #feed
 	//rpl::producer<MessagePosition> unreadPositionChanges() const; // #feed
 
-	//void setUnreadMark(bool unread);
-	//bool unreadMark() const;
-	//int unreadCountForBadge() const; // unreadCount || unreadMark ? 1 : 0.
-
-	void setPinnedChatsLimit(int limit);
-
-	// Places on the last place in the list otherwise.
-	// Does nothing if already pinned.
-	void addPinnedChat(const Dialogs::Key &key);
-
-	// if (pinned) places on the first place in the list.
-	void setChatPinned(const Dialogs::Key &key, bool pinned);
-
-	void applyPinnedChats(const QVector<MTPDialogPeer> &list);
-	const std::vector<Dialogs::Key> &pinnedChatsOrder() const;
-	void clearPinnedChats();
-	void reorderTwoPinnedChats(
-		const Dialogs::Key &key1,
-		const Dialogs::Key &key2);
+	void updateCloudUnread(const MTPDdialogFolder &data);
+	void unreadStateChanged(
+		const Dialogs::UnreadState &wasState,
+		const Dialogs::UnreadState &nowState);
+	void unreadEntryChanged(const Dialogs::UnreadState &state, bool added);
 
 	TimeId adjustedChatListTimeId() const override;
-	int unreadCount() const;
-	bool unreadCountKnown() const;
 
 	int fixedOnTopIndex() const override;
 	bool toImportant() const override;
@@ -87,13 +55,13 @@ public:
 	int chatListUnreadCount() const override;
 	bool chatListUnreadMark() const override;
 	bool chatListMutedBadge() const override;
+	Dialogs::UnreadState chatListUnreadState() const override;
 	HistoryItem *chatListMessage() const override;
 	bool chatListMessageKnown() const override;
 	void requestChatListMessage() override;
 	const QString &chatListName() const override;
 	const base::flat_set<QString> &chatListNameWords() const override;
 	const base::flat_set<QChar> &chatListFirstLetters() const override;
-	void changedInChatListHook(Dialogs::Mode list, bool added) override;
 
 	void loadUserpic() override;
 	void paintUserpic(
@@ -103,34 +71,20 @@ public:
 		int size) const override;
 
 	bool chatsListLoaded() const;
-	void setChatsListLoaded(bool loaded);
-	//int32 chatsHash() const;
-	//void setChats(std::vector<not_null<PeerData*>> chats); // #feed
+	void setChatsListLoaded(bool loaded = true);
 
 private:
 	void indexNameParts();
-	//void changeChatsList(
-	//	const std::vector<not_null<PeerData*>> &add,
-	//	const std::vector<not_null<PeerData*>> &remove);
-
-	template <typename PerformUpdate>
-	void updateUnreadCounts(PerformUpdate &&performUpdate);
 
 	FolderId _id = 0;
-	Dialogs::IndexedList _chatsList;
-	Dialogs::IndexedList _importantChatsList;
-	Dialogs::PinnedList _pinnedChatsList;
-	bool _chatsListLoaded = false;
+	Dialogs::MainList _chatsList;
 
 	QString _name;
 	base::flat_set<QString> _nameWords;
 	base::flat_set<QChar> _nameFirstLetters;
 
+	Dialogs::UnreadState _cloudUnread;
 	//rpl::variable<MessagePosition> _unreadPosition;
-	std::optional<int> _unreadCount;
-	rpl::event_stream<int> _unreadCountChanges;
-	int _unreadMutedCount = 0;
-	//bool _unreadMark = false;
 
 };
 
diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp
index 48d5ca19e..b22367e58 100644
--- a/Telegram/SourceFiles/data/data_session.cpp
+++ b/Telegram/SourceFiles/data/data_session.cpp
@@ -140,6 +140,16 @@ MTPPhotoSize FindDocumentThumbnail(const MTPDdocument &data) {
 		: MTPPhotoSize(MTP_photoSizeEmpty(MTP_string("")));
 }
 
+rpl::producer<int> PinnedDialogsCountMaxValue() {
+	return rpl::single(
+		rpl::empty_value()
+	) | rpl::then(
+		Core::App().configUpdates()
+	) | rpl::map([=] {
+		return Global::PinnedDialogsCountMax();
+	});
+}
+
 } // namespace
 
 Session::Session(not_null<AuthSession*> session)
@@ -150,11 +160,9 @@ Session::Session(not_null<AuthSession*> session)
 , _bigFileCache(Core::App().databases().get(
 	Local::cacheBigFilePath(),
 	Local::cacheBigFileSettings()))
-, _chatsList(Dialogs::SortMode::Date)
-, _importantChatsList(Dialogs::SortMode::Date)
+, _chatsList(PinnedDialogsCountMaxValue())
 , _contactsList(Dialogs::SortMode::Name)
 , _contactsNoChatsList(Dialogs::SortMode::Name)
-, _pinnedChatsList(Global::PinnedDialogsCountMax())
 , _selfDestructTimer([=] { checkSelfDestructItems(); })
 , _sendActionsAnimation([=](crl::time now) {
 	return sendActionsAnimationCallback(now);
@@ -168,14 +176,6 @@ Session::Session(not_null<AuthSession*> session)
 	setupChannelLeavingViewer();
 	setupPeerNameViewer();
 	setupUserIsContactViewer();
-
-	Core::App().configUpdates(
-	) | rpl::start_with_next([=] {
-		_pinnedChatsList.setLimit(Global::PinnedDialogsCountMax());
-		for (const auto &[folderId, folder] : _folders) {
-			folder->setPinnedChatsLimit(Global::PinnedDialogsInFolderMax());
-		}
-	}, _lifetime);
 }
 
 void Session::clear() {
@@ -786,7 +786,7 @@ bool Session::sendActionsAnimationCallback(crl::time now) {
 }
 
 bool Session::chatsListLoaded(Data::Folder *folder) {
-	return folder ? folder->chatsListLoaded() : _chatsListLoaded;
+	return chatsList(folder)->loaded();
 }
 
 void Session::chatsListChanged(FolderId folderId) {
@@ -797,12 +797,11 @@ void Session::chatsListChanged(Data::Folder *folder) {
 	_chatsListChanged.fire_copy(folder);
 }
 
-void Session::chatsListDone(FolderId folderId) {
-	const auto folder = folderId ? this->folder(folderId).get() : nullptr;
+void Session::chatsListDone(Data::Folder *folder) {
 	if (folder) {
-		folder->setChatsListLoaded(true);
+		folder->setChatsListLoaded();
 	} else {
-		_chatsListLoaded = true;
+		_chatsList.setLoaded();
 	}
 	_chatsListLoadedEvents.fire_copy(folder);
 }
@@ -964,13 +963,6 @@ void Session::setupPeerNameViewer() {
 	) | rpl::start_with_next([=](const Notify::PeerUpdate &update) {
 		const auto peer = update.peer;
 		const auto &oldLetters = update.oldNameFirstLetters;
-		_chatsList.peerNameChanged(Dialogs::Mode::All, peer, oldLetters);
-		if (Global::DialogsModeEnabled()) {
-			_importantChatsList.peerNameChanged(
-				Dialogs::Mode::Important,
-				peer,
-				oldLetters);
-		}
 		_contactsNoChatsList.peerNameChanged(peer, oldLetters);
 		_contactsList.peerNameChanged(peer, oldLetters);
 	}, _lifetime);
@@ -991,7 +983,7 @@ void Session::setupUserIsContactViewer() {
 		if (user->contactStatus() == UserData::ContactStatus::Contact) {
 			const auto history = user->owner().history(user->id);
 			_contactsList.addByName(history);
-			if (!_chatsList.contains(history)) {
+			if (!_chatsList.indexed()->contains(history)) {
 				_contactsNoChatsList.addByName(history);
 			}
 		} else if (const auto history = user->owner().historyLoaded(user)) {
@@ -1288,16 +1280,6 @@ rpl::producer<not_null<UserData*>> Session::megagroupParticipantAdded(
 	});
 }
 
-void Session::notifyFolderUpdated(
-		not_null<Folder*> folder,
-		FolderUpdateFlag update) {
-	_folderUpdates.fire({ folder, update });
-}
-
-rpl::producer<FolderUpdate> Session::folderUpdated() const {
-	return _folderUpdates.events();
-}
-
 void Session::notifyStickersUpdated() {
 	_stickersUpdated.fire({});
 }
@@ -1355,33 +1337,24 @@ MessageIdsList Session::itemOrItsGroup(not_null<HistoryItem*> item) const {
 void Session::setChatPinned(const Dialogs::Key &key, bool pinned) {
 	Expects(key.entry()->folderKnown());
 
-	if (const auto folder = key.entry()->folder()) {
-		folder->setChatPinned(key, pinned);
-	} else {
-		_pinnedChatsList.setPinned(key, pinned);
-	}
+	const auto list = chatsList(key.entry()->folder())->pinned();
+	list->setPinned(key, pinned);
 }
 
 void Session::setPinnedFromDialog(const Dialogs::Key &key, bool pinned) {
 	Expects(key.entry()->folderKnown());
 
-	if (const auto folder = key.entry()->folder()) {
-		if (pinned) {
-			folder->addPinnedChat(key);
-		} else {
-			folder->setChatPinned(key, false);
-		}
-	} else if (pinned) {
-		_pinnedChatsList.addPinned(key);
+	const auto list = chatsList(key.entry()->folder())->pinned();
+	if (pinned) {
+		list->addPinned(key);
 	} else {
-		_pinnedChatsList.setPinned(key, false);
+		list->setPinned(key, false);
 	}
 }
 
 void Session::applyPinnedChats(
-		FolderId folderId,
+		Data::Folder *folder,
 		const QVector<MTPDialogPeer> &list) {
-	const auto folder = folderId ? this->folder(folderId).get() : nullptr;
 	for (const auto &peer : list) {
 		peer.match([&](const MTPDdialogPeer &data) {
 			const auto history = this->history(peerFromMTP(data.vpeer));
@@ -1396,33 +1369,31 @@ void Session::applyPinnedChats(
 			}
 		});
 	}
-	if (folder) {
-		folder->applyPinnedChats(list);
-	} else {
-		_pinnedChatsList.applyList(this, list);
-	}
+	chatsList(folder)->pinned()->applyList(this, list);
 }
 
 void Session::applyDialogs(
-		FolderId requestFolderId,
+		Data::Folder *requestFolder,
 		const QVector<MTPMessage> &messages,
 		const QVector<MTPDialog> &dialogs) {
 	App::feedMsgs(messages, NewMessageLast);
 	for (const auto &dialog : dialogs) {
 		dialog.match([&](const auto &data) {
-			applyDialog(requestFolderId, data);
+			applyDialog(requestFolder, data);
 		});
 	}
 }
 
-void Session::applyDialog(FolderId requestFolderId, const MTPDdialog &data) {
+void Session::applyDialog(
+		Data::Folder *requestFolder,
+		const MTPDdialog &data) {
 	const auto peerId = peerFromMTP(data.vpeer);
 	if (!peerId) {
 		return;
 	}
 
 	const auto history = session().data().history(peerId);
-	history->applyDialog(requestFolderId, data);
+	history->applyDialog(requestFolder, data);
 	setPinnedFromDialog(history, data.is_pinned());
 
 	if (!history->fixedOnTopIndex() && !history->isPinnedDialog()) {
@@ -1443,10 +1414,10 @@ void Session::applyDialog(FolderId requestFolderId, const MTPDdialog &data) {
 }
 
 void Session::applyDialog(
-		FolderId requestFolderId,
+		Data::Folder *requestFolder,
 		const MTPDdialogFolder &data) {
-	if (requestFolderId != 0) {
-		LOG(("API Error: requestFolderId != 0 for dialogFolder."));
+	if (requestFolder) {
+		LOG(("API Error: requestFolder != nullptr for dialogFolder."));
 	}
 	const auto folder = processFolder(data.vfolder);
 	folder->applyDialog(data);
@@ -1478,36 +1449,23 @@ void Session::addAllSavedPeers() {
 	addSavedPeersAfter(QDateTime());
 }
 
-int Session::pinnedChatsCount(FolderId folderId) const {
-	return pinnedChatsOrder(folderId).size();
+int Session::pinnedChatsCount(Data::Folder *folder) const {
+	return pinnedChatsOrder(folder).size();
 }
 
-int Session::pinnedChatsLimit(FolderId folderId) const {
-	return folderId
+int Session::pinnedChatsLimit(Data::Folder *folder) const {
+	return folder
 		? Global::PinnedDialogsInFolderMax()
 		: Global::PinnedDialogsCountMax();
 }
 
 const std::vector<Dialogs::Key> &Session::pinnedChatsOrder(
-		FolderId folderId) const {
-	if (folderId) {
-		if (const auto folder = folderLoaded(folderId)) {
-			return folder->pinnedChatsOrder();
-		}
-		static const auto result = std::vector<Dialogs::Key>();
-		return result;
-	}
-	return _pinnedChatsList.order();
+		Data::Folder *folder) const {
+	return chatsList(folder)->pinned()->order();
 }
 
-void Session::clearPinnedChats(FolderId folderId) {
-	if (folderId) {
-		if (const auto folder = folderLoaded(folderId)) {
-			folder->clearPinnedChats();
-		}
-	} else {
-		_pinnedChatsList.clear();
-	}
+void Session::clearPinnedChats(Data::Folder *folder) {
+	chatsList(folder)->pinned()->clear();
 }
 
 void Session::reorderTwoPinnedChats(
@@ -1516,12 +1474,7 @@ void Session::reorderTwoPinnedChats(
 	Expects(key1.entry()->folderKnown() && key2.entry()->folderKnown());
 	Expects(key1.entry()->folder() == key2.entry()->folder());
 
-	const auto folder = key1.entry()->folder();
-	if (folder) {
-		folder->reorderTwoPinnedChats(key1, key2);
-	} else {
-		_pinnedChatsList.reorder(key1, key2);
-	}
+	chatsList(key1.entry()->folder())->pinned()->reorder(key1, key2);
 }
 
 NotifySettings &Session::defaultNotifySettings(
@@ -1621,57 +1574,61 @@ void Session::updateSendActionAnimation(
 }
 
 int Session::unreadBadge() const {
+	const auto state = _chatsList.unreadState();
 	return computeUnreadBadge(
-		_unreadFull,
-		_unreadMuted,
-		_unreadEntriesFull,
-		_unreadEntriesMuted);
+		state.messagesCount.value_or(0),
+		state.messagesCountMuted,
+		state.chatsCount,
+		state.chatsCountMuted);
 }
 
 bool Session::unreadBadgeMuted() const {
+	const auto state = _chatsList.unreadState();
 	return computeUnreadBadgeMuted(
-		_unreadFull,
-		_unreadMuted,
-		_unreadEntriesFull,
-		_unreadEntriesMuted);
+		state.messagesCount.value_or(0),
+		state.messagesCountMuted,
+		state.chatsCount,
+		state.chatsCountMuted);
 }
 
 int Session::unreadBadgeIgnoreOne(History *history) const {
-	const auto removeCount = (history
-		&& history->inChatList(Dialogs::Mode::All))
+	const auto removeCount = (history && history->inChatList())
 		? history->unreadCount()
 		: 0;
 	if (!removeCount) {
 		return unreadBadge();
 	}
-	const auto removeMuted = history->mute();
+	const auto state = _chatsList.unreadState();
+	const auto removeMuted = history->mute()
+		|| (history->folder() != nullptr);
 	return computeUnreadBadge(
-		_unreadFull - removeCount,
-		_unreadMuted - (removeMuted ? removeCount : 0),
-		_unreadEntriesFull - 1,
-		_unreadEntriesMuted - (removeMuted ? 1 : 0));
+		state.messagesCount.value_or(0) - removeCount,
+		state.messagesCountMuted - (removeMuted ? removeCount : 0),
+		state.chatsCount - 1,
+		state.chatsCountMuted - (removeMuted ? 1 : 0));
 }
 
 bool Session::unreadBadgeMutedIgnoreOne(History *history) const {
-	const auto removeCount = (history
-		&& history->inChatList(Dialogs::Mode::All))
+	const auto removeCount = (history && history->inChatList())
 		? history->unreadCount()
 		: 0;
 	if (!removeCount) {
 		return unreadBadgeMuted();
 	}
+	const auto state = _chatsList.unreadState();
 	const auto removeMuted = history->mute();
 	return computeUnreadBadgeMuted(
-		_unreadFull - removeCount,
-		_unreadMuted - (removeMuted ? removeCount : 0),
-		_unreadEntriesFull - 1,
-		_unreadEntriesMuted - (removeMuted ? 1 : 0));
+		state.messagesCount.value_or(0) - removeCount,
+		state.messagesCountMuted - (removeMuted ? removeCount : 0),
+		state.chatsCount - 1,
+		state.chatsCountMuted - (removeMuted ? 1 : 0));
 }
 
 int Session::unreadOnlyMutedBadge() const {
+	const auto state = _chatsList.unreadState();
 	return _session->settings().countUnreadMessages()
-		? _unreadMuted
-		: _unreadEntriesMuted;
+		? state.messagesCountMuted
+		: state.chatsCountMuted;
 }
 
 int Session::computeUnreadBadge(
@@ -1698,57 +1655,29 @@ bool Session::computeUnreadBadgeMuted(
 		: (entriesMuted >= entriesFull);
 }
 
-void Session::unreadIncrement(int count, bool muted) {
-	if (!count) {
-		return;
-	}
-	_unreadFull += count;
-	if (muted) {
-		_unreadMuted += count;
-	}
-	if (_session->settings().countUnreadMessages()) {
-		if (!muted || _session->settings().includeMutedCounter()) {
-			Notify::unreadCounterUpdated();
-		}
-	}
-}
+void Session::unreadStateChanged(
+		const Dialogs::Key &key,
+		const Dialogs::UnreadState &wasState) {
+	Expects(key.entry()->folderKnown());
+	Expects(key.entry()->inChatList());
 
-void Session::unreadMuteChanged(int count, bool muted) {
-	const auto wasAll = (_unreadMuted == _unreadFull);
-	if (muted) {
-		_unreadMuted += count;
+	const auto nowState = key.entry()->chatListUnreadState();
+	if (const auto folder = key.entry()->folder()) {
+		folder->unreadStateChanged(wasState, nowState);
 	} else {
-		_unreadMuted -= count;
-	}
-	if (_session->settings().countUnreadMessages()) {
-		const auto nowAll = (_unreadMuted == _unreadFull);
-		const auto changed = !_session->settings().includeMutedCounter()
-			|| (wasAll != nowAll);
-		if (changed) {
-			Notify::unreadCounterUpdated();
-		}
+		_chatsList.unreadStateChanged(wasState, nowState);
 	}
+	Notify::unreadCounterUpdated();
 }
 
-void Session::unreadEntriesChanged(
-		int withUnreadDelta,
-		int mutedWithUnreadDelta) {
-	if (!withUnreadDelta && !mutedWithUnreadDelta) {
-		return;
-	}
-	const auto wasAll = (_unreadEntriesMuted == _unreadEntriesFull);
-	_unreadEntriesFull += withUnreadDelta;
-	_unreadEntriesMuted += mutedWithUnreadDelta;
-	if (!_session->settings().countUnreadMessages()) {
-		const auto nowAll = (_unreadEntriesMuted == _unreadEntriesFull);
-		const auto withMuted = _session->settings().includeMutedCounter();
-		const auto withMutedChanged = withMuted
-			&& (withUnreadDelta != 0 || wasAll != nowAll);
-		const auto withoutMutedChanged = !withMuted
-			&& (withUnreadDelta != mutedWithUnreadDelta);
-		if (withMutedChanged || withoutMutedChanged) {
-			Notify::unreadCounterUpdated();
-		}
+void Session::unreadEntryChanged(const Dialogs::Key &key, bool added) {
+	Expects(key.entry()->folderKnown());
+
+	const auto state = key.entry()->chatListUnreadState();
+	if (const auto folder = key.entry()->folder()) {
+		folder->unreadEntryChanged(state, added);
+	} else {
+		_chatsList.unreadEntryChanged(state, added);
 	}
 }
 
@@ -2986,10 +2915,13 @@ not_null<Folder*> Session::processFolder(const MTPDfolder &data) {
 //	return _defaultFeedId.value();
 //}
 
-not_null<Dialogs::IndexedList*> Session::chatsList(Dialogs::Mode list) {
-	return (list == Dialogs::Mode::All)
-		? &_chatsList
-		: &_importantChatsList;
+not_null<Dialogs::MainList*> Session::chatsList(Data::Folder *folder) {
+	return folder ? folder->chatsList().get() : &_chatsList;
+}
+
+not_null<const Dialogs::MainList*> Session::chatsList(
+		Data::Folder *folder) const {
+	return folder ? folder->chatsList() : &_chatsList;
 }
 
 not_null<Dialogs::IndexedList*> Session::contactsList() {
@@ -3006,7 +2938,7 @@ auto Session::refreshChatListEntry(Dialogs::Key key)
 
 	const auto entry = key.entry();
 	auto result = RefreshChatListEntryResult();
-	result.changed = !entry->inChatList(Mode::All);
+	result.changed = !entry->inChatList();
 	if (result.changed) {
 		const auto mainRow = entry->addToChatList(Mode::All);
 		_contactsNoChatsList.del(key, mainRow);
@@ -3241,7 +3173,7 @@ void Session::serviceNotification(
 			MTPstring()));
 	}
 	const auto history = this->history(PeerData::kServiceNotificationsId);
-	if (!history->lastMessageKnown()) {
+	if (!history->folderKnown()) {
 		_session->api().requestDialogEntry(history, [=] {
 			insertCheckedServiceNotification(message, media, date);
 		});
@@ -3263,10 +3195,6 @@ void Session::insertCheckedServiceNotification(
 		const MTPMessageMedia &media,
 		TimeId date) {
 	const auto history = this->history(PeerData::kServiceNotificationsId);
-	if (!history->isReadyFor(ShowAtUnreadMsgId)) {
-		history->setUnreadCount(0);
-		history->getReadyFor(ShowAtTheEndMsgId);
-	}
 	const auto flags = MTPDmessage::Flag::f_entities
 		| MTPDmessage::Flag::f_from_id
 		| MTPDmessage_ClientFlag::f_clientside_unread
diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h
index 1593abc56..283900920 100644
--- a/Telegram/SourceFiles/data/data_session.h
+++ b/Telegram/SourceFiles/data/data_session.h
@@ -11,7 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "chat_helpers/stickers.h"
 #include "dialogs/dialogs_key.h"
 #include "dialogs/dialogs_indexed_list.h"
-#include "dialogs/dialogs_pinned_list.h"
+#include "dialogs/dialogs_main_list.h"
 #include "data/data_groups.h"
 #include "data/data_notify_settings.h"
 #include "history/history_location_manager.h"
@@ -51,8 +51,6 @@ struct SavedCredentials;
 namespace Data {
 
 class Folder;
-enum class FolderUpdateFlag;
-struct FolderUpdate;
 
 class WallPaper;
 
@@ -148,7 +146,7 @@ public:
 	}
 	void chatsListChanged(FolderId folderId);
 	void chatsListChanged(Data::Folder *folder);
-	void chatsListDone(FolderId folderId);
+	void chatsListDone(Data::Folder *folder);
 
 	struct ItemVisibilityQuery {
 		not_null<HistoryItem*> item;
@@ -209,9 +207,6 @@ public:
 	[[nodiscard]] rpl::producer<not_null<UserData*>> megagroupParticipantAdded(
 		not_null<ChannelData*> channel) const;
 
-	void notifyFolderUpdated(not_null<Folder*> folder, FolderUpdateFlag update);
-	[[nodiscard]] rpl::producer<FolderUpdate> folderUpdated() const;
-
 	void notifyStickersUpdated();
 	[[nodiscard]] rpl::producer<> stickersUpdated() const;
 	void notifySavedGifsUpdated();
@@ -299,20 +294,20 @@ public:
 	void applyUpdate(const MTPDupdateChatDefaultBannedRights &update);
 
 	void applyDialogs(
-		FolderId requestFolderId,
+		Data::Folder *requestFolder,
 		const QVector<MTPMessage> &messages,
 		const QVector<MTPDialog> &dialogs);
 	void addSavedPeersAfter(const QDateTime &date);
 	void addAllSavedPeers();
 
-	int pinnedChatsCount(FolderId folderId) const;
-	int pinnedChatsLimit(FolderId folderId) const;
+	int pinnedChatsCount(Data::Folder *folder) const;
+	int pinnedChatsLimit(Data::Folder *folder) const;
 	const std::vector<Dialogs::Key> &pinnedChatsOrder(
-		FolderId folderId) const;
+		Data::Folder *folder) const;
 	void setChatPinned(const Dialogs::Key &key, bool pinned);
-	void clearPinnedChats(FolderId folderId);
+	void clearPinnedChats(Data::Folder *folder);
 	void applyPinnedChats(
-		FolderId folderId,
+		Data::Folder *folder,
 		const QVector<MTPDialogPeer> &list);
 	void reorderTwoPinnedChats(
 		const Dialogs::Key &key1,
@@ -346,11 +341,10 @@ public:
 	bool unreadBadgeMutedIgnoreOne(History *history) const;
 	int unreadOnlyMutedBadge() const;
 
-	void unreadIncrement(int count, bool muted);
-	void unreadMuteChanged(int count, bool muted);
-	void unreadEntriesChanged(
-		int withUnreadDelta,
-		int mutedWithUnreadDelta);
+	void unreadStateChanged(
+		const Dialogs::Key &key,
+		const Dialogs::UnreadState &wasState);
+	void unreadEntryChanged(const Dialogs::Key &key, bool added);
 
 	void selfDestructIn(not_null<HistoryItem*> item, crl::time delay);
 
@@ -525,7 +519,9 @@ public:
 	//FeedId defaultFeedId() const;
 	//rpl::producer<FeedId> defaultFeedIdValue() const;
 
-	not_null<Dialogs::IndexedList*> chatsList(Dialogs::Mode list);
+	not_null<Dialogs::MainList*> chatsList(Data::Folder *folder = nullptr);
+	not_null<const Dialogs::MainList*> chatsList(
+		Data::Folder *folder = nullptr) const;
 	not_null<Dialogs::IndexedList*> contactsList();
 	not_null<Dialogs::IndexedList*> contactsNoChatsList();
 
@@ -601,6 +597,7 @@ private:
 	void setupUserIsContactViewer();
 
 	void checkSelfDestructItems();
+
 	int computeUnreadBadge(
 		int full,
 		int muted,
@@ -612,8 +609,10 @@ private:
 		int entriesFull,
 		int entriesMuted) const;
 
-	void applyDialog(FolderId requestFolderId, const MTPDdialog &data);
-	void applyDialog(FolderId requestFolderId, const MTPDdialogFolder &data);
+	void applyDialog(Data::Folder *requestFolder, const MTPDdialog &data);
+	void applyDialog(
+		Data::Folder *requestFolder,
+		const MTPDdialogFolder &data);
 
 	void photoApplyFields(
 		not_null<PhotoData*> photo,
@@ -734,7 +733,6 @@ private:
 	QPointer<BoxContent> _exportSuggestion;
 
 	rpl::variable<bool> _contactsLoaded = false;
-	bool _chatsListLoaded = false;
 	rpl::event_stream<Data::Folder*> _chatsListLoadedEvents;
 	rpl::event_stream<Data::Folder*> _chatsListChanged;
 	base::Observable<ItemVisibilityQuery> _queryItemVisibility;
@@ -756,7 +754,6 @@ private:
 	rpl::event_stream<not_null<History*>> _historyChanged;
 	rpl::event_stream<MegagroupParticipant> _megagroupParticipantRemoved;
 	rpl::event_stream<MegagroupParticipant> _megagroupParticipantAdded;
-	rpl::event_stream<FolderUpdate> _folderUpdates;
 	rpl::event_stream<DialogsRowReplacement> _dialogsRowReplacements;
 
 	rpl::event_stream<> _stickersUpdated;
@@ -773,16 +770,9 @@ private:
 	Stickers::Order _archivedStickerSetsOrder;
 	Stickers::SavedGifs _savedGifs;
 
-	int _unreadFull = 0;
-	int _unreadMuted = 0;
-	int _unreadEntriesFull = 0;
-	int _unreadEntriesMuted = 0;
-
-	Dialogs::IndexedList _chatsList;
-	Dialogs::IndexedList _importantChatsList;
+	Dialogs::MainList _chatsList;
 	Dialogs::IndexedList _contactsList;
 	Dialogs::IndexedList _contactsNoChatsList;
-	Dialogs::PinnedList _pinnedChatsList;
 
 	base::Timer _selfDestructTimer;
 	std::vector<FullMsgId> _selfDestructItems;
diff --git a/Telegram/SourceFiles/dialogs/dialogs_entry.cpp b/Telegram/SourceFiles/dialogs/dialogs_entry.cpp
index 1dad18c81..1e0b4aeee 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_entry.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_entry.cpp
@@ -77,7 +77,7 @@ void Entry::cacheProxyPromoted(bool promoted) {
 }
 
 bool Entry::needUpdateInChatList() const {
-	return inChatList(Dialogs::Mode::All) || shouldBeInChatList();
+	return inChatList() || shouldBeInChatList();
 }
 
 void Entry::updateChatListSortPosition() {
@@ -102,6 +102,10 @@ void Entry::updateChatListExistence() {
 	setChatListExistence(shouldBeInChatList());
 }
 
+void Entry::notifyUnreadStateChange(const UnreadState &wasState) {
+	owner().unreadStateChanged(_key, wasState);
+}
+
 void Entry::setChatListExistence(bool exists) {
 	if (const auto main = App::main()) {
 		if (exists && _sortKeyInChatList) {
@@ -117,9 +121,6 @@ TimeId Entry::adjustedChatListTimeId() const {
 	return chatListTimeId();
 }
 
-void Entry::changedInChatListHook(Dialogs::Mode list, bool added) {
-}
-
 void Entry::changedChatListPinHook() {
 }
 
@@ -160,7 +161,9 @@ int Entry::posInChatList(Dialogs::Mode list) const {
 not_null<Row*> Entry::addToChatList(Mode list) {
 	if (!inChatList(list)) {
 		chatListLinks(list) = myChatsList(list)->addToEnd(_key);
-		changedInChatListHook(list, true);
+		if (list == Mode::All) {
+			owner().unreadEntryChanged(_key, true);
+		}
 	}
 	return mainChatListLink(list);
 }
@@ -169,7 +172,9 @@ void Entry::removeFromChatList(Dialogs::Mode list) {
 	if (inChatList(list)) {
 		myChatsList(list)->del(_key);
 		chatListLinks(list).clear();
-		changedInChatListHook(list, false);
+		if (list == Mode::All) {
+			owner().unreadEntryChanged(_key, false);
+		}
 	}
 }
 
@@ -194,7 +199,7 @@ void Entry::addChatListEntryByLetter(
 
 void Entry::updateChatListEntry() const {
 	if (const auto main = App::main()) {
-		if (inChatList(Mode::All)) {
+		if (inChatList()) {
 			main->repaintDialogRow(
 				Mode::All,
 				mainChatListLink(Mode::All));
@@ -212,10 +217,7 @@ void Entry::updateChatListEntry() const {
 }
 
 not_null<IndexedList*> Entry::myChatsList(Mode list) const {
-	if (const auto current = folder()) {
-		return current->chatsList(list);
-	}
-	return owner().chatsList(list);
+	return owner().chatsList(folder())->indexed(list);
 }
 
 } // namespace Dialogs
diff --git a/Telegram/SourceFiles/dialogs/dialogs_entry.h b/Telegram/SourceFiles/dialogs/dialogs_entry.h
index bd40f4278..6f11954d7 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_entry.h
+++ b/Telegram/SourceFiles/dialogs/dialogs_entry.h
@@ -40,6 +40,15 @@ struct PositionChange {
 	int to = -1;
 };
 
+struct UnreadState {
+	std::optional<int> messagesCount;
+	int messagesCountMuted = 0;
+	int chatsCount = 0;
+	int chatsCountMuted = 0;
+	bool mark = false;
+	bool markMuted = false;
+};
+
 class Entry {
 public:
 	Entry(not_null<Data::Session*> owner, const Key &key);
@@ -51,7 +60,7 @@ public:
 	AuthSession &session() const;
 
 	PositionChange adjustByPosInChatList(Mode list);
-	bool inChatList(Mode list) const {
+	bool inChatList(Mode list = Mode::All) const {
 		return !chatListLinks(list).empty();
 	}
 	int posInChatList(Mode list) const;
@@ -89,6 +98,7 @@ public:
 	virtual int chatListUnreadCount() const = 0;
 	virtual bool chatListUnreadMark() const = 0;
 	virtual bool chatListMutedBadge() const = 0;
+	virtual UnreadState chatListUnreadState() const = 0;
 	virtual HistoryItem *chatListMessage() const = 0;
 	virtual bool chatListMessageKnown() const = 0;
 	virtual void requestChatListMessage() = 0;
@@ -125,10 +135,22 @@ public:
 	mutable const HistoryItem *textCachedFor = nullptr; // cache
 	mutable Text lastItemTextCache;
 
+protected:
+	auto unreadStateChangeNotifier(bool required) {
+		const auto notify = required && inChatList();
+		const auto wasState = notify ? chatListUnreadState() : UnreadState();
+		return gsl::finally([=] {
+			if (notify) {
+				notifyUnreadStateChange(wasState);
+			}
+		});
+	}
+
 private:
-	virtual void changedInChatListHook(Mode list, bool added);
 	virtual void changedChatListPinHook();
 
+	void notifyUnreadStateChange(const UnreadState &wasState);
+
 	void setChatListExistence(bool exists);
 	RowsByLetter &chatListLinks(Mode list);
 	const RowsByLetter &chatListLinks(Mode list) const;
diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
index 3a951a0b9..d8dd8c273 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
@@ -96,6 +96,14 @@ DialogsInner::DialogsInner(QWidget *parent, not_null<Window::Controller*> contro
 
 	subscribe(session().downloaderTaskFinished(), [=] { update(); });
 
+	subscribe(session().notifications().settingsChanged(), [=](
+			Window::Notifications::ChangeType change) {
+		if (change == Window::Notifications::ChangeType::CountMessages) {
+			// Folder rows change their unread badge with this setting.
+			update();
+		}
+	});
+
 	session().data().contactsLoaded().changes(
 	) | rpl::start_with_next([=] {
 		refresh();
@@ -143,8 +151,9 @@ DialogsInner::DialogsInner(QWidget *parent, not_null<Window::Controller*> contro
 	) | rpl::filter([=](Data::Folder *folder) {
 		return (folder == _openedFolder);
 	}) | rpl::start_with_next([=] {
+		const auto mode = Global::DialogsMode();
 		if (_openedFolder
-			&& _openedFolder->chatsList(Global::DialogsMode())->empty()) {
+			&& _openedFolder->chatsList()->indexed(mode)->empty()) {
 			_openedFolder->updateChatListSortPosition();
 			cancelFolder();
 		} else {
@@ -188,10 +197,6 @@ DialogsInner::DialogsInner(QWidget *parent, not_null<Window::Controller*> contro
 			}
 		}
 	}));
-	session().data().folderUpdated(
-	) | rpl::start_with_next([=](const Data::FolderUpdate &update) {
-		updateDialogRow({ update.folder, FullMsgId() });
-	}, lifetime());
 
 	_controller->activeChatEntryValue(
 	) | rpl::combine_previous(
@@ -218,8 +223,7 @@ void DialogsInner::handleChatMigration(not_null<ChatData*> chat) {
 
 	if (const auto from = chat->owner().historyLoaded(chat)) {
 		if (const auto to = chat->owner().historyLoaded(channel)) {
-			if (to->inChatList(Dialogs::Mode::All)
-				&& from->inChatList(Dialogs::Mode::All)) {
+			if (to->inChatList() && from->inChatList()) {
 				removeDialog(from);
 			}
 		}
@@ -227,8 +231,11 @@ void DialogsInner::handleChatMigration(not_null<ChatData*> chat) {
 }
 
 int DialogsInner::dialogsOffset() const {
-	return (_openedFolder ? openedFolderSkip() : 0)
-		+ (_importantSwitch ? st::dialogsImportantBarHeight : 0);
+	return _openedFolder
+		? openedFolderSkip()
+		: _importantSwitch
+		? st::dialogsImportantBarHeight
+		: 0;
 }
 
 int DialogsInner::proxyPromotedCount() const {
@@ -298,6 +305,9 @@ bool DialogsInner::changeOpenedFolder(Data::Folder *folder) {
 			Ui::DialogTextOptions());
 	}
 	refresh();
+	if (_loadMoreCallback) {
+		_loadMoreCallback();
+	}
 	return true;
 }
 
@@ -320,7 +330,7 @@ void DialogsInner::paintEvent(QPaintEvent *e) {
 	}
 	if (_state == State::Default) {
 		auto rows = shownDialogs();
-		if (_importantSwitch) {
+		if (!_openedFolder && _importantSwitch) {
 			auto selected = isPressed() ? _importantSwitchPressed : _importantSwitchSelected;
 			Dialogs::Layout::paintImportantSwitch(p, Global::DialogsMode(), fullWidth, selected);
 			dialogsClip.translate(0, -st::dialogsImportantBarHeight);
@@ -807,12 +817,21 @@ void DialogsInner::selectByMouse(QPoint globalPosition) {
 	_mouseSelection = true;
 	_lastMousePosition = globalPosition;
 
-	int w = width(), mouseY = local.y();
+	const auto w = width();
+	const auto mouseY = local.y();
 	clearIrrelevantState();
 	if (_state == State::Default) {
-		auto importantSwitchSelected = (_importantSwitch && mouseY >= 0 && mouseY < dialogsOffset());
-		mouseY -= dialogsOffset();
-		auto selected = importantSwitchSelected ? nullptr : (mouseY >= 0 ? shownDialogs()->rowAtY(mouseY, st::dialogsRowHeight) : nullptr);
+		const auto switchTop = _openedFolder ? openedFolderSkip() : 0;
+		const auto switchBottom = dialogsOffset();
+		const auto importantSwitchSelected = _importantSwitch
+			&& !_openedFolder
+			&& (mouseY >= switchTop)
+			&& (mouseY < switchBottom);
+		const auto selected = importantSwitchSelected
+			? nullptr
+			: (mouseY >= switchBottom)
+			? shownDialogs()->rowAtY(mouseY - switchBottom, st::dialogsRowHeight)
+			: nullptr;
 		if (_selected != selected || _importantSwitchSelected != importantSwitchSelected) {
 			updateSelectedRow();
 			_selected = selected;
@@ -945,8 +964,7 @@ void DialogsInner::checkReorderPinnedStart(QPoint localPosition) {
 	if (updateReorderIndexGetCount() < 2) {
 		_dragging = nullptr;
 	} else {
-		const auto folderId = _openedFolder ? _openedFolder->id() : 0;
-		const auto &order = session().data().pinnedChatsOrder(folderId);
+		const auto &order = session().data().pinnedChatsOrder(_openedFolder);
 		_pinnedOnDragStart = base::flat_set<Dialogs::Key>{
 			order.begin(),
 			order.end()
@@ -989,8 +1007,7 @@ int DialogsInner::countPinnedIndex(Dialogs::Row *ofRow) {
 }
 
 void DialogsInner::savePinnedOrder() {
-	const auto folderId = _openedFolder ? _openedFolder->id() : 0;
-	const auto &newOrder = session().data().pinnedChatsOrder(folderId);
+	const auto &newOrder = session().data().pinnedChatsOrder(_openedFolder);
 	if (newOrder.size() != _pinnedOnDragStart.size()) {
 		return; // Something has changed in the set of pinned chats.
 	}
@@ -999,7 +1016,7 @@ void DialogsInner::savePinnedOrder() {
 			return; // Something has changed in the set of pinned chats.
 		}
 	}
-	session().api().savePinnedOrder(folderId);
+	session().api().savePinnedOrder(_openedFolder);
 }
 
 void DialogsInner::finishReorderPinned() {
@@ -1513,7 +1530,8 @@ void DialogsInner::updateSelectedRow(Dialogs::Key key) {
 		} else if (_selected) {
 			update(0, dialogsOffset() + _selected->pos() * st::dialogsRowHeight, width(), st::dialogsRowHeight);
 		} else if (_importantSwitchSelected) {
-			update(0, 0, width(), st::dialogsImportantBarHeight);
+			const auto switchTop = _openedFolder ? openedFolderSkip() : 0;
+			update(0, switchTop, width(), st::dialogsImportantBarHeight);
 		}
 	} else if (_state == State::Filtered) {
 		if (key) {
@@ -1536,9 +1554,8 @@ void DialogsInner::updateSelectedRow(Dialogs::Key key) {
 }
 
 not_null<Dialogs::IndexedList*> DialogsInner::shownDialogs() const {
-	return _openedFolder
-		? _openedFolder->chatsList(Global::DialogsMode())
-		: session().data().chatsList(Global::DialogsMode());
+	const auto mode = Global::DialogsMode();
+	return session().data().chatsList(_openedFolder)->indexed(mode);
 }
 
 void DialogsInner::leaveEventHook(QEvent *e) {
@@ -1676,7 +1693,7 @@ void DialogsInner::applyFilterUpdate(QString newFilter, bool force) {
 			_filterResultsGlobal.clear();
 			if (!_searchInChat && !words.isEmpty()) {
 				const Dialogs::List *toFilter = nullptr;
-				if (const auto list = session().data().chatsList(Dialogs::Mode::All); !list->empty()) {
+				if (const auto list = session().data().chatsList()->indexed(); !list->empty()) {
 					for (fi = fb; fi != fe; ++fi) {
 						const auto found = list->filtered(fi->at(0));
 						if (!found || found->empty()) {
@@ -2001,7 +2018,7 @@ void DialogsInner::peerSearchReceived(
 	for (const auto &mtpPeer : result) {
 		if (const auto peer = session().data().peerLoaded(peerFromMTP(mtpPeer))) {
 			if (const auto history = peer->owner().historyLoaded(peer)) {
-				if (history->inChatList(Dialogs::Mode::All)) {
+				if (history->inChatList()) {
 					continue; // skip existing chats
 				}
 			}
@@ -2017,7 +2034,7 @@ void DialogsInner::peerSearchReceived(
 }
 
 void DialogsInner::notify_historyMuteUpdated(History *history) {
-	if (!_importantSwitch || !history->inChatList(Dialogs::Mode::All)) {
+	if (!_importantSwitch || !history->inChatList()) {
 		return;
 	}
 	refreshDialog(history);
diff --git a/Telegram/SourceFiles/dialogs/dialogs_list.h b/Telegram/SourceFiles/dialogs/dialogs_list.h
index b383c39ff..34fb53d14 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_list.h
+++ b/Telegram/SourceFiles/dialogs/dialogs_list.h
@@ -14,7 +14,7 @@ namespace Dialogs {
 
 enum class SortMode;
 
-class List {
+class List final {
 public:
 	List(SortMode sortMode);
 	List(const List &other) = delete;
diff --git a/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp b/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp
new file mode 100644
index 000000000..347cfbd2e
--- /dev/null
+++ b/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp
@@ -0,0 +1,130 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "dialogs/dialogs_main_list.h"
+
+#include "observer_peer.h"
+#include "history/history.h"
+
+namespace Dialogs {
+namespace {
+
+UnreadState ApplyMarkToCounters(const UnreadState &state) {
+	auto result = UnreadState();
+	const auto count = state.messagesCount.value_or(0);
+	result.messagesCount = (count > 0)
+		? count
+		: state.mark
+		? 1
+		: 0;
+	result.messagesCountMuted = (state.messagesCountMuted > 0)
+		? state.messagesCountMuted
+		: state.markMuted
+		? 1
+		: 0;
+	result.chatsCount = (state.chatsCount > 0)
+		? state.chatsCount
+		: state.mark
+		? 1
+		: 0;
+	result.chatsCountMuted = (state.chatsCountMuted > 0)
+		? state.chatsCountMuted
+		: state.markMuted
+		? 1
+		: 0;
+	return result;
+}
+
+} // namespace
+
+MainList::MainList(rpl::producer<int> pinnedLimit)
+: _all(SortMode::Date)
+, _important(SortMode::Date)
+, _pinned(1) {
+	_unreadState.messagesCount = 0;
+
+	std::move(
+		pinnedLimit
+	) | rpl::start_with_next([=](int limit) {
+		_pinned.setLimit(limit);
+	}, _lifetime);
+
+	Notify::PeerUpdateViewer(
+		Notify::PeerUpdate::Flag::NameChanged
+	) | rpl::start_with_next([=](const Notify::PeerUpdate &update) {
+		const auto peer = update.peer;
+		const auto &oldLetters = update.oldNameFirstLetters;
+		_all.peerNameChanged(Mode::All, peer, oldLetters);
+		_important.peerNameChanged(Mode::Important, peer, oldLetters);
+	}, _lifetime);
+}
+
+bool MainList::empty() const {
+	return _all.empty();
+}
+
+bool MainList::loaded() const {
+	return _loaded;
+}
+
+void MainList::setLoaded(bool loaded) {
+	_loaded = loaded;
+}
+
+void MainList::clear() {
+	_all.clear();
+	_important.clear();
+	_unreadState = UnreadState();
+}
+
+void MainList::unreadStateChanged(
+		const UnreadState &wasState,
+		const UnreadState &nowState) {
+	const auto wasWithMark = ApplyMarkToCounters(wasState);
+	const auto nowWithMark = ApplyMarkToCounters(nowState);
+	*_unreadState.messagesCount += *nowWithMark.messagesCount
+		- *wasWithMark.messagesCount;
+	_unreadState.messagesCountMuted += nowWithMark.messagesCountMuted
+		- wasWithMark.messagesCountMuted;
+	_unreadState.chatsCount += nowWithMark.chatsCount
+		- wasWithMark.chatsCount;
+	_unreadState.chatsCountMuted += nowWithMark.chatsCountMuted
+		- wasWithMark.chatsCountMuted;
+}
+
+void MainList::unreadEntryChanged(
+		const Dialogs::UnreadState &state,
+		bool added) {
+	const auto withMark = ApplyMarkToCounters(state);
+	const auto delta = (added ? 1 : -1);
+	*_unreadState.messagesCount += delta * *withMark.messagesCount;
+	_unreadState.messagesCountMuted += delta * withMark.messagesCountMuted;
+	_unreadState.chatsCount += delta * withMark.chatsCount;
+	_unreadState.chatsCountMuted += delta * withMark.chatsCountMuted;
+}
+
+UnreadState MainList::unreadState() const {
+	return _unreadState;
+}
+
+not_null<IndexedList*> MainList::indexed(Mode list) {
+	return (list == Mode::All) ? &_all : &_important;
+}
+
+not_null<const IndexedList*> MainList::indexed(Mode list) const {
+	return (list == Mode::All) ? &_all : &_important;
+}
+
+not_null<PinnedList*> MainList::pinned() {
+	return &_pinned;
+}
+
+not_null<const PinnedList*> MainList::pinned() const {
+	return &_pinned;
+}
+
+} // namespace Dialogs
diff --git a/Telegram/SourceFiles/dialogs/dialogs_main_list.h b/Telegram/SourceFiles/dialogs/dialogs_main_list.h
new file mode 100644
index 000000000..ce5831105
--- /dev/null
+++ b/Telegram/SourceFiles/dialogs/dialogs_main_list.h
@@ -0,0 +1,49 @@
+/*
+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 "dialogs/dialogs_indexed_list.h"
+#include "dialogs/dialogs_pinned_list.h"
+
+namespace Dialogs {
+
+class MainList final {
+public:
+	explicit MainList(rpl::producer<int> pinnedLimit);
+
+	bool empty() const;
+	bool loaded() const;
+	void setLoaded(bool loaded = true);
+	void clear();
+
+	void unreadStateChanged(
+		const UnreadState &wasState,
+		const UnreadState &nowState);
+	void unreadEntryChanged(
+		const Dialogs::UnreadState &state,
+		bool added);
+	UnreadState unreadState() const;
+
+	not_null<IndexedList*> indexed(Mode list = Mode::All);
+	not_null<const IndexedList*> indexed(Mode list = Mode::All) const;
+	not_null<PinnedList*> pinned();
+	not_null<const PinnedList*> pinned() const;
+
+private:
+	IndexedList _all;
+	IndexedList _important;
+	PinnedList _pinned;
+	UnreadState _unreadState;
+
+	bool _loaded = false;
+
+	rpl::lifetime _lifetime;
+
+};
+
+} // namespace Dialogs
diff --git a/Telegram/SourceFiles/dialogs/dialogs_pinned_list.cpp b/Telegram/SourceFiles/dialogs/dialogs_pinned_list.cpp
index fecabf373..d98ebf861 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_pinned_list.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_pinned_list.cpp
@@ -41,7 +41,7 @@ int PinnedList::addPinnedGetPosition(const Key &key) {
 	applyLimit(_limit - 1);
 	const auto position = int(_data.size());
 	_data.push_back(key);
-	key.entry()->cachePinnedIndex(position);
+	key.entry()->cachePinnedIndex(position + 1);
 	return position;
 }
 
diff --git a/Telegram/SourceFiles/dialogs/dialogs_pinned_list.h b/Telegram/SourceFiles/dialogs/dialogs_pinned_list.h
index bda94285e..c7910fc2d 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_pinned_list.h
+++ b/Telegram/SourceFiles/dialogs/dialogs_pinned_list.h
@@ -15,7 +15,7 @@ namespace Dialogs {
 
 class Key;
 
-class PinnedList {
+class PinnedList final {
 public:
 	explicit PinnedList(int limit);
 
diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
index bd9a5dcab..286dfd9e2 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
@@ -248,9 +248,8 @@ DialogsWidget::DialogsWidget(QWidget *parent, not_null<Window::Controller*> cont
 			onSearchMore();
 		} else {
 			const auto folder = _inner->shownFolder();
-			const auto folderId = folder ? folder->id() : FolderId(0);
-			if (!folderId || !folder->chatsListLoaded()) {
-				session().api().requestDialogs(folderId);
+			if (!folder || !folder->chatsListLoaded()) {
+				session().api().requestDialogs(folder);
 			}
 		}
 	});
@@ -378,13 +377,13 @@ void DialogsWidget::activate() {
 }
 
 void DialogsWidget::refreshDialog(Dialogs::Key key) {
-	const auto creating = !key.entry()->inChatList(Dialogs::Mode::All);
+	const auto creating = !key.entry()->inChatList();
 	_inner->refreshDialog(key);
 	const auto history = key.history();
 	if (creating && history && history->peer->migrateFrom()) {
 		if (const auto migrated = history->owner().historyLoaded(
 				history->peer->migrateFrom())) {
-			if (migrated->inChatList(Dialogs::Mode::All)) {
+			if (migrated->inChatList()) {
 				_inner->removeDialog(migrated);
 			}
 		}
diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp
index 48cb5373c..aa56cc027 100644
--- a/Telegram/SourceFiles/history/history.cpp
+++ b/Telegram/SourceFiles/history/history.cpp
@@ -1610,98 +1610,69 @@ int History::unreadCountForBadge() const {
 }
 
 bool History::unreadCountKnown() const {
-	return !!_unreadCount;
+	return _unreadCount.has_value();
 }
 
 void History::setUnreadCount(int newUnreadCount) {
-	if (!_unreadCount || *_unreadCount != newUnreadCount) {
-		const auto wasUnread = _unreadMark || unreadCount();
-		const auto unreadCountDelta = _unreadCount | [&](int count) {
-			return newUnreadCount - count;
-		};
-		if (newUnreadCount == 1) {
-			if (loadedAtBottom()) {
-				_firstUnreadView = !isEmpty()
-					? blocks.back()->messages.back().get()
-					: nullptr;
-			}
-			if (const auto last = msgIdForRead()) {
-				setInboxReadTill(last - 1);
-			}
-		} else if (!newUnreadCount) {
-			_firstUnreadView = nullptr;
-			if (const auto last = msgIdForRead()) {
-				setInboxReadTill(last);
-			}
-		} else {
-			if (!_firstUnreadView && !_unreadBarView && loadedAtBottom()) {
-				calculateFirstUnreadMessage();
-			}
-		}
-		const auto unreadMarkDelta = [&] {
-			if (_unreadMark) {
-				const auto was = _unreadCount && (*_unreadCount > 0);
-				const auto now = (newUnreadCount > 0);
-				if (was != now) {
-					return was ? 1 : -1;
-				}
-			}
-			return 0;
-		}();
-		_unreadCount = newUnreadCount;
+	Expects(folderKnown());
 
-		if (_unreadBarView) {
-			const auto count = chatListUnreadCount();
-			if (count > 0) {
-				_unreadBarView->setUnreadBarCount(count);
-			} else {
-				_unreadBarView->setUnreadBarFreezed();
-			}
-		}
-
-		if (inChatList(Dialogs::Mode::All)) {
-			const auto delta = unreadMarkDelta + (unreadCountDelta
-				? *unreadCountDelta
-				: newUnreadCount);
-			owner().unreadIncrement(delta, mute());
-
-			const auto nowUnread = (*_unreadCount > 0) || _unreadMark;
-			const auto entriesDelta = (wasUnread && !nowUnread)
-				? -1
-				: (nowUnread && !wasUnread)
-				? 1
-				: 0;
-			owner().unreadEntriesChanged(
-				entriesDelta,
-				mute() ? entriesDelta : 0);
-		}
-		Notify::peerUpdatedDelayed(
-			peer,
-			Notify::PeerUpdate::Flag::UnreadViewChanged);
+	if (_unreadCount == newUnreadCount) {
+		return;
 	}
+	const auto notifier = unreadStateChangeNotifier(true);
+
+	_unreadCount = newUnreadCount;
+
+	if (newUnreadCount == 1) {
+		if (loadedAtBottom()) {
+			_firstUnreadView = !isEmpty()
+				? blocks.back()->messages.back().get()
+				: nullptr;
+		}
+		if (const auto last = msgIdForRead()) {
+			setInboxReadTill(last - 1);
+		}
+	} else if (!newUnreadCount) {
+		_firstUnreadView = nullptr;
+		if (const auto last = msgIdForRead()) {
+			setInboxReadTill(last);
+		}
+	} else {
+		if (!_firstUnreadView && !_unreadBarView && loadedAtBottom()) {
+			calculateFirstUnreadMessage();
+		}
+	}
+	if (_unreadBarView) {
+		const auto count = chatListUnreadCount();
+		if (count > 0) {
+			_unreadBarView->setUnreadBarCount(count);
+		} else {
+			_unreadBarView->setUnreadBarFreezed();
+		}
+	}
+	Notify::peerUpdatedDelayed(
+		peer,
+		Notify::PeerUpdate::Flag::UnreadViewChanged);
 }
 
 void History::setUnreadMark(bool unread) {
 	if (clearUnreadOnClientSide()) {
 		unread = false;
 	}
-	if (_unreadMark != unread) {
-		_unreadMark = unread;
-		if (!_unreadCount || !*_unreadCount) {
-			if (inChatList(Dialogs::Mode::All)) {
-				const auto delta = _unreadMark ? 1 : -1;
-				owner().unreadIncrement(delta, mute());
-				owner().unreadEntriesChanged(
-					delta,
-					mute() ? delta : 0);
-
-				updateChatListEntry();
-			}
-		}
-		Notify::peerUpdatedDelayed(
-			peer,
-			Notify::PeerUpdate::Flag::UnreadViewChanged);
+	if (_unreadMark == unread) {
+		return;
 	}
+	const auto noUnreadMessages = !unreadCount();
+	const auto notifier = unreadStateChangeNotifier(noUnreadMessages);
+
+	_unreadMark = unread;
+
+	if (inChatList() && noUnreadMessages) {
+		updateChatListEntry();
+	}
+	Notify::peerUpdatedDelayed(
+		peer,
+		Notify::PeerUpdate::Flag::UnreadViewChanged);
 }
 
 bool History::unreadMark() const {
@@ -1728,38 +1699,15 @@ bool History::changeMute(bool newMute) {
 	if (_mute == newMute) {
 		return false;
 	}
+	const auto notify = (unreadCountForBadge() > 0);
+	const auto notifier = unreadStateChangeNotifier(notify);
+
 	_mute = newMute;
 
-	//const auto feed = peer->isChannel() // #feed
-	//	? peer->asChannel()->feed()
-	//	: nullptr;
-	//if (feed) {
-	//	if (_unreadCount) {
-	//		if (*_unreadCount) {
-	//			const auto unreadCountDelta = 0;
-	//			const auto mutedCountDelta = _mute ? *_unreadCount : -*_unreadCount;
-	//			feed->unreadCountChanged(unreadCountDelta, mutedCountDelta);
-	//		}
-	//	} else {
-	//		session().api().requestDialogEntry(this);
-	//		session().api().requestDialogEntry(feed);
-	//	}
-	//}
-	if (inChatList(Dialogs::Mode::All)) {
-		if (const auto count = unreadCountForBadge()) {
-			owner().unreadMuteChanged(count, _mute);
-
-			const auto entriesWithUnreadDelta = 0;
-			const auto mutedEntriesWithUnreadDelta = _mute ? 1 : -1;
-			owner().unreadEntriesChanged(
-				entriesWithUnreadDelta,
-				mutedEntriesWithUnreadDelta);
-
-			Notify::unreadCounterUpdated();
-		}
+	if (inChatList()) {
 		Notify::historyMuteUpdated(this);
+		updateChatListEntry();
 	}
-	updateChatListEntry();
 	Notify::peerUpdatedDelayed(
 		peer,
 		Notify::PeerUpdate::Flag::NotificationsEnabled);
@@ -1839,9 +1787,9 @@ void History::setFolderPointer(Data::Folder *folder) {
 		owner().setChatPinned(this, false);
 	}
 	const auto wasKnown = folderKnown();
-	const auto wasInAll = inChatList(Mode::All);
-	const auto wasInImportant = wasInAll && inChatList(Mode::Important);
-	if (wasInAll) {
+	const auto wasInList = inChatList();
+	const auto wasInImportant = wasInList && inChatList(Mode::Important);
+	if (wasInList) {
 		removeFromChatList(Mode::All);
 		if (wasInImportant) {
 			removeFromChatList(Mode::Important);
@@ -1852,7 +1800,7 @@ void History::setFolderPointer(Data::Folder *folder) {
 	if (was) {
 		was->unregisterOne(this);
 	}
-	if (wasInAll) {
+	if (wasInList) {
 		addToChatList(Mode::All);
 		if (wasInImportant) {
 			addToChatList(Mode::Important);
@@ -2068,6 +2016,18 @@ bool History::chatListMutedBadge() const {
 	return mute();
 }
 
+Dialogs::UnreadState History::chatListUnreadState() const {
+	auto result = Dialogs::UnreadState();
+	const auto count = _unreadCount.value_or(0);
+	result.messagesCount = _unreadCount;
+	result.messagesCountMuted = (_unreadCount && mute()) ? count : 0;
+	result.chatsCount = count ? 1 : 0;
+	result.chatsCountMuted = (count && mute()) ? 1 : 0;
+	result.mark = _unreadMark;
+	result.markMuted = mute() ? _unreadMark : false;
+	return result;
+}
+
 HistoryItem *History::chatListMessage() const {
 	return _chatListMessage.value_or(nullptr);
 }
@@ -2482,8 +2442,16 @@ bool History::isServerSideUnread(not_null<const HistoryItem*> item) const {
 		: (!_inboxReadBefore || (item->id >= *_inboxReadBefore));
 }
 
-void History::applyDialog(FolderId requestFolderId, const MTPDdialog &data) {
+void History::applyDialog(
+		Data::Folder *requestFolder,
+		const MTPDdialog &data) {
+	const auto folder = data.has_folder_id()
+		? (data.vfolder_id.v
+			? owner().folder(data.vfolder_id.v).get()
+			: nullptr)
+		: requestFolder;
 	applyDialogFields(
+		folder,
 		data.vunread_count.v,
 		data.vread_inbox_max_id.v,
 		data.vread_outbox_max_id.v);
@@ -2512,15 +2480,6 @@ void History::applyDialog(FolderId requestFolderId, const MTPDdialog &data) {
 	if (data.has_draft() && data.vdraft.type() == mtpc_draftMessage) {
 		Data::applyPeerCloudDraft(peer->id, data.vdraft.c_draftMessage());
 	}
-
-	const auto folderId = data.has_folder_id()
-		? data.vfolder_id.v
-		: requestFolderId;
-	if (folderId) {
-		setFolder(owner().folder(folderId));
-	} else {
-		clearFolder();
-	}
 	session().api().dialogEntryApplied(this);
 }
 
@@ -2593,9 +2552,15 @@ bool History::skipUnreadUpdate() const {
 }
 
 void History::applyDialogFields(
+		Data::Folder *folder,
 		int unreadCount,
 		MsgId maxInboxRead,
 		MsgId maxOutboxRead) {
+	if (folder) {
+		setFolder(folder);
+	} else {
+		clearFolder();
+	}
 	if (!skipUnreadUpdate()) {
 		setUnreadCount(unreadCount);
 		setInboxReadTill(maxInboxRead);
@@ -3089,19 +3054,6 @@ void History::applyGroupAdminChanges(
 	}
 }
 
-void History::changedInChatListHook(Dialogs::Mode list, bool added) {
-	if (list == Dialogs::Mode::All) {
-		if (const auto delta = unreadCountForBadge() * (added ? 1 : -1)) {
-			owner().unreadIncrement(delta, mute());
-
-			const auto entriesDelta = added ? 1 : -1;
-			owner().unreadEntriesChanged(
-				entriesDelta,
-				mute() ? entriesDelta : 0);
-		}
-	}
-}
-
 void History::changedChatListPinHook() {
 	Notify::peerUpdatedDelayed(
 		peer,
diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h
index 480e71664..5697d3da2 100644
--- a/Telegram/SourceFiles/history/history.h
+++ b/Telegram/SourceFiles/history/history.h
@@ -191,9 +191,10 @@ public:
 	bool lastMessageKnown() const;
 	void unknownMessageDeleted(MsgId messageId);
 	void applyDialogTopMessage(MsgId topMessageId);
-	void applyDialog(FolderId requestFolderId, const MTPDdialog &data);
+	void applyDialog(Data::Folder *requestFolder, const MTPDdialog &data);
 	void applyPinnedUpdate(const MTPDupdateDialogPinned &data);
 	void applyDialogFields(
+		Data::Folder *folder,
 		int unreadCount,
 		MsgId maxInboxRead,
 		MsgId maxOutboxRead);
@@ -299,6 +300,7 @@ public:
 	int chatListUnreadCount() const override;
 	bool chatListUnreadMark() const override;
 	bool chatListMutedBadge() const override;
+	Dialogs::UnreadState chatListUnreadState() const override;
 	HistoryItem *chatListMessage() const override;
 	bool chatListMessageKnown() const override;
 	void requestChatListMessage() override;
@@ -417,7 +419,6 @@ private:
 	void removeNotification(not_null<HistoryItem*> item);
 
 	TimeId adjustedChatListTimeId() const override;
-	void changedInChatListHook(Dialogs::Mode list, bool added) override;
 	void changedChatListPinHook() override;
 
 	void setInboxReadTill(MsgId upTo);
diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp
index 53ad5ca1e..b5ec3c2e4 100644
--- a/Telegram/SourceFiles/mainwidget.cpp
+++ b/Telegram/SourceFiles/mainwidget.cpp
@@ -2872,16 +2872,16 @@ void MainWidget::gotChannelDifference(
 			history->setNotLoadedAtBottom();
 			session().api().requestChannelRangeDifference(history);
 		}
-		App::feedMsgs(data.vmessages, NewMessageLast);
 		data.vdialog.match([&](const MTPDdialog &data) {
 			if (data.has_pts()) {
 				channel->ptsInit(data.vpts.v);
 			}
-			if (history) {
-				history->applyDialog(data);
-			}
 		}, [&](const MTPDdialogFolder &) {
 		});
+		session().data().applyDialogs(
+			nullptr,
+			data.vmessages.v,
+			QVector<MTPDialog>(1, data.vdialog));
 		if (_history->peer() == channel) {
 			_history->updateHistoryDownVisibility();
 			_history->preloadHistoryIfNeeded();
@@ -4270,6 +4270,11 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
 	case mtpc_updatePinnedDialogs: {
 		const auto &d = update.c_updatePinnedDialogs();
 		const auto folderId = d.has_folder_id() ? d.vfolder_id.v : 0;
+		const auto loaded = !folderId
+			|| (session().data().folderLoaded(folderId) != nullptr);
+		const auto folder = folderId
+			? session().data().folder(folderId).get()
+			: nullptr;
 		const auto done = [&] {
 			if (!d.has_order()) {
 				return false;
@@ -4280,6 +4285,11 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
 					return !session().data().historyLoaded(
 						peerFromMTP(data.vpeer));
 				}, [&](const MTPDdialogPeerFolder &data) {
+					if (folderId) {
+						LOG(("API Error: "
+							"updatePinnedDialogs has nested folders."));
+						return true;
+					}
 					return !session().data().folderLoaded(data.vfolder_id.v);
 				});
 			};
@@ -4288,17 +4298,23 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
 			if (!allLoaded) {
 				return false;
 			}
-			session().data().applyPinnedChats(folderId, order);
+			session().data().applyPinnedChats(folder, order);
 			return true;
 		}();
 		if (!done) {
-			session().api().requestPinnedDialogs(folderId);
+			session().api().requestPinnedDialogs(folder);
+		}
+		if (!loaded) {
+			session().api().requestDialogEntry(folder);
 		}
 	} break;
 
 	case mtpc_updateDialogPinned: {
 		const auto &d = update.c_updateDialogPinned();
 		const auto folderId = d.has_folder_id() ? d.vfolder_id.v : 0;
+		const auto folder = folderId
+			? session().data().folder(folderId).get()
+			: nullptr;
 		const auto done = d.vpeer.match([&](const MTPDdialogPeer &data) {
 			const auto id = peerFromMTP(data.vpeer);
 			if (const auto history = session().data().historyLoaded(id)) {
@@ -4329,7 +4345,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
 			return false;
 		});
 		if (!done) {
-			session().api().requestPinnedDialogs(folderId);
+			session().api().requestPinnedDialogs(folder);
 		}
 	} break;
 
diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp
index 89085a940..0f07e72e5 100644
--- a/Telegram/SourceFiles/window/window_peer_menu.cpp
+++ b/Telegram/SourceFiles/window/window_peer_menu.cpp
@@ -94,13 +94,13 @@ private:
 
 };
 
-History *FindWastedPin(FolderId folderId) {
-	const auto &order = Auth().data().pinnedChatsOrder(folderId);
+History *FindWastedPin(Data::Folder *folder) {
+	const auto &order = Auth().data().pinnedChatsOrder(folder);
 	for (const auto &pinned : order) {
 		if (const auto history = pinned.history()) {
 			if (history->peer->isChat()
 				&& history->peer->asChat()->isDeactivated()
-				&& !history->inChatList(Dialogs::Mode::All)) {
+				&& !history->inChatList()) {
 				return history;
 			}
 		}
@@ -116,17 +116,16 @@ bool PinnedLimitReached(Dialogs::Key key) {
 	Expects(key.entry()->folderKnown());
 
 	const auto folder = key.entry()->folder();
-	const auto folderId = folder ? folder->id() : 0;
-	const auto pinnedCount = Auth().data().pinnedChatsCount(folderId);
-	const auto pinnedMax = Auth().data().pinnedChatsLimit(folderId);
+	const auto pinnedCount = Auth().data().pinnedChatsCount(folder);
+	const auto pinnedMax = Auth().data().pinnedChatsLimit(folder);
 	if (pinnedCount < pinnedMax) {
 		return false;
 	}
 	// Some old chat, that was converted, maybe is still pinned.
-	if (const auto wasted = FindWastedPin(folderId)) {
+	if (const auto wasted = FindWastedPin(folder)) {
 		Auth().data().setChatPinned(wasted, false);
 		Auth().data().setChatPinned(key, true);
-		Auth().api().savePinnedOrder(folderId);
+		Auth().api().savePinnedOrder(folder);
 	} else {
 		auto errorText = lng_error_pinned_max(
 			lt_count,
diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt
index daeea1868..ed2e3bc14 100644
--- a/Telegram/gyp/telegram_sources.txt
+++ b/Telegram/gyp/telegram_sources.txt
@@ -228,6 +228,8 @@
 <(src_loc)/dialogs/dialogs_layout.h
 <(src_loc)/dialogs/dialogs_list.cpp
 <(src_loc)/dialogs/dialogs_list.h
+<(src_loc)/dialogs/dialogs_main_list.cpp
+<(src_loc)/dialogs/dialogs_main_list.h
 <(src_loc)/dialogs/dialogs_pinned_list.cpp
 <(src_loc)/dialogs/dialogs_pinned_list.h
 <(src_loc)/dialogs/dialogs_row.cpp