From 47ad5ea98ae751744ef9dc8276ac22a72b65e02f Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Mon, 22 Jan 2018 19:42:25 +0300
Subject: [PATCH] Display active feed state in dialogs list.

---
 .../dialogs/dialogs_inner_widget.cpp          | 134 +++++++++--------
 .../dialogs/dialogs_inner_widget.h            |   3 +-
 Telegram/SourceFiles/dialogs/dialogs_key.cpp  |   7 +
 Telegram/SourceFiles/dialogs/dialogs_key.h    |  31 ++++
 .../admin_log/history_admin_log_inner.cpp     |  15 +-
 .../admin_log/history_admin_log_inner.h       |   3 +-
 .../admin_log/history_admin_log_item.cpp      |  22 +--
 .../admin_log/history_admin_log_item.h        |   2 +-
 .../admin_log/history_admin_log_section.cpp   |   4 +
 .../admin_log/history_admin_log_section.h     |  16 +--
 .../history/feed/history_feed_section.cpp     |   4 +
 .../history/feed/history_feed_section.h       |   2 +
 Telegram/SourceFiles/history/history.cpp      |  10 ++
 Telegram/SourceFiles/history/history.h        |  16 ++-
 .../SourceFiles/history/history_widget.cpp    |  53 +++----
 Telegram/SourceFiles/history/history_widget.h |   1 -
 .../history/view/history_view_message.cpp     |  22 +--
 .../SourceFiles/info/info_section_widget.cpp  |   4 +-
 .../SourceFiles/info/info_section_widget.h    |   2 +-
 .../SourceFiles/info/info_wrap_widget.cpp     |  20 ++-
 Telegram/SourceFiles/info/info_wrap_widget.h  |   5 +-
 Telegram/SourceFiles/mainwidget.cpp           | 135 ++++++------------
 Telegram/SourceFiles/mainwidget.h             |  23 +--
 Telegram/SourceFiles/rpl/combine_previous.h   |   2 +-
 Telegram/SourceFiles/rpl/operators_tests.cpp  |  29 ++++
 Telegram/SourceFiles/window/section_widget.h  |   5 +-
 .../SourceFiles/window/window_controller.cpp  |  40 ++++++
 .../SourceFiles/window/window_controller.h    |  12 +-
 .../SourceFiles/window/window_peer_menu.cpp   |   9 +-
 29 files changed, 381 insertions(+), 250 deletions(-)

diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
index 3db32bbde..3661f0608 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
@@ -108,7 +108,10 @@ DialogsInner::DialogsInner(QWidget *parent, not_null<Window::Controller*> contro
 	}, lifetime());
 	subscribe(App::histories().sendActionAnimationUpdated(), [this](const Histories::SendActionAnimationUpdate &update) {
 		auto updateRect = Dialogs::Layout::RowPainter::sendActionAnimationRect(update.width, update.height, getFullWidth(), update.textUpdated);
-		updateDialogRow(update.history, MsgId(0), updateRect, UpdateRowSection::Default | UpdateRowSection::Filtered);
+		updateDialogRow(
+			Dialogs::RowDescriptor(update.history, MsgId(0)),
+			updateRect,
+			UpdateRowSection::Default | UpdateRowSection::Filtered);
 	});
 
 	subscribe(Window::Theme::Background(), [this](const Window::Theme::BackgroundUpdate &data) {
@@ -140,6 +143,15 @@ DialogsInner::DialogsInner(QWidget *parent, not_null<Window::Controller*> contro
 		}
 	}));
 
+	_controller->activeChatEntryValue(
+	) | rpl::combine_previous(
+	) | rpl::start_with_next([=](
+			Dialogs::RowDescriptor previous,
+			Dialogs::RowDescriptor next) {
+		const auto rect = QRect(0, 0, getFullWidth(), st::dialogsRowHeight);
+		updateDialogRow(previous, rect);
+		updateDialogRow(next, rect);
+	}, lifetime());
 	refresh();
 }
 
@@ -181,6 +193,7 @@ void DialogsInner::paintRegion(Painter &p, const QRegion &region, bool paintingO
 	if (!paintingOther) {
 		p.setClipRect(r);
 	}
+	const auto activeEntry = _controller->activeChatEntryCurrent();
 	auto fullWidth = getFullWidth();
 	auto ms = getms();
 	if (_state == State::Default) {
@@ -195,15 +208,7 @@ void DialogsInner::paintRegion(Painter &p, const QRegion &region, bool paintingO
 			p.translate(0, st::dialogsImportantBarHeight);
 		}
 		auto otherStart = rows->size() * st::dialogsRowHeight;
-		// #TODO feeds show
-		const auto active = [&] {
-			if (const auto peer = App::main()->activePeer()) {
-				if (const auto history = App::historyLoaded(peer)) {
-					return Dialogs::Key(history);
-				}
-			}
-			return Dialogs::Key();
-		}();
+		const auto active = activeEntry.key;
 		const auto selected = _menuKey
 			? _menuKey
 			: (isPressed()
@@ -238,7 +243,14 @@ void DialogsInner::paintRegion(Painter &p, const QRegion &region, bool paintingO
 
 					// Skip currently dragged chat to paint it above others after.
 					if (lastPaintedPos != _aboveIndex) {
-						paintDialog(p, row, fullWidth, active, selected, paintingOther, ms);
+						paintDialog(
+							p,
+							row,
+							fullWidth,
+							active,
+							selected,
+							paintingOther,
+							ms);
 					}
 
 					p.translate(0, st::dialogsRowHeight);
@@ -318,19 +330,11 @@ void DialogsInner::paintRegion(Painter &p, const QRegion &region, bool paintingO
 			auto to = ceilclamp(r.y() + r.height() - skip, st::dialogsRowHeight, 0, _filterResults.size());
 			p.translate(0, from * st::dialogsRowHeight);
 			if (from < _filterResults.size()) {
-				// #TODO feeds show
-				const auto activePeer = App::main()->activePeer();
-				const auto activeMsgId = App::main()->activeMsgId();
 				for (; from < to; ++from) {
 					const auto row = _filterResults[from];
 					const auto key = row->key();
-					const auto history = key.history();
-					const auto peer = history ? history->peer.get() : nullptr;
-					const auto active = !activeMsgId
-						&& peer
-						&& activePeer
-						&& ((peer == activePeer)
-							|| (peer->migrateTo() == activePeer));
+					const auto active = (activeEntry.key == key)
+						&& !activeEntry.msgId;
 					const auto selected = _menuKey
 						? (key == _menuKey)
 						: (from == (isPressed()
@@ -363,12 +367,11 @@ void DialogsInner::paintRegion(Painter &p, const QRegion &region, bool paintingO
 			auto to = ceilclamp(r.y() + r.height() - skip, st::dialogsRowHeight, 0, _peerSearchResults.size());
 			p.translate(0, from * st::dialogsRowHeight);
 			if (from < _peerSearchResults.size()) {
-				auto activePeer = App::main()->activePeer();
-				auto activeMsgId = App::main()->activeMsgId();
+				const auto activePeer = activeEntry.key.peer();
 				for (; from < to; ++from) {
 					const auto &result = _peerSearchResults[from];
 					const auto peer = result->peer;
-					const auto active = !activeMsgId
+					const auto active = !activeEntry.msgId
 						&& activePeer
 						&& ((peer == activePeer)
 							|| (peer->migrateTo() == activePeer));
@@ -414,21 +417,27 @@ void DialogsInner::paintRegion(Painter &p, const QRegion &region, bool paintingO
 			auto to = ceilclamp(r.y() + r.height() - skip, st::dialogsRowHeight, 0, _searchResults.size());
 			p.translate(0, from * st::dialogsRowHeight);
 			if (from < _searchResults.size()) {
-				auto activePeer = App::main()->activePeer();
-				auto activeMsgId = App::main()->activeMsgId();
+				const auto activePeer = activeEntry.key.peer();
 				for (; from < to; ++from) {
 					const auto &result = _searchResults[from];
 					const auto item = result->item();
 					const auto peer = item->history()->peer;
 					const auto active = (peer == activePeer
-							&& item->id == activeMsgId)
+							&& item->id == activeEntry.msgId)
 						|| (peer->migrateTo()
 							&& peer->migrateTo() == activePeer
-							&& item->id == -activeMsgId);
+							&& item->id == -activeEntry.msgId);
 					const auto selected = (from == (isPressed()
 						? _searchedPressed
 						: _searchedSelected));
-					Dialogs::Layout::RowPainter::paint(p, result.get(), fullWidth, active, selected, paintingOther, ms);
+					Dialogs::Layout::RowPainter::paint(
+						p,
+						result.get(),
+						fullWidth,
+						active,
+						selected,
+						paintingOther,
+						ms);
 					p.translate(0, st::dialogsRowHeight);
 				}
 			}
@@ -1243,8 +1252,7 @@ void DialogsInner::dlgUpdated(
 
 void DialogsInner::dlgUpdated(not_null<History*> history, MsgId msgId) {
 	updateDialogRow(
-		history,
-		msgId,
+		Dialogs::RowDescriptor(history, msgId),
 		QRect(0, 0, getFullWidth(), st::dialogsRowHeight));
 }
 
@@ -1264,17 +1272,20 @@ void DialogsInner::updateSearchResult(not_null<PeerData*> peer) {
 }
 
 void DialogsInner::updateDialogRow(
-		not_null<History*> history,
-		MsgId msgId,
+		Dialogs::RowDescriptor row,
 		QRect updateRect,
 		UpdateRowSections sections) {
-	auto updateRow = [this, updateRect](int rowTop) {
-		rtlupdate(updateRect.x(), rowTop + updateRect.y(), updateRect.width(), updateRect.height());
+	auto updateRow = [&](int rowTop) {
+		rtlupdate(
+			updateRect.x(),
+			rowTop + updateRect.y(),
+			updateRect.width(),
+			updateRect.height());
 	};
 	if (_state == State::Default) {
 		if (sections & UpdateRowSection::Default) {
-			if (const auto row = shownDialogs()->getRow(history)) {
-				auto position = row->pos();
+			if (const auto dialog = shownDialogs()->getRow(row.key)) {
+				const auto position = dialog->pos();
 				auto top = dialogsOffset();
 				if (base::in_range(position, 0, _pinnedRows.size())) {
 					top += qRound(_pinnedRows[position].yadd.current());
@@ -1283,35 +1294,46 @@ void DialogsInner::updateDialogRow(
 			}
 		}
 	} else if (_state == State::Filtered) {
-		if ((sections & UpdateRowSection::Filtered) && !_filterResults.isEmpty()) {
-			auto index = 0, add = filteredOffset();
-			for_const (auto row, _filterResults) {
-				if (row->history() == history) {
+		if ((sections & UpdateRowSection::Filtered)
+			&& !_filterResults.isEmpty()) {
+			const auto add = filteredOffset();
+			auto index = 0;
+			for (const auto result : _filterResults) {
+				if (result->key() == row.key) {
 					updateRow(add + index * st::dialogsRowHeight);
 					break;
 				}
 				++index;
 			}
 		}
-		if ((sections & UpdateRowSection::PeerSearch) && !_peerSearchResults.empty()) {
-			auto index = 0, add = peerSearchOffset();
-			for_const (auto &result, _peerSearchResults) {
-				if (result->peer == history->peer) {
-					updateRow(add + index * st::dialogsRowHeight);
-					break;
+		if ((sections & UpdateRowSection::PeerSearch)
+			&& !_peerSearchResults.empty()) {
+			if (const auto peer = row.key.peer()) {
+				const auto add = peerSearchOffset();
+				auto index = 0;
+				for (const auto &result : _peerSearchResults) {
+					if (result->peer == peer) {
+						updateRow(add + index * st::dialogsRowHeight);
+						break;
+					}
+					++index;
 				}
-				++index;
 			}
 		}
-		if ((sections & UpdateRowSection::MessageSearch) && !_searchResults.empty()) {
-			auto index = 0, add = searchedOffset();
-			for_const (auto &result, _searchResults) {
-				auto item = result->item();
-				if (item->history() == history && item->id == msgId) {
-					updateRow(add + index * st::dialogsRowHeight);
-					break;
+		if ((sections & UpdateRowSection::MessageSearch)
+			&& !_searchResults.empty()) {
+			if (const auto history = row.key.history()) {
+				const auto add = searchedOffset();
+				auto index = 0;
+				for (const auto &result : _searchResults) {
+					auto item = result->item();
+					if (item->history() == history
+						&& item->id == row.msgId) {
+						updateRow(add + index * st::dialogsRowHeight);
+						break;
+					}
+					++index;
 				}
-				++index;
 			}
 		}
 	}
diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h
index f58a015f5..f76b34e25 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h
+++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h
@@ -207,8 +207,7 @@ private:
 
 	void updateSearchResult(not_null<PeerData*> peer);
 	void updateDialogRow(
-		not_null<History*> history,
-		MsgId msgId,
+		Dialogs::RowDescriptor row,
 		QRect updateRect,
 		UpdateRowSections sections = UpdateRowSection::All);
 
diff --git a/Telegram/SourceFiles/dialogs/dialogs_key.cpp b/Telegram/SourceFiles/dialogs/dialogs_key.cpp
index 1e0e2f35b..3e64063af 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_key.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_key.cpp
@@ -35,4 +35,11 @@ Data::Feed *Key::feed() const {
 	return nullptr;
 }
 
+PeerData *Key::peer() const {
+	if (const auto p = base::get_if<not_null<History*>>(&_value)) {
+		return (*p)->peer;
+	}
+	return nullptr;
+}
+
 } // namespace Dialogs
diff --git a/Telegram/SourceFiles/dialogs/dialogs_key.h b/Telegram/SourceFiles/dialogs/dialogs_key.h
index 94c3517b9..7a3f94af4 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_key.h
+++ b/Telegram/SourceFiles/dialogs/dialogs_key.h
@@ -37,6 +37,7 @@ public:
 	not_null<Entry*> entry() const;
 	History *history() const;
 	Data::Feed *feed() const;
+	PeerData *peer() const;
 
 	inline bool operator<(const Key &other) const {
 		return _value < other._value;
@@ -80,6 +81,36 @@ struct RowDescriptor {
 
 	Key key;
 	MsgId msgId = 0;
+
 };
 
+inline bool operator==(const RowDescriptor &a, const RowDescriptor &b) {
+	return (a.key == b.key) && (a.msgId == b.msgId);
+}
+
+inline bool operator!=(const RowDescriptor &a, const RowDescriptor &b) {
+	return !(a == b);
+}
+
+inline bool operator<(const RowDescriptor &a, const RowDescriptor &b) {
+	if (a.key < b.key) {
+		return true;
+	} else if (a.key > b.key) {
+		return false;
+	}
+	return a.msgId < b.msgId;
+}
+
+inline bool operator>(const RowDescriptor &a, const RowDescriptor &b) {
+	return (b < a);
+}
+
+inline bool operator<=(const RowDescriptor &a, const RowDescriptor &b) {
+	return !(b < a);
+}
+
+inline bool operator>=(const RowDescriptor &a, const RowDescriptor &b) {
+	return !(a < b);
+}
+
 } // namespace Dialogs
diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp
index 87926ef47..a26a94845 100644
--- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp
+++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp
@@ -210,7 +210,11 @@ InnerWidget::InnerWidget(
 , _channel(channel)
 , _history(App::history(channel))
 , _scrollDateCheck([this] { scrollDateCheck(); })
-, _emptyText(st::historyAdminLogEmptyWidth - st::historyAdminLogEmptyPadding.left() - st::historyAdminLogEmptyPadding.left()) {
+, _emptyText(
+	st::historyAdminLogEmptyWidth
+	- st::historyAdminLogEmptyPadding.left()
+	- st::historyAdminLogEmptyPadding.left())
+, _idManager(_history->adminLogIdManager()) {
 	setMouseTracking(true);
 	_scrollDateHideTimer.setCallback([this] { scrollDateHideByTimer(); });
 	Auth().data().viewRepaintRequest(
@@ -511,7 +515,9 @@ void InnerWidget::saveState(not_null<SectionMemento*> memento) {
 void InnerWidget::restoreState(not_null<SectionMemento*> memento) {
 	_items = memento->takeItems();
 	_itemsByIds = memento->takeItemsByIds();
-	_idManager = memento->takeIdManager();
+	if (auto manager = memento->takeIdManager()) {
+		_idManager = std::move(manager);
+	}
 	_admins = memento->takeAdmins();
 	_adminsCanEdit = memento->takeAdminsCanEdit();
 	_filter = memento->takeFilter();
@@ -601,7 +607,7 @@ void InnerWidget::addEvents(Direction direction, const QVector<MTPChannelAdminLo
 		GenerateItems(
 			this,
 			_history,
-			_idManager,
+			_idManager.get(),
 			data,
 			addOne);
 		if (count > 1) {
@@ -805,7 +811,8 @@ void InnerWidget::clearAfterFilterChange() {
 	_filterChanged = false;
 	_items.clear();
 	_itemsByIds.clear();
-	_idManager = LocalIdManager();
+	_idManager = nullptr;
+	_idManager = _history->adminLogIdManager();
 	updateEmptyText();
 	updateSize();
 }
diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h
index 159d7263b..43082b060 100644
--- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h
+++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h
@@ -198,7 +198,6 @@ private:
 	int _itemsWidth = 0;
 	int _itemsHeight = 0;
 
-	LocalIdManager _idManager;
 	int _minHeight = 0;
 	int _visibleTop = 0;
 	int _visibleBottom = 0;
@@ -251,6 +250,8 @@ private:
 	std::vector<not_null<UserData*>> _adminsCanEdit;
 	base::lambda<void(FilterValue &&filter)> _showFilterCallback;
 
+	std::shared_ptr<LocalIdManager> _idManager;
+
 };
 
 } // namespace AdminLog
diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp
index 16e826d83..861607303 100644
--- a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp
+++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp
@@ -312,7 +312,7 @@ OwnedItem::~OwnedItem() {
 void GenerateItems(
 		not_null<HistoryView::ElementDelegate*> delegate,
 		not_null<History*> history,
-		LocalIdManager &idManager,
+		not_null<LocalIdManager*> idManager,
 		const MTPDchannelAdminLogEvent &event,
 		base::lambda<void(OwnedItem item)> callback) {
 	Expects(history->peer->isChannel());
@@ -334,7 +334,7 @@ void GenerateItems(
 	auto addSimpleServiceMessage = [&](const QString &text, PhotoData *photo = nullptr) {
 		auto message = HistoryService::PreparedText { text };
 		message.links.push_back(fromLink);
-		addPart(new HistoryService(history, idManager.next(), ::date(date), message, 0, peerToUser(from->id), photo));
+		addPart(new HistoryService(history, idManager->next(), ::date(date), message, 0, peerToUser(from->id), photo));
 	};
 
 	auto createChangeTitle = [&](const MTPDchannelAdminLogEventActionChangeTitle &action) {
@@ -355,7 +355,7 @@ void GenerateItems(
 		auto bodyReplyTo = 0;
 		auto bodyViaBotId = 0;
 		auto newDescription = PrepareText(newValue, QString());
-		auto body = new HistoryMessage(history, idManager.next(), bodyFlags, bodyReplyTo, bodyViaBotId, ::date(date), peerToUser(from->id), QString(), newDescription);
+		auto body = new HistoryMessage(history, idManager->next(), bodyFlags, bodyReplyTo, bodyViaBotId, ::date(date), peerToUser(from->id), QString(), newDescription);
 		if (!oldValue.isEmpty()) {
 			auto oldDescription = PrepareText(oldValue, QString());
 			body->addLogEntryOriginal(id, lang(lng_admin_log_previous_description), oldDescription);
@@ -376,7 +376,7 @@ void GenerateItems(
 		auto bodyReplyTo = 0;
 		auto bodyViaBotId = 0;
 		auto newLink = newValue.isEmpty() ? TextWithEntities() : PrepareText(Messenger::Instance().createInternalLinkFull(newValue), QString());
-		auto body = new HistoryMessage(history, idManager.next(), bodyFlags, bodyReplyTo, bodyViaBotId, ::date(date), peerToUser(from->id), QString(), newLink);
+		auto body = new HistoryMessage(history, idManager->next(), bodyFlags, bodyReplyTo, bodyViaBotId, ::date(date), peerToUser(from->id), QString(), newLink);
 		if (!oldValue.isEmpty()) {
 			auto oldLink = PrepareText(Messenger::Instance().createInternalLinkFull(oldValue), QString());
 			body->addLogEntryOriginal(id, lang(lng_admin_log_previous_link), oldLink);
@@ -427,7 +427,7 @@ void GenerateItems(
 			addPart(history->createItem(
 				PrepareLogMessage(
 					action.vmessage,
-					idManager.next(),
+					idManager->next(),
 					date.v),
 				detachExistingItem));
 		}
@@ -447,7 +447,7 @@ void GenerateItems(
 		auto body = history->createItem(
 			PrepareLogMessage(
 				action.vnew_message,
-				idManager.next(),
+				idManager->next(),
 				date.v),
 			detachExistingItem);
 		if (oldValue.text.isEmpty()) {
@@ -463,7 +463,7 @@ void GenerateItems(
 
 		auto detachExistingItem = false;
 		addPart(history->createItem(
-			PrepareLogMessage(action.vmessage, idManager.next(), date.v),
+			PrepareLogMessage(action.vmessage, idManager->next(), date.v),
 			detachExistingItem));
 	};
 
@@ -486,7 +486,7 @@ void GenerateItems(
 		auto bodyReplyTo = 0;
 		auto bodyViaBotId = 0;
 		auto bodyText = GenerateParticipantChangeText(channel, action.vparticipant);
-		addPart(new HistoryMessage(history, idManager.next(), bodyFlags, bodyReplyTo, bodyViaBotId, ::date(date), peerToUser(from->id), QString(), bodyText));
+		addPart(new HistoryMessage(history, idManager->next(), bodyFlags, bodyReplyTo, bodyViaBotId, ::date(date), peerToUser(from->id), QString(), bodyText));
 	};
 
 	auto createParticipantToggleBan = [&](const MTPDchannelAdminLogEventActionParticipantToggleBan &action) {
@@ -494,7 +494,7 @@ void GenerateItems(
 		auto bodyReplyTo = 0;
 		auto bodyViaBotId = 0;
 		auto bodyText = GenerateParticipantChangeText(channel, action.vnew_participant, &action.vprev_participant);
-		addPart(new HistoryMessage(history, idManager.next(), bodyFlags, bodyReplyTo, bodyViaBotId, ::date(date), peerToUser(from->id), QString(), bodyText));
+		addPart(new HistoryMessage(history, idManager->next(), bodyFlags, bodyReplyTo, bodyViaBotId, ::date(date), peerToUser(from->id), QString(), bodyText));
 	};
 
 	auto createParticipantToggleAdmin = [&](const MTPDchannelAdminLogEventActionParticipantToggleAdmin &action) {
@@ -502,7 +502,7 @@ void GenerateItems(
 		auto bodyReplyTo = 0;
 		auto bodyViaBotId = 0;
 		auto bodyText = GenerateParticipantChangeText(channel, action.vnew_participant, &action.vprev_participant);
-		addPart(new HistoryMessage(history, idManager.next(), bodyFlags, bodyReplyTo, bodyViaBotId, ::date(date), peerToUser(from->id), QString(), bodyText));
+		addPart(new HistoryMessage(history, idManager->next(), bodyFlags, bodyReplyTo, bodyViaBotId, ::date(date), peerToUser(from->id), QString(), bodyText));
 	};
 
 	auto createChangeStickerSet = [&](const MTPDchannelAdminLogEventActionChangeStickerSet &action) {
@@ -523,7 +523,7 @@ void GenerateItems(
 			auto message = HistoryService::PreparedText { text };
 			message.links.push_back(fromLink);
 			message.links.push_back(setLink);
-			addPart(new HistoryService(history, idManager.next(), ::date(date), message, 0, peerToUser(from->id)));
+			addPart(new HistoryService(history, idManager->next(), ::date(date), message, 0, peerToUser(from->id)));
 		}
 	};
 
diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.h b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.h
index ed20f43fa..7f238be38 100644
--- a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.h
+++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.h
@@ -20,7 +20,7 @@ class LocalIdManager;
 void GenerateItems(
 	not_null<HistoryView::ElementDelegate*> delegate,
 	not_null<History*> history,
-	LocalIdManager &idManager,
+	not_null<LocalIdManager*> idManager,
 	const MTPDchannelAdminLogEvent &event,
 	base::lambda<void(OwnedItem item)> callback);
 
diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp
index 67505191a..49f00f0bf 100644
--- a/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp
+++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp
@@ -279,6 +279,10 @@ not_null<ChannelData*> Widget::channel() const {
 	return _inner->channel();
 }
 
+Dialogs::RowDescriptor Widget::activeChat() const {
+	return { App::history(channel()), MsgId(0) };
+}
+
 QPixmap Widget::grabForShowAnimation(const Window::SectionSlideParams &params) {
 	if (params.withTopBarShadow) _fixedBarShadow->hide();
 	auto result = Ui::GrabWidget(this);
diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_section.h b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.h
index 1f5d8a757..abdb6c240 100644
--- a/Telegram/SourceFiles/history/admin_log/history_admin_log_section.h
+++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.h
@@ -52,12 +52,6 @@ public:
 	LocalIdManager() = default;
 	LocalIdManager(const LocalIdManager &other) = delete;
 	LocalIdManager &operator=(const LocalIdManager &other) = delete;
-	LocalIdManager(LocalIdManager &&other) : _counter(std::exchange(other._counter, ServerMaxMsgId)) {
-	}
-	LocalIdManager &operator=(LocalIdManager &&other) {
-		_counter = std::exchange(other._counter, ServerMaxMsgId);
-		return *this;
-	}
 	MsgId next() {
 		return ++_counter;
 	}
@@ -72,9 +66,7 @@ public:
 	Widget(QWidget *parent, not_null<Window::Controller*> controller, not_null<ChannelData*> channel);
 
 	not_null<ChannelData*> channel() const;
-	PeerData *activePeer() const override {
-		return channel();
-	}
+	Dialogs::RowDescriptor activeChat() const override;
 
 	bool hasTopBarShadow() const override {
 		return true;
@@ -173,7 +165,7 @@ public:
 	void setSearchQuery(QString &&query) {
 		_searchQuery = std::move(query);
 	}
-	void setIdManager(LocalIdManager &&manager) {
+	void setIdManager(std::shared_ptr<LocalIdManager> &&manager) {
 		_idManager = std::move(manager);
 	}
 	std::vector<OwnedItem> takeItems() {
@@ -182,7 +174,7 @@ public:
 	std::map<uint64, not_null<Element*>> takeItemsByIds() {
 		return std::move(_itemsByIds);
 	}
-	LocalIdManager takeIdManager() {
+	std::shared_ptr<LocalIdManager> takeIdManager() {
 		return std::move(_idManager);
 	}
 	bool upLoaded() const {
@@ -207,7 +199,7 @@ private:
 	std::map<uint64, not_null<Element*>> _itemsByIds;
 	bool _upLoaded = false;
 	bool _downLoaded = true;
-	LocalIdManager _idManager;
+	std::shared_ptr<LocalIdManager> _idManager;
 	FilterValue _filter;
 	QString _searchQuery;
 
diff --git a/Telegram/SourceFiles/history/feed/history_feed_section.cpp b/Telegram/SourceFiles/history/feed/history_feed_section.cpp
index af2a9f5b5..1cee74505 100644
--- a/Telegram/SourceFiles/history/feed/history_feed_section.cpp
+++ b/Telegram/SourceFiles/history/feed/history_feed_section.cpp
@@ -93,6 +93,10 @@ Widget::Widget(
 	}, lifetime());
 }
 
+Dialogs::RowDescriptor Widget::activeChat() const {
+	return Dialogs::RowDescriptor(_feed, MsgId(0));
+}
+
 void Widget::updateAdaptiveLayout() {
 	_topBarShadow->moveToLeft(
 		Adaptive::OneColumn() ? 0 : st::lineWidth,
diff --git a/Telegram/SourceFiles/history/feed/history_feed_section.h b/Telegram/SourceFiles/history/feed/history_feed_section.h
index c4964b304..cf2ed7f6d 100644
--- a/Telegram/SourceFiles/history/feed/history_feed_section.h
+++ b/Telegram/SourceFiles/history/feed/history_feed_section.h
@@ -36,6 +36,8 @@ public:
 		not_null<Window::Controller*> controller,
 		not_null<Data::Feed*> feed);
 
+	Dialogs::RowDescriptor activeChat() const override;
+
 	bool hasTopBarShadow() const override {
 		return true;
 	}
diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp
index 86d337845..aad620e44 100644
--- a/Telegram/SourceFiles/history/history.cpp
+++ b/Telegram/SourceFiles/history/history.cpp
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "history/history.h"
 
 #include "history/view/history_view_element.h"
+#include "history/admin_log/history_admin_log_section.h"
 #include "history/history_message.h"
 #include "history/history_media_types.h"
 #include "history/history_service.h"
@@ -1924,6 +1925,15 @@ void History::getNextFirstUnreadMessage() {
 	_firstUnreadView = nullptr;
 }
 
+std::shared_ptr<AdminLog::LocalIdManager> History::adminLogIdManager() {
+	if (const auto strong = _adminLogIdManager.lock()) {
+		return strong;
+	}
+	auto result = std::make_shared<AdminLog::LocalIdManager>();
+	_adminLogIdManager = result;
+	return result;
+}
+
 QDateTime History::adjustChatListDate() const {
 	const auto result = chatsListDate();
 	if (const auto draft = cloudDraft()) {
diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h
index cdda66176..99b491a97 100644
--- a/Telegram/SourceFiles/history/history.h
+++ b/Telegram/SourceFiles/history/history.h
@@ -19,16 +19,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 class History;
 
+namespace HistoryView {
+class Element;
+} // namespace HistoryView
+
+namespace AdminLog {
+class LocalIdManager;
+} // namespace AdminLog
+
 enum NewMessageType : char {
 	NewMessageUnread,
 	NewMessageLast,
 	NewMessageExisting,
 };
 
-namespace HistoryView {
-class Element;
-} // namespace HistoryView
-
 class Histories {
 public:
 	using Map = QHash<PeerId, History*>;
@@ -331,6 +335,8 @@ public:
 	// of the displayed window relative to the history start coordinate
 	void countScrollState(int top);
 
+	std::shared_ptr<AdminLog::LocalIdManager> adminLogIdManager();
+
 	virtual ~History();
 
 	// Still public data.
@@ -503,6 +509,8 @@ private:
 
 	int _pinnedIndex = 0; // > 0 for pinned dialogs
 
+	std::weak_ptr<AdminLog::LocalIdManager> _adminLogIdManager;
+
  };
 
 class HistoryJoined;
diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp
index 92b03be30..827d0dac3 100644
--- a/Telegram/SourceFiles/history/history_widget.cpp
+++ b/Telegram/SourceFiles/history/history_widget.cpp
@@ -1371,7 +1371,9 @@ bool HistoryWidget::notify_switchInlineBotButtonReceived(const QString &query, U
 			return true;
 		}
 	} else if (auto bot = _peer ? _peer->asUser() : nullptr) {
-		PeerId toPeerId = bot->botInfo ? bot->botInfo->inlineReturnPeerId : 0;
+		const auto toPeerId = bot->botInfo
+			? bot->botInfo->inlineReturnPeerId
+			: PeerId(0);
 		if (!toPeerId) {
 			return false;
 		}
@@ -1620,11 +1622,6 @@ void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool re
 			bool canShowNow = _history->isReadyFor(showAtMsgId);
 			if (!canShowNow) {
 				delayedShowAt(showAtMsgId);
-
-				if (wasHistory) {
-					App::main()->dlgUpdated(wasHistory, wasMsgId);
-				}
-				emit historyShown(_history, _showAtMsgId);
 			} else {
 				_history->forgetScrollState();
 				if (_migrated) {
@@ -1655,12 +1652,18 @@ void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool re
 			_topBar->update();
 			update();
 
-			if (startBot && _peer->isUser() && _peer->asUser()->botInfo) {
-				if (wasHistory) _peer->asUser()->botInfo->inlineReturnPeerId = wasHistory->peer->id;
-				onBotStart();
-				_history->clearLocalDraft();
-				applyDraft();
-				_send->finishAnimating();
+			if (const auto user = _peer->asUser()) {
+				if (const auto &info = user->botInfo) {
+					if (startBot) {
+						if (wasHistory) {
+							info->inlineReturnPeerId = wasHistory->peer->id;
+						}
+						onBotStart();
+						_history->clearLocalDraft();
+						applyDraft();
+						_send->finishAnimating();
+					}
+				}
 			}
 			return;
 		}
@@ -1802,9 +1805,15 @@ void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool re
 
 		connect(_scroll, SIGNAL(geometryChanged()), _list, SLOT(onParentGeometryChanged()));
 
-		if (startBot && _peer->isUser() && _peer->asUser()->botInfo) {
-			if (wasHistory) _peer->asUser()->botInfo->inlineReturnPeerId = wasHistory->peer->id;
-			onBotStart();
+		if (const auto user = _peer->asUser()) {
+			if (const auto &info = user->botInfo) {
+				if (startBot) {
+					if (wasHistory) {
+						info->inlineReturnPeerId = wasHistory->peer->id;
+					}
+					onBotStart();
+				}
+			}
 		}
 		unreadCountChanged(_history); // set _historyDown badge.
 	} else {
@@ -1815,16 +1824,11 @@ void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool re
 	updateForwarding();
 	updateOverStates(mapFromGlobal(QCursor::pos()));
 
-	if (App::wnd()) QTimer::singleShot(0, App::wnd(), SLOT(setInnerFocus()));
-
-	if (wasHistory) {
-		App::main()->dlgUpdated(wasHistory, wasMsgId);
-	}
-	emit historyShown(_history, _showAtMsgId);
+	crl::on_main(App::wnd(), [] { App::wnd()->setInnerFocus(); });
 
 	controller()->historyPeer = _peer;
-	if (_peer) {
-		controller()->activePeer = _peer;
+	if (_history) {
+		controller()->setActiveChatEntry({ _history, _showAtMsgId });
 	}
 	update();
 }
@@ -3038,9 +3042,8 @@ void HistoryWidget::setMsgId(MsgId showAtMsgId) {
 		auto wasMsgId = _showAtMsgId;
 		_showAtMsgId = showAtMsgId;
 		if (_history) {
-			App::main()->dlgUpdated(_history, wasMsgId);
+			controller()->setActiveChatEntry({ _history, _showAtMsgId });
 		}
-		emit historyShown(_history, _showAtMsgId);
 	}
 }
 
diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h
index 117c427fe..11f6d2d25 100644
--- a/Telegram/SourceFiles/history/history_widget.h
+++ b/Telegram/SourceFiles/history/history_widget.h
@@ -367,7 +367,6 @@ protected:
 
 signals:
 	void cancelled();
-	void historyShown(History *history, MsgId atMsgId);
 
 public slots:
 	void onCancel();
diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp
index 5bea47ab1..3ff99c4a8 100644
--- a/Telegram/SourceFiles/history/view/history_view_message.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_message.cpp
@@ -1304,6 +1304,9 @@ bool Message::hasBubble() const {
 }
 
 bool Message::hasFastReply() const {
+	if (context() != Context::History) {
+		return false;
+	}
 	const auto peer = data()->history()->peer;
 	return !hasOutLayout() && (peer->isChat() || peer->isMegagroup());
 }
@@ -1491,23 +1494,24 @@ TextSelection Message::unskipTextSelection(TextSelection selection) const {
 
 QRect Message::countGeometry() const {
 	const auto item = message();
+	const auto media = this->media();
+	const auto mediaWidth = media ? media->width() : width();
 	const auto outbg = hasOutLayout();
+	const auto availableWidth = width()
+		- st::msgMargin.left()
+		- st::msgMargin.right();
 	auto contentLeft = (outbg && !Adaptive::ChatWide())
 		? st::msgMargin.right()
 		: st::msgMargin.left();
+	auto contentWidth = availableWidth;
 	if (hasFromPhoto()) {
 		contentLeft += st::msgPhotoSkip;
+		if (displayRightAction()) {
+			contentWidth -= st::msgPhotoSkip;
+		}
 	//} else if (!Adaptive::Wide() && !out() && !fromChannel() && st::msgPhotoSkip - (hmaxwidth - hwidth) > 0) {
 	//	contentLeft += st::msgPhotoSkip - (hmaxwidth - hwidth);
 	}
-
-	const auto media = this->media();
-	const auto mediaWidth = media ? media->width() : width();
-	const auto availableWidth = width() - st::msgMargin.left() - st::msgMargin.right();
-	auto contentWidth = availableWidth;
-	if (item->history()->peer->isSelf() && !outbg) {
-		contentWidth -= st::msgPhotoSkip;
-	}
 	accumulate_min(contentWidth, maxWidth());
 	accumulate_min(contentWidth, st::msgMaxWidth);
 	if (mediaWidth < contentWidth) {
@@ -1546,7 +1550,7 @@ int Message::resizeContentGetHeight(int newWidth) {
 
 	// This code duplicates countGeometry() but also resizes media.
 	auto contentWidth = newWidth - (st::msgMargin.left() + st::msgMargin.right());
-	if (item->history()->peer->isSelf() && !hasOutLayout()) {
+	if (hasFromPhoto() && displayRightAction()) {
 		contentWidth -= st::msgPhotoSkip;
 	}
 	accumulate_min(contentWidth, maxWidth());
diff --git a/Telegram/SourceFiles/info/info_section_widget.cpp b/Telegram/SourceFiles/info/info_section_widget.cpp
index e86d2fb4d..8124ebb30 100644
--- a/Telegram/SourceFiles/info/info_section_widget.cpp
+++ b/Telegram/SourceFiles/info/info_section_widget.cpp
@@ -44,8 +44,8 @@ void SectionWidget::init() {
 	}, _content->lifetime());
 }
 
-PeerData *SectionWidget::activePeer() const {
-	return _content->activePeer();
+Dialogs::RowDescriptor SectionWidget::activeChat() const {
+	return _content->activeChat();
 }
 
 bool SectionWidget::hasTopBarShadow() const {
diff --git a/Telegram/SourceFiles/info/info_section_widget.h b/Telegram/SourceFiles/info/info_section_widget.h
index 5c6d1d791..86a50912f 100644
--- a/Telegram/SourceFiles/info/info_section_widget.h
+++ b/Telegram/SourceFiles/info/info_section_widget.h
@@ -35,7 +35,7 @@ public:
 		Wrap wrap,
 		not_null<MoveMemento*> memento);
 
-	PeerData *activePeer() const override;
+	Dialogs::RowDescriptor activeChat() const override;
 
 	bool hasTopBarShadow() const override;
 	QPixmap grabForShowAnimation(
diff --git a/Telegram/SourceFiles/info/info_wrap_widget.cpp b/Telegram/SourceFiles/info/info_wrap_widget.cpp
index 178fbeeba..a7c413a26 100644
--- a/Telegram/SourceFiles/info/info_wrap_widget.cpp
+++ b/Telegram/SourceFiles/info/info_wrap_widget.cpp
@@ -104,17 +104,25 @@ void WrapWidget::startInjectingActivePeerProfiles() {
 	using namespace rpl::mappers;
 	rpl::combine(
 		_wrap.value(),
-		_controller->parentController()->activePeer.value()
+		_controller->parentController()->activeChatValue()
 	) | rpl::filter(
-		(_1 == Wrap::Side) && (_2 != nullptr)
+		(_1 == Wrap::Side) && _2
 	) | rpl::map(
 		_2
-	) | rpl::start_with_next([this](not_null<PeerData*> peer) {
-		injectActivePeerProfile(peer);
+	) | rpl::start_with_next([this](Dialogs::Key key) {
+		injectActiveProfile(key);
 	}, lifetime());
 
 }
 
+void WrapWidget::injectActiveProfile(Dialogs::Key key) {
+	if (const auto peer = key.peer()) {
+		injectActivePeerProfile(peer);
+	} else if (const auto feed = key.feed()) {
+		// #TODO feed profile
+	}
+}
+
 void WrapWidget::injectActivePeerProfile(not_null<PeerData*> peer) {
 	const auto firstPeerId = hasStackHistory()
 		? _historyStack.front().section->peerId()
@@ -169,6 +177,10 @@ not_null<PeerData*> WrapWidget::peer() const {
 	return _controller->peer();
 }
 
+Dialogs::RowDescriptor WrapWidget::activeChat() const {
+	return Dialogs::RowDescriptor(App::history(peer()), MsgId(0));
+}
+
 // This was done for tabs support.
 //
 //void WrapWidget::createTabs() {
diff --git a/Telegram/SourceFiles/info/info_wrap_widget.h b/Telegram/SourceFiles/info/info_wrap_widget.h
index 19aa7e66c..0963a9c6f 100644
--- a/Telegram/SourceFiles/info/info_wrap_widget.h
+++ b/Telegram/SourceFiles/info/info_wrap_widget.h
@@ -78,9 +78,7 @@ public:
 		not_null<Memento*> memento);
 
 	not_null<PeerData*> peer() const;
-	PeerData *activePeer() const override {
-		return peer();
-	}
+	Dialogs::RowDescriptor activeChat() const override;
 	Wrap wrap() const {
 		return _wrap.current();
 	}
@@ -140,6 +138,7 @@ private:
 	struct StackItem;
 
 	void startInjectingActivePeerProfiles();
+	void injectActiveProfile(Dialogs::Key key);
 	void injectActivePeerProfile(not_null<PeerData*> peer);
 	void restoreHistoryStack(
 		std::vector<std::unique_ptr<ContentMemento>> stack);
diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp
index 71a752d4a..31e75f7b6 100644
--- a/Telegram/SourceFiles/mainwidget.cpp
+++ b/Telegram/SourceFiles/mainwidget.cpp
@@ -223,7 +223,6 @@ MainWidget::MainWidget(
 	connect(&_byPtsTimer, SIGNAL(timeout()), this, SLOT(onGetDifferenceTimeByPts()));
 	connect(&_byMinChannelTimer, SIGNAL(timeout()), this, SLOT(getDifference()));
 	connect(&_failDifferenceTimer, SIGNAL(timeout()), this, SLOT(onGetDifferenceTimeAfterFail()));
-	connect(_history, SIGNAL(historyShown(History*,MsgId)), this, SLOT(onHistoryShown(History*,MsgId)));
 	connect(&updateNotifySettingTimer, SIGNAL(timeout()), this, SLOT(onUpdateNotifySettings()));
 	subscribe(Media::Player::Updated(), [this](const AudioMsgId &audioId) {
 		if (audioId.type() != AudioMsgId::Type::Video) {
@@ -250,15 +249,16 @@ MainWidget::MainWidget(
 	});
 
 	using namespace rpl::mappers;
-	_controller->activePeer.value(
-	) | rpl::map([](PeerData *peer) {
+	_controller->activeChatValue(
+	) | rpl::map([](Dialogs::Key key) {
+		const auto peer = key.peer();
 		auto canWrite = peer
 			? Data::CanWriteValue(peer)
 			: rpl::single(false);
-		return std::move(canWrite) | rpl::map(tuple(peer, _1));
+		return std::move(canWrite) | rpl::map(tuple(key, _1));
 	}) | rpl::flatten_latest(
-	) | rpl::start_with_next([this](PeerData *peer, bool canWrite) {
-		updateThirdColumnToCurrentPeer(peer, canWrite);
+	) | rpl::start_with_next([this](Dialogs::Key key, bool canWrite) {
+		updateThirdColumnToCurrentChat(key, canWrite);
 	}, lifetime());
 
 	QCoreApplication::instance()->installEventFilter(this);
@@ -813,7 +813,6 @@ void MainWidget::noHider(HistoryHider *destroyed) {
 				_forwardConfirm->closeBox();
 				_forwardConfirm = nullptr;
 			}
-			onHistoryShown(_history->history(), _history->msgId());
 			if (_mainSection || (_history->peer() && _history->peer()->id)) {
 				auto animationParams = ([this] {
 					if (_mainSection) {
@@ -851,7 +850,6 @@ void MainWidget::hiderLayer(object_ptr<HistoryHider> h) {
 		_hider->hide();
 		auto animationParams = prepareDialogsAnimation();
 
-		onHistoryShown(nullptr, 0);
 		if (_mainSection) {
 			_mainSection->hide();
 		} else {
@@ -1039,11 +1037,13 @@ void MainWidget::removeDialog(Dialogs::Key key) {
 	_dialogs->removeDialog(key);
 }
 
-void MainWidget::deleteConversation(PeerData *peer, bool deleteHistory) {
-	if (activePeer() == peer) {
+void MainWidget::deleteConversation(
+		not_null<PeerData*> peer,
+		bool deleteHistory) {
+	if (_controller->activeChatCurrent().peer() == peer) {
 		Ui::showChatsList();
 	}
-	if (auto history = App::historyLoaded(peer->id)) {
+	if (const auto history = App::historyLoaded(peer->id)) {
 		Auth().data().setPinnedDialog(history, false);
 		removeDialog(history);
 		if (peer->isMegagroup() && peer->asChannel()->mgInfo->migrateFromPtr) {
@@ -2163,7 +2163,7 @@ void MainWidget::ui_showPeerHistory(
 		}
 	}
 
-	auto wasActivePeer = activePeer();
+	const auto wasActivePeer = _controller->activeChatCurrent().peer();
 	if (params.activation != anim::activation::background) {
 		Ui::hideSettingsAndLayer();
 	}
@@ -2200,9 +2200,7 @@ void MainWidget::ui_showPeerHistory(
 
 	auto animationParams = animatedShow() ? prepareHistoryAnimation(peerId) : Window::SectionSlideParams();
 
-	if (back || (way == Way::ClearStack)) {
-		clearEntryInStack();
-	} else {
+	if (!back && (way != Way::ClearStack)) {
 		// This may modify the current section, for example remove its contents.
 		saveSectionInStack();
 	}
@@ -2232,14 +2230,17 @@ void MainWidget::ui_showPeerHistory(
 			}
 		}
 	} else {
-		if (!noPeer && wasActivePeer != activePeer()) {
-			if (activePeer()->isChannel()) {
-				activePeer()->asChannel()->ptsWaitingForShortPoll(
+		const auto nowActivePeer = _controller->activeChatCurrent().peer();
+		if (nowActivePeer && nowActivePeer != wasActivePeer) {
+			if (const auto channel = nowActivePeer->asChannel()) {
+				channel->ptsWaitingForShortPoll(
 					WaitForChannelGetDifference);
 			}
-			_viewsIncremented.remove(activePeer());
+			_viewsIncremented.remove(nowActivePeer);
+		}
+		if (Adaptive::OneColumn() && !_dialogs->isHidden()) {
+			_dialogs->hide();
 		}
-		if (Adaptive::OneColumn() && !_dialogs->isHidden()) _dialogs->hide();
 		if (!_a_show.animating()) {
 			if (!animationParams.oldContentCache.isNull()) {
 				_history->showAnimated(
@@ -2255,9 +2256,6 @@ void MainWidget::ui_showPeerHistory(
 			}
 		}
 	}
-	//if (wasActivePeer && wasActivePeer->isChannel() && activePeer() != wasActivePeer) {
-	//	wasActivePeer->asChannel()->ptsWaitingForShortPoll(false);
-	//}
 
 	if (!_dialogs->isHidden()) {
 		if (!back) {
@@ -2268,8 +2266,8 @@ void MainWidget::ui_showPeerHistory(
 		_dialogs->update();
 	}
 
-	if (!peerId) {
-		_controller->activePeer = nullptr;
+	if (noPeer) {
+		_controller->setActiveChatEntry(Dialogs::Key());
 	}
 
 	checkFloatPlayerVisibility();
@@ -2299,34 +2297,6 @@ PeerData *MainWidget::peer() {
 	return _history->peer();
 }
 
-PeerData *MainWidget::activePeer() {
-	if (const auto history = _history->history()) {
-		return history->peer;
-	} else if (_historyInStack) {
-		return _historyInStack->peer;
-	}
-	return nullptr;
-}
-
-MsgId MainWidget::activeMsgId() {
-	return _history->peer() ? _history->msgId() : _msgIdInStack;
-}
-
-void MainWidget::setEntryInStack(not_null<History*> history, MsgId msgId) {
-	setEntryInStackValues(history, msgId);
-}
-
-void MainWidget::clearEntryInStack() {
-	setEntryInStackValues(nullptr, 0);
-}
-
-void MainWidget::setEntryInStackValues(History *history, MsgId msgId) {
-	updateCurrentChatListEntry();
-	_historyInStack = history;
-	_msgIdInStack = msgId;
-	updateCurrentChatListEntry();
-}
-
 void MainWidget::saveSectionInStack() {
 	if (_mainSection) {
 		if (auto memento = _mainSection->createMemento()) {
@@ -2335,10 +2305,9 @@ void MainWidget::saveSectionInStack() {
 			_stack.back()->setThirdSectionWeak(_thirdSection.data());
 		}
 	} else if (const auto history = _history->history()) {
-		setEntryInStack(history, _history->msgId());
 		_stack.push_back(std::make_unique<StackItemHistory>(
-			_historyInStack,
-			_msgIdInStack,
+			history,
+			_history->msgId(),
 			_history->replyReturns()));
 		_stack.back()->setThirdSectionWeak(_thirdSection.data());
 	}
@@ -2618,7 +2587,9 @@ void MainWidget::showNewSection(
 	}
 
 	if (settingSection.data() == _mainSection.data()) {
-		_controller->activePeer = _mainSection->activePeer();
+		if (const auto entry = _mainSection->activeChat(); entry.key) {
+			_controller->setActiveChatEntry(entry);
+		}
 	}
 
 	checkFloatPlayerVisibility();
@@ -2666,7 +2637,9 @@ void MainWidget::showBackFromStack(
 	if (selectingPeer()) return;
 	if (_stack.empty()) {
 		_controller->clearSectionStack(params);
-		if (App::wnd()) QTimer::singleShot(0, App::wnd(), SLOT(setInnerFocus()));
+		if (App::wnd()) {
+			QTimer::singleShot(0, App::wnd(), SLOT(setInnerFocus()));
+		}
 		return;
 	}
 	auto item = std::move(_stack.back());
@@ -2676,14 +2649,6 @@ void MainWidget::showBackFromStack(
 	}
 	_thirdSectionFromStack = item->takeThirdSectionMemento();
 	if (item->type() == HistoryStackItem) {
-		clearEntryInStack();
-		for (auto i = _stack.size(); i > 0;) {
-			if (_stack[--i]->type() == HistoryStackItem) {
-				auto historyItem = static_cast<StackItemHistory*>(_stack[i].get());
-				setEntryInStack(historyItem->history, historyItem->msgId);
-				break;
-			}
-		}
 		auto historyItem = static_cast<StackItemHistory*>(item.get());
 		_controller->showPeerHistory(
 			historyItem->peer()->id,
@@ -2806,12 +2771,6 @@ QPixmap MainWidget::grabForShowAnimation(const Window::SectionSlideParams &param
 	return result;
 }
 
-void MainWidget::updateCurrentChatListEntry() {
-	if (_historyInStack) {
-		_dialogs->dlgUpdated(_historyInStack, _msgIdInStack);
-	}
-}
-
 void MainWidget::dlgUpdated(
 		Dialogs::Mode list,
 		not_null<Dialogs::Row*> row) {
@@ -3248,18 +3207,23 @@ bool MainWidget::saveThirdSectionToStackBack() const {
 }
 
 auto MainWidget::thirdSectionForCurrentMainSection(
-	not_null<PeerData*> peer)
+	Dialogs::Key key)
 -> std::unique_ptr<Window::SectionMemento> {
 	if (_thirdSectionFromStack) {
 		return std::move(_thirdSectionFromStack);
+	} else if (const auto peer = key.peer()) {
+		return std::make_unique<Info::Memento>(
+			peer->id,
+			Info::Memento::DefaultSection(peer));
+	} else {
+		return std::make_unique<Info::Memento>(
+			App::self()->id,
+			Info::Memento::DefaultSection(App::self()));
 	}
-	return std::make_unique<Info::Memento>(
-		peer->id,
-		Info::Memento::DefaultSection(peer));
 }
 
-void MainWidget::updateThirdColumnToCurrentPeer(
-		PeerData *peer,
+void MainWidget::updateThirdColumnToCurrentChat(
+		Dialogs::Key key,
 		bool canWrite) {
 	auto saveOldThirdSection = [&] {
 		if (saveThirdSectionToStackBack()) {
@@ -3285,7 +3249,7 @@ void MainWidget::updateThirdColumnToCurrentPeer(
 		}
 
 		_controller->showSection(
-			std::move(*thirdSectionForCurrentMainSection(peer)),
+			std::move(*thirdSectionForCurrentMainSection(key)),
 			params.withThirdColumn());
 	};
 	auto switchTabbedFast = [&] {
@@ -3294,7 +3258,7 @@ void MainWidget::updateThirdColumnToCurrentPeer(
 	};
 	if (Adaptive::ThreeColumn()
 		&& Auth().settings().tabbedSelectorSectionEnabled()
-		&& peer) {
+		&& key) {
 		if (!canWrite) {
 			switchInfoFast();
 			Auth().settings().setTabbedSelectorSectionEnabled(true);
@@ -3305,7 +3269,7 @@ void MainWidget::updateThirdColumnToCurrentPeer(
 		}
 	} else {
 		Auth().settings().setTabbedReplacedWithInfo(false);
-		if (!peer) {
+		if (!key) {
 			if (_thirdSection) {
 				_thirdSection.destroy();
 				_thirdShadow.destroy();
@@ -3449,13 +3413,6 @@ int MainWidget::backgroundFromY() const {
 	return -getMainSectionTop();
 }
 
-void MainWidget::onHistoryShown(History *history, MsgId atMsgId) {
-//	updateControlsGeometry();
-	if (history) {
-		dlgUpdated(history, atMsgId);
-	}
-}
-
 void MainWidget::searchInPeer(PeerData *peer) {
 	_dialogs->searchInPeer(peer);
 	if (Adaptive::OneColumn()) {
@@ -3626,7 +3583,7 @@ void MainWidget::gotChannelDifference(ChannelData *channel, const MTPupdates_Cha
 	if (!isFinal) {
 		MTP_LOG(0, ("getChannelDifference { good - after not final channelDifference was received }%1").arg(cTestMode() ? " TESTMODE" : ""));
 		getChannelDifference(channel);
-	} else if (activePeer() == channel) {
+	} else if (_controller->activeChatCurrent().peer() == channel) {
 		channel->ptsWaitingForShortPoll(timeout ? (timeout * 1000) : WaitForChannelGetDifference);
 	}
 }
diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h
index 96708a033..64d59e126 100644
--- a/Telegram/SourceFiles/mainwidget.h
+++ b/Telegram/SourceFiles/mainwidget.h
@@ -134,8 +134,6 @@ public:
 		const Dialogs::RowDescriptor &which) const;
 
 	PeerData *peer();
-	PeerData *activePeer();
-	MsgId activeMsgId();
 
 	int backgroundFromY() const;
 	void showSection(
@@ -200,7 +198,9 @@ public:
 		const QVector<MTPint> &ids,
 		bool forEveryone);
 	void deletedContact(UserData *user, const MTPcontacts_Link &result);
-	void deleteConversation(PeerData *peer, bool deleteHistory = true);
+	void deleteConversation(
+		not_null<PeerData*> peer,
+		bool deleteHistory = true);
 	void deleteAndExit(ChatData *chat);
 	void deleteAllFromUser(ChannelData *channel, UserData *from);
 
@@ -357,8 +357,6 @@ public slots:
 	void updateOnline(bool gotOtherOffline = false);
 	void checkIdleFinish();
 
-	void onHistoryShown(History *history, MsgId atMsgId);
-
 	void searchInPeer(PeerData *peer);
 
 	void onUpdateNotifySettings();
@@ -425,12 +423,12 @@ private:
 	void updateMediaPlaylistPosition(int x);
 	void updateControlsGeometry();
 	void updateDialogsWidthAnimated();
-	void updateThirdColumnToCurrentPeer(
-		PeerData *peer,
+	void updateThirdColumnToCurrentChat(
+		Dialogs::Key key,
 		bool canWrite);
 	[[nodiscard]] bool saveThirdSectionToStackBack() const;
-	[[nodiscard]] auto thirdSectionForCurrentMainSection(
-		not_null<PeerData*> peer) -> std::unique_ptr<Window::SectionMemento>;
+	[[nodiscard]] auto thirdSectionForCurrentMainSection(Dialogs::Key key)
+		-> std::unique_ptr<Window::SectionMemento>;
 	void userIsContactUpdated(not_null<UserData*> user);
 
 	void createPlayer();
@@ -439,11 +437,6 @@ private:
 	void closeBothPlayers();
 	void playerHeightUpdated();
 
-	void updateCurrentChatListEntry();
-	void setEntryInStack(not_null<History*> history, MsgId msgId);
-	void clearEntryInStack();
-	void setEntryInStackValues(History *history, MsgId msgId);
-
 	void setCurrentCall(Calls::Call *call);
 	void createCallTopBar();
 	void destroyCallTopBar();
@@ -588,8 +581,6 @@ private:
 	QPointer<ConfirmBox> _forwardConfirm; // for single column layout
 	object_ptr<HistoryHider> _hider = { nullptr };
 	std::vector<std::unique_ptr<StackItem>> _stack;
-	History *_historyInStack = nullptr;
-	MsgId _msgIdInStack = 0;
 
 	int _playerHeight = 0;
 	int _callTopBarHeight = 0;
diff --git a/Telegram/SourceFiles/rpl/combine_previous.h b/Telegram/SourceFiles/rpl/combine_previous.h
index b2787b818..8f89895c9 100644
--- a/Telegram/SourceFiles/rpl/combine_previous.h
+++ b/Telegram/SourceFiles/rpl/combine_previous.h
@@ -26,7 +26,7 @@ public:
 			>();
 			return std::move(initial).start(
 				[consumer, previous](auto &&value) {
-					if (auto exists = *previous) {
+					if (auto &exists = *previous) {
 						auto &existing = *exists;
 						auto next = std::make_tuple(
 							std::move(existing),
diff --git a/Telegram/SourceFiles/rpl/operators_tests.cpp b/Telegram/SourceFiles/rpl/operators_tests.cpp
index 24a029d97..704c447f2 100644
--- a/Telegram/SourceFiles/rpl/operators_tests.cpp
+++ b/Telegram/SourceFiles/rpl/operators_tests.cpp
@@ -401,6 +401,35 @@ TEST_CASE("basic operators tests", "[rpl::operators]") {
 		REQUIRE(*sum == "0-11-22-3");
 	}
 
+	SECTION("combine_previous test") {
+		auto sum = std::make_shared<std::string>("");
+		{
+			rpl::lifetime lifetime;
+			event_stream<int> a;
+
+			a.events(
+			) | combine_previous(
+			) | start_with_next([=](int previous, int next) {
+				*sum += std::to_string(previous) + ' ';
+				*sum += std::to_string(next) + ' ';
+			}, lifetime);
+
+			a.events(
+			) | combine_previous(
+				5
+			) | start_with_next([=](int previous, int next) {
+				*sum += std::to_string(10 + previous) + ' ';
+				*sum += std::to_string(next) + ' ';
+			}, lifetime);
+
+			a.fire(1);
+			a.fire(2);
+			a.fire(3);
+			a.fire(4);
+		}
+		REQUIRE(*sum == "15 1 1 2 11 2 2 3 12 3 3 4 13 4 ");
+	}
+
 	SECTION("take test") {
 		auto sum = std::make_shared<std::string>("");
 		{
diff --git a/Telegram/SourceFiles/window/section_widget.h b/Telegram/SourceFiles/window/section_widget.h
index e421742e6..0c269c8ce 100644
--- a/Telegram/SourceFiles/window/section_widget.h
+++ b/Telegram/SourceFiles/window/section_widget.h
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #pragma once
 
 #include "ui/rp_widget.h"
+#include "dialogs/dialogs_key.h"
 
 namespace Window {
 
@@ -69,8 +70,8 @@ class SectionWidget : public AbstractSectionWidget {
 public:
 	SectionWidget(QWidget *parent, not_null<Window::Controller*> controller);
 
-	virtual PeerData *activePeer() const {
-		return nullptr;
+	virtual Dialogs::RowDescriptor activeChat() const {
+		return {};
 	}
 
 	// When resizing the widget with top edge moved up or down and we
diff --git a/Telegram/SourceFiles/window/window_controller.cpp b/Telegram/SourceFiles/window/window_controller.cpp
index 8ef31edb5..fd96720a0 100644
--- a/Telegram/SourceFiles/window/window_controller.cpp
+++ b/Telegram/SourceFiles/window/window_controller.cpp
@@ -36,6 +36,46 @@ Controller::Controller(not_null<MainWindow*> window)
 	}, lifetime());
 }
 
+void Controller::setActiveChatEntry(Dialogs::RowDescriptor row) {
+	_activeChatEntry = row;
+}
+
+void Controller::setActiveChatEntry(Dialogs::Key key) {
+	setActiveChatEntry({ key, MsgId(0) });
+}
+
+Dialogs::RowDescriptor Controller::activeChatEntryCurrent() const {
+	return _activeChatEntry.current();
+}
+
+Dialogs::Key Controller::activeChatCurrent() const {
+	return activeChatEntryCurrent().key;
+}
+
+auto Controller::activeChatEntryChanges() const
+-> rpl::producer<Dialogs::RowDescriptor> {
+	return _activeChatEntry.changes();
+}
+
+rpl::producer<Dialogs::Key> Controller::activeChatChanges() const {
+	return activeChatEntryChanges(
+	) | rpl::map([](const Dialogs::RowDescriptor &value) {
+		return value.key;
+	}) | rpl::distinct_until_changed();
+}
+
+auto Controller::activeChatEntryValue() const
+-> rpl::producer<Dialogs::RowDescriptor> {
+	return _activeChatEntry.value();
+}
+
+rpl::producer<Dialogs::Key> Controller::activeChatValue() const {
+	return activeChatEntryValue(
+	) | rpl::map([](const Dialogs::RowDescriptor &value) {
+		return value.key;
+	}) | rpl::distinct_until_changed();
+}
+
 void Controller::enableGifPauseReason(GifPauseReason reason) {
 	if (!(_gifPauseReasons & reason)) {
 		auto notify = (static_cast<int>(_gifPauseReasons) < static_cast<int>(reason));
diff --git a/Telegram/SourceFiles/window/window_controller.h b/Telegram/SourceFiles/window/window_controller.h
index e9d9c4207..8b253e53e 100644
--- a/Telegram/SourceFiles/window/window_controller.h
+++ b/Telegram/SourceFiles/window/window_controller.h
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 #include <rpl/variable.h>
 #include "base/flags.h"
+#include "dialogs/dialogs_key.h"
 
 class MainWidget;
 class HistoryMessage;
@@ -118,8 +119,14 @@ public:
 	// Also used in the Info::Profile to toggle Send Message button.
 	rpl::variable<PeerData*> historyPeer;
 
-	// This is used for auto-switch in third column Info::Profile.
-	rpl::variable<PeerData*> activePeer;
+	void setActiveChatEntry(Dialogs::RowDescriptor row);
+	void setActiveChatEntry(Dialogs::Key key);
+	Dialogs::RowDescriptor activeChatEntryCurrent() const;
+	Dialogs::Key activeChatCurrent() const;
+	rpl::producer<Dialogs::RowDescriptor> activeChatEntryChanges() const;
+	rpl::producer<Dialogs::Key> activeChatChanges() const;
+	rpl::producer<Dialogs::RowDescriptor> activeChatEntryValue() const;
+	rpl::producer<Dialogs::Key> activeChatValue() const;
 
 	void enableGifPauseReason(GifPauseReason reason);
 	void disableGifPauseReason(GifPauseReason reason);
@@ -237,6 +244,7 @@ private:
 	base::Observable<void> _gifPauseLevelChanged;
 	base::Observable<void> _floatPlayerAreaUpdated;
 
+	rpl::variable<Dialogs::RowDescriptor> _activeChatEntry;
 	base::Variable<bool> _dialogsListFocused = { false };
 	base::Variable<bool> _dialogsListDisplayForced = { false };
 
diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp
index 928810501..14104b9e7 100644
--- a/Telegram/SourceFiles/window/window_peer_menu.cpp
+++ b/Telegram/SourceFiles/window/window_peer_menu.cpp
@@ -158,7 +158,7 @@ Filler::Filler(
 bool Filler::showInfo() {
 	if (_source == PeerMenuSource::Profile || _peer->isSelf()) {
 		return false;
-	} else if (_controller->activePeer.current() != _peer) {
+	} else if (_controller->activeChatCurrent().peer() != _peer) {
 		return true;
 	} else if (!Adaptive::ThreeColumn()) {
 		return true;
@@ -626,14 +626,15 @@ base::lambda<void()> DeleteAndLeaveHandler(not_null<PeerData*> peer) {
 			: st::attentionBoxButton;
 		auto callback = [peer] {
 			Ui::hideLayer();
-			if (App::wnd()->controller()->activePeer.current() == peer) {
+			const auto controller = App::wnd()->controller();
+			if (controller->activeChatCurrent().peer() == peer) {
 				Ui::showChatsList();
 			}
 			if (peer->isUser()) {
 				App::main()->deleteConversation(peer);
-			} else if (auto chat = peer->asChat()) {
+			} else if (const auto chat = peer->asChat()) {
 				App::main()->deleteAndExit(chat);
-			} else if (auto channel = peer->asChannel()) {
+			} else if (const auto channel = peer->asChannel()) {
 				// Don't delete old history by default,
 				// because Android app doesn't.
 				//