diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt
index d5d3b2cac..8ea6aa9ae 100644
--- a/Telegram/CMakeLists.txt
+++ b/Telegram/CMakeLists.txt
@@ -986,6 +986,8 @@ PRIVATE
     info/profile/info_profile_values.h
     info/profile/info_profile_widget.cpp
     info/profile/info_profile_widget.h
+    info/reactions_list/info_reactions_list_widget.cpp
+    info/reactions_list/info_reactions_list_widget.h
     info/requests_list/info_requests_list_widget.cpp
     info/requests_list/info_requests_list_widget.h
     info/saved/info_saved_sublists_widget.cpp
diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.h b/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.h
index 834ec5431..4db714bc3 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.h
+++ b/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.h
@@ -59,8 +59,6 @@ private:
 		UserData *offsetUser = nullptr;
 		bool allLoaded = false;
 		bool wasLoading = false;
-		rpl::lifetime lifetime;
-
 	};
 
 	static std::unique_ptr<PeerListSearchController> CreateSearchController(
diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp
index ba765b3c7..0fb338a88 100644
--- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp
@@ -1510,11 +1510,13 @@ void AddWhoReactedAction(
 			strong->hideMenu();
 		}
 		if (const auto item = controller->session().data().message(itemId)) {
-			controller->window().show(Reactions::FullListBox(
-				controller,
-				item,
-				{},
-				whoReadIds));
+			controller->showSection(
+				std::make_shared<Info::Memento>(
+					whoReadIds,
+					itemId,
+					HistoryView::Reactions::DefaultSelectedTab(
+						item,
+						whoReadIds)));
 		}
 	};
 	if (!menu->empty()) {
@@ -1685,10 +1687,10 @@ void ShowWhoReactedMenu(
 	};
 	const auto showAllChosen = [=, itemId = item->fullId()]{
 		if (const auto item = controller->session().data().message(itemId)) {
-			controller->window().show(Reactions::FullListBox(
-				controller,
-				item,
-				id));
+			controller->showSection(std::make_shared<Info::Memento>(
+				nullptr,
+				itemId,
+				HistoryView::Reactions::DefaultSelectedTab(item, id)));
 		}
 	};
 	const auto owner = &controller->session().data();
diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_list.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_list.cpp
index f925dcb90..209e73e3c 100644
--- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_list.cpp
+++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_list.cpp
@@ -62,8 +62,8 @@ private:
 class Controller final : public PeerListController {
 public:
 	Controller(
-		not_null<Window::SessionController*> window,
-		not_null<HistoryItem*> item,
+		not_null<Window::SessionNavigation*> window,
+		FullMsgId itemId,
 		const ReactionId &selected,
 		rpl::producer<ReactionId> switches,
 		std::shared_ptr<Api::WhoReadList> whoReadIds);
@@ -73,9 +73,26 @@ public:
 	void rowClicked(not_null<PeerListRow*> row) override;
 	void loadMoreRows() override;
 
+	std::unique_ptr<PeerListRow> createRestoredRow(
+		not_null<PeerData*> peer) override;
+
+	std::unique_ptr<PeerListState> saveState() const override;
+	void restoreState(std::unique_ptr<PeerListState> state) override;
+
 private:
 	using AllEntry = std::pair<not_null<PeerData*>, Data::ReactionId>;
 
+	struct SavedState : SavedStateBase {
+		ReactionId shownReaction;
+		base::flat_map<std::pair<PeerId, ReactionId>, uint64> idsMap;
+		uint64 idsCounter = 0;
+		std::vector<AllEntry> all;
+		QString allOffset;
+		std::vector<not_null<PeerData*>> filtered;
+		QString filteredOffset;
+		bool wasLoading = false;
+	};
+
 	void fillWhoRead();
 	void loadMore(const ReactionId &reaction);
 	bool appendRow(not_null<PeerData*> peer, ReactionId reaction);
@@ -88,14 +105,15 @@ private:
 		not_null<PeerData*> peer,
 		const ReactionId &reaction) const;
 
-	const not_null<Window::SessionController*> _window;
-	const not_null<HistoryItem*> _item;
+	const not_null<Window::SessionNavigation*> _window;
+	const not_null<PeerData*> _peer;
+	const FullMsgId _itemId;
 	const Ui::Text::CustomEmojiFactory _factory;
+	const std::shared_ptr<Api::WhoReadList> _whoReadIds;
+	const std::vector<not_null<PeerData*>> _whoRead;
 	MTP::Sender _api;
 
 	ReactionId _shownReaction;
-	std::shared_ptr<Api::WhoReadList> _whoReadIds;
-	std::vector<not_null<PeerData*>> _whoRead;
 
 	mutable base::flat_map<std::pair<PeerId, ReactionId>, uint64> _idsMap;
 	mutable uint64 _idsCounter = 0;
@@ -110,6 +128,22 @@ private:
 
 };
 
+[[nodiscard]] std::vector<not_null<PeerData*>> ResolveWhoRead(
+		not_null<Window::SessionNavigation*> window,
+		const std::shared_ptr<Api::WhoReadList> &whoReadIds) {
+	if (!whoReadIds || whoReadIds->list.empty()) {
+		return {};
+	}
+	auto result = std::vector<not_null<PeerData*>>();
+	auto &owner = window->session().data();
+	for (const auto &peerWithDate : whoReadIds->list) {
+		if (const auto peer = owner.peerLoaded(peerWithDate.peer)) {
+			result.push_back(peer);
+		}
+	}
+	return result;
+}
+
 Row::Row(
 	uint64 id,
 	not_null<PeerData*> peer,
@@ -166,17 +200,19 @@ void Row::rightActionPaint(
 }
 
 Controller::Controller(
-	not_null<Window::SessionController*> window,
-	not_null<HistoryItem*> item,
+	not_null<Window::SessionNavigation*> window,
+	FullMsgId itemId,
 	const ReactionId &selected,
 	rpl::producer<ReactionId> switches,
 	std::shared_ptr<Api::WhoReadList> whoReadIds)
 : _window(window)
-, _item(item)
+, _peer(window->session().data().peer(itemId.peer))
+, _itemId(itemId)
 , _factory(Data::ReactedMenuFactory(&window->session()))
+, _whoReadIds(whoReadIds)
+, _whoRead(ResolveWhoRead(window, _whoReadIds))
 , _api(&window->session().mtp())
-, _shownReaction(selected)
-, _whoReadIds(whoReadIds) {
+, _shownReaction(selected) {
 	std::move(
 		switches
 	) | rpl::filter([=](const ReactionId &reaction) {
@@ -248,14 +284,6 @@ uint64 Controller::id(
 }
 
 void Controller::fillWhoRead() {
-	if (_whoReadIds && !_whoReadIds->list.empty() && _whoRead.empty()) {
-		auto &owner = _window->session().data();
-		for (const auto &peerWithDate : _whoReadIds->list) {
-			if (const auto peer = owner.peerLoaded(peerWithDate.peer)) {
-				_whoRead.push_back(peer);
-			}
-		}
-	}
 	for (const auto &peer : _whoRead) {
 		appendRow(peer, ReactionId());
 	}
@@ -271,6 +299,60 @@ void Controller::loadMoreRows() {
 	loadMore(_shownReaction);
 }
 
+std::unique_ptr<PeerListRow> Controller::createRestoredRow(
+		not_null<PeerData*> peer) {
+	if (_shownReaction.emoji() == u"read"_q) {
+		return createRow(peer, Data::ReactionId());
+	} else if (_shownReaction.empty()) {
+		const auto i = ranges::find(_all, peer, &AllEntry::first);
+		const auto reaction = (i != end(_all)) ? i->second : _shownReaction;
+		return createRow(peer, reaction);
+	}
+	return createRow(peer, _shownReaction);
+}
+
+std::unique_ptr<PeerListState> Controller::saveState() const {
+	auto result = PeerListController::saveState();
+
+	auto my = std::make_unique<SavedState>();
+	my->shownReaction = _shownReaction;
+	my->idsMap = _idsMap;
+	my->idsCounter = _idsCounter;
+	my->all = _all;
+	my->allOffset = _allOffset;
+	my->filtered = _filtered;
+	my->filteredOffset = _filteredOffset;
+	my->wasLoading = (_loadRequestId != 0);
+	result->controllerState = std::move(my);
+	return result;
+}
+
+void Controller::restoreState(std::unique_ptr<PeerListState> state) {
+	auto typeErasedState = state
+		? state->controllerState.get()
+		: nullptr;
+	if (const auto my = dynamic_cast<SavedState*>(typeErasedState)) {
+		if (const auto requestId = base::take(_loadRequestId)) {
+			_api.request(requestId).cancel();
+		}
+		_shownReaction = my->shownReaction;
+		_idsMap = std::move(my->idsMap);
+		_idsCounter = my->idsCounter;
+		_all = std::move(my->all);
+		_allOffset = std::move(my->allOffset);
+		_filtered = std::move(my->filtered);
+		_filteredOffset = std::move(my->filteredOffset);
+		if (my->wasLoading) {
+			loadMoreRows();
+		}
+		PeerListController::restoreState(std::move(state));
+		if (delegate()->peerListFullRowsCount()) {
+			setDescriptionText(QString());
+			delegate()->peerListRefreshRows();
+		}
+	}
+}
+
 void Controller::loadMore(const ReactionId &reaction) {
 	if (reaction.emoji() == u"read"_q) {
 		loadMore(ReactionId());
@@ -290,8 +372,8 @@ void Controller::loadMore(const ReactionId &reaction) {
 		| (reaction.empty() ? Flag(0) : Flag::f_reaction);
 	_loadRequestId = _api.request(MTPmessages_GetMessageReactionsList(
 		MTP_flags(flags),
-		_item->history()->peer->input,
-		MTP_int(_item->id),
+		_peer->input,
+		MTP_int(_itemId.msg),
 		Data::ReactionToMTP(reaction),
 		MTP_string(offset),
 		MTP_int(offset.isEmpty() ? kPerPageFirst : kPerPage)
@@ -332,7 +414,7 @@ void Controller::rowClicked(not_null<PeerListRow*> row) {
 	const auto window = _window;
 	const auto peer = row->peer();
 	crl::on_main(window, [=] {
-		window->show(PrepareShortInfoBox(peer, window));
+		window->showPeerInfo(peer);
 	});
 }
 
@@ -353,72 +435,75 @@ std::unique_ptr<PeerListRow> Controller::createRow(
 		_factory,
 		Data::ReactionEntityData(reaction),
 		[=](Row *row) { delegate()->peerListUpdateRow(row); },
-		[=] { return _window->isGifPausedAtLeastFor(
+		[=] { return _window->parentController()->isGifPausedAtLeastFor(
 			Window::GifPauseReason::Layer); });
 }
 
 } // namespace
 
-object_ptr<Ui::BoxContent> FullListBox(
-		not_null<Window::SessionController*> window,
+Data::ReactionId DefaultSelectedTab(
+		not_null<HistoryItem*> item,
+		std::shared_ptr<Api::WhoReadList> whoReadIds) {
+	return DefaultSelectedTab(item, {}, std::move(whoReadIds));
+}
+
+Data::ReactionId DefaultSelectedTab(
 		not_null<HistoryItem*> item,
 		Data::ReactionId selected,
 		std::shared_ptr<Api::WhoReadList> whoReadIds) {
-	Expects(IsServerMsgId(item->id));
-
-	if (!ranges::contains(
-			item->reactions(),
-			selected,
-			&Data::MessageReaction::id)) {
+	const auto proj = &Data::MessageReaction::id;
+	if (!ranges::contains(item->reactions(), selected, proj)) {
 		selected = {};
 	}
-	if (selected.empty() && whoReadIds && !whoReadIds->list.empty()) {
-		selected = Data::ReactionId{ u"read"_q };
-	}
-	const auto tabRequests = std::make_shared<
-		rpl::event_stream<Data::ReactionId>>();
-	const auto initBox = [=](not_null<PeerListBox*> box) {
-		box->setNoContentMargin(true);
+	return (selected.empty() && whoReadIds && !whoReadIds->list.empty())
+		? Data::ReactionId{ u"read"_q }
+		: selected;
+}
 
-		auto map = item->reactions();
-		if (whoReadIds && !whoReadIds->list.empty()) {
-			map.push_back({
-				.id = Data::ReactionId{ u"read"_q },
-				.count = int(whoReadIds->list.size()),
-			});
-		}
-		const auto tabs = CreateTabs(
-			box,
-			Data::ReactedMenuFactory(&item->history()->session()),
-			[=] { return window->isGifPausedAtLeastFor(
-				Window::GifPauseReason::Layer); },
-			map,
-			selected,
-			whoReadIds ? whoReadIds->type : Ui::WhoReadType::Reacted);
-		tabs->changes(
-		) | rpl::start_to_stream(*tabRequests, box->lifetime());
-
-		box->widthValue(
-		) | rpl::start_with_next([=](int width) {
-			tabs->resizeToWidth(width);
-			tabs->move(0, 0);
-		}, box->lifetime());
-		tabs->heightValue(
-		) | rpl::start_with_next([=](int height) {
-			box->setAddedTopScrollSkip(height);
-		}, box->lifetime());
-		box->addButton(tr::lng_close(), [=] {
-			box->closeBox();
+not_null<Tabs*> CreateReactionsTabs(
+		not_null<QWidget*> parent,
+		not_null<Window::SessionNavigation*> window,
+		FullMsgId itemId,
+		Data::ReactionId selected,
+		std::shared_ptr<Api::WhoReadList> whoReadIds) {
+	const auto item = window->session().data().message(itemId);
+	auto map = item
+		? item->reactions()
+		: std::vector<Data::MessageReaction>();
+	if (whoReadIds && !whoReadIds->list.empty()) {
+		map.push_back({
+			.id = Data::ReactionId{ u"read"_q },
+			.count = int(whoReadIds->list.size()),
 		});
-	};
-	return Box<PeerListBox>(
-		std::make_unique<Controller>(
+	}
+	return CreateTabs(
+		parent,
+		Data::ReactedMenuFactory(&window->session()),
+		[=] { return window->parentController()->isGifPausedAtLeastFor(
+			Window::GifPauseReason::Layer); },
+		map,
+		selected,
+		whoReadIds ? whoReadIds->type : Ui::WhoReadType::Reacted);
+}
+
+PreparedFullList FullListController(
+		not_null<Window::SessionNavigation*> window,
+		FullMsgId itemId,
+		Data::ReactionId selected,
+		std::shared_ptr<Api::WhoReadList> whoReadIds) {
+	Expects(IsServerMsgId(itemId.msg));
+
+	const auto tab = std::make_shared<
+		rpl::event_stream<Data::ReactionId>>();
+	return {
+		.controller = std::make_unique<Controller>(
 			window,
-			item,
+			itemId,
 			selected,
-			tabRequests->events(),
+			tab->events(),
 			whoReadIds),
-		initBox);
+		.switchTab = [=](Data::ReactionId id) { tab->fire_copy(id); },
+	};
 }
 
 } // namespace HistoryView::Reactions
diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_list.h b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_list.h
index b65c21065..be6dc59fe 100644
--- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_list.h
+++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_list.h
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "base/object_ptr.h"
 
 class HistoryItem;
+class PeerListController;
 
 namespace Data {
 struct ReactionId;
@@ -21,6 +22,7 @@ struct WhoReadList;
 
 namespace Window {
 class SessionController;
+class SessionNavigation;
 } // namespace Window
 
 namespace Ui {
@@ -29,10 +31,31 @@ class BoxContent;
 
 namespace HistoryView::Reactions {
 
-object_ptr<Ui::BoxContent> FullListBox(
-	not_null<Window::SessionController*> window,
+[[nodiscard]] Data::ReactionId DefaultSelectedTab(
+	not_null<HistoryItem*> item,
+	std::shared_ptr<Api::WhoReadList> whoReadIds);
+
+[[nodiscard]] Data::ReactionId DefaultSelectedTab(
 	not_null<HistoryItem*> item,
 	Data::ReactionId selected,
 	std::shared_ptr<Api::WhoReadList> whoReadIds = nullptr);
 
+struct Tabs;
+[[nodiscard]] not_null<Tabs*> CreateReactionsTabs(
+	not_null<QWidget*> parent,
+	not_null<Window::SessionNavigation*> window,
+	FullMsgId itemId,
+	Data::ReactionId selected,
+	std::shared_ptr<Api::WhoReadList> whoReadIds);
+
+struct PreparedFullList {
+	std::unique_ptr<PeerListController> controller;
+	Fn<void(Data::ReactionId)> switchTab;
+};
+[[nodiscard]] PreparedFullList FullListController(
+	not_null<Window::SessionNavigation*> window,
+	FullMsgId itemId,
+	Data::ReactionId selected,
+	std::shared_ptr<Api::WhoReadList> whoReadIds = nullptr);
+
 } // namespace HistoryView::Reactions
diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_tabs.h b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_tabs.h
index 51d776004..8e3566552 100644
--- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_tabs.h
+++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_tabs.h
@@ -27,7 +27,7 @@ struct Tabs {
 	Fn<rpl::producer<int>()> heightValue;
 };
 
-not_null<Tabs*> CreateTabs(
+[[nodiscard]] not_null<Tabs*> CreateTabs(
 	not_null<QWidget*> parent,
 	Ui::Text::CustomEmojiFactory factory,
 	Fn<bool()> paused,
diff --git a/Telegram/SourceFiles/info/info_content_widget.cpp b/Telegram/SourceFiles/info/info_content_widget.cpp
index 942864710..15583240a 100644
--- a/Telegram/SourceFiles/info/info_content_widget.cpp
+++ b/Telegram/SourceFiles/info/info_content_widget.cpp
@@ -7,27 +7,28 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "info/info_content_widget.h"
 
-#include "window/window_session_controller.h"
-#include "ui/widgets/scroll_area.h"
-#include "ui/widgets/fields/input_field.h"
-#include "ui/wrap/padding_wrap.h"
-#include "ui/search_field_controller.h"
-#include "ui/ui_utility.h"
-#include "lang/lang_keys.h"
-#include "info/profile/info_profile_widget.h"
-#include "info/media/info_media_widget.h"
-#include "info/common_groups/info_common_groups_widget.h"
-#include "info/info_layer_widget.h"
-#include "info/info_section_widget.h"
-#include "info/info_controller.h"
+#include "api/api_who_reacted.h"
 #include "boxes/peer_list_box.h"
 #include "data/data_chat.h"
 #include "data/data_channel.h"
 #include "data/data_session.h"
 #include "data/data_forum_topic.h"
 #include "data/data_forum.h"
+#include "info/profile/info_profile_widget.h"
+#include "info/media/info_media_widget.h"
+#include "info/common_groups/info_common_groups_widget.h"
+#include "info/info_layer_widget.h"
+#include "info/info_section_widget.h"
+#include "info/info_controller.h"
+#include "lang/lang_keys.h"
 #include "main/main_session.h"
+#include "ui/widgets/scroll_area.h"
+#include "ui/widgets/fields/input_field.h"
+#include "ui/wrap/padding_wrap.h"
+#include "ui/search_field_controller.h"
+#include "ui/ui_utility.h"
 #include "window/window_peer_menu.h"
+#include "window/window_session_controller.h"
 #include "styles/style_info.h"
 #include "styles/style_profile.h"
 #include "styles/style_layers.h"
@@ -377,6 +378,8 @@ Key ContentMemento::key() const {
 		return Stories::Tag{ peer, storiesTab() };
 	} else if (const auto peer = statisticsTag().peer) {
 		return statisticsTag();
+	} else if (const auto who = reactionsWhoReadIds()) {
+		return Key(who, _reactionsSelected, _pollReactionsContextId);
 	} else {
 		return Downloads::Tag();
 	}
@@ -417,4 +420,15 @@ ContentMemento::ContentMemento(Statistics::Tag statistics)
 : _statisticsTag(statistics) {
 }
 
+ContentMemento::ContentMemento(
+	std::shared_ptr<Api::WhoReadList> whoReadIds,
+	FullMsgId contextId,
+	Data::ReactionId selected)
+: _reactionsWhoReadIds(whoReadIds
+	? whoReadIds
+	: std::make_shared<Api::WhoReadList>())
+, _reactionsSelected(selected)
+, _pollReactionsContextId(contextId) {
+}
+
 } // namespace Info
diff --git a/Telegram/SourceFiles/info/info_content_widget.h b/Telegram/SourceFiles/info/info_content_widget.h
index fd39d7384..f0c46da5a 100644
--- a/Telegram/SourceFiles/info/info_content_widget.h
+++ b/Telegram/SourceFiles/info/info_content_widget.h
@@ -10,6 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "info/info_wrap_widget.h"
 #include "info/statistics/info_statistics_tag.h"
 
+namespace Api {
+struct WhoReadList;
+} // namespace Api
+
 namespace Dialogs::Stories {
 struct Content;
 } // namespace Dialogs::Stories
@@ -189,8 +193,12 @@ public:
 	explicit ContentMemento(Statistics::Tag statistics);
 	ContentMemento(not_null<PollData*> poll, FullMsgId contextId)
 	: _poll(poll)
-	, _pollContextId(contextId) {
+	, _pollReactionsContextId(contextId) {
 	}
+	ContentMemento(
+		std::shared_ptr<Api::WhoReadList> whoReadIds,
+		FullMsgId contextId,
+		Data::ReactionId selected);
 
 	virtual object_ptr<ContentWidget> createWidget(
 		QWidget *parent,
@@ -222,7 +230,16 @@ public:
 		return _poll;
 	}
 	FullMsgId pollContextId() const {
-		return _pollContextId;
+		return _poll ? _pollReactionsContextId : FullMsgId();
+	}
+	std::shared_ptr<Api::WhoReadList> reactionsWhoReadIds() const {
+		return _reactionsWhoReadIds;
+	}
+	Data::ReactionId reactionsSelected() const {
+		return _reactionsSelected;
+	}
+	FullMsgId reactionsContextId() const {
+		return _reactionsWhoReadIds ? _pollReactionsContextId : FullMsgId();
 	}
 	Key key() const;
 
@@ -264,7 +281,9 @@ private:
 	Stories::Tab _storiesTab = {};
 	Statistics::Tag _statisticsTag;
 	PollData * const _poll = nullptr;
-	const FullMsgId _pollContextId;
+	std::shared_ptr<Api::WhoReadList> _reactionsWhoReadIds;
+	Data::ReactionId _reactionsSelected;
+	const FullMsgId _pollReactionsContextId;
 
 	int _scrollTop = 0;
 	QString _searchFieldQuery;
diff --git a/Telegram/SourceFiles/info/info_controller.cpp b/Telegram/SourceFiles/info/info_controller.cpp
index 25ad70b2a..388f870fe 100644
--- a/Telegram/SourceFiles/info/info_controller.cpp
+++ b/Telegram/SourceFiles/info/info_controller.cpp
@@ -50,6 +50,13 @@ Key::Key(not_null<PollData*> poll, FullMsgId contextId)
 : _value(PollKey{ poll, contextId }) {
 }
 
+Key::Key(
+	std::shared_ptr<Api::WhoReadList> whoReadIds,
+	Data::ReactionId selected,
+	FullMsgId contextId)
+: _value(ReactionsKey{ whoReadIds, selected, contextId }) {
+}
+
 PeerData *Key::peer() const {
 	if (const auto peer = std::get_if<not_null<PeerData*>>(&_value)) {
 		return *peer;
@@ -113,6 +120,27 @@ FullMsgId Key::pollContextId() const {
 	return FullMsgId();
 }
 
+std::shared_ptr<Api::WhoReadList> Key::reactionsWhoReadIds() const {
+	if (const auto data = std::get_if<ReactionsKey>(&_value)) {
+		return data->whoReadIds;
+	}
+	return nullptr;
+}
+
+Data::ReactionId Key::reactionsSelected() const {
+	if (const auto data = std::get_if<ReactionsKey>(&_value)) {
+		return data->selected;
+	}
+	return Data::ReactionId();
+}
+
+FullMsgId Key::reactionsContextId() const {
+	if (const auto data = std::get_if<ReactionsKey>(&_value)) {
+		return data->contextId;
+	}
+	return FullMsgId();
+}
+
 rpl::producer<SparseIdsMergedSlice> AbstractController::mediaSource(
 		SparseIdsMergedSlice::UniversalMsgId aroundId,
 		int limitBefore,
@@ -183,6 +211,19 @@ PollData *AbstractController::poll() const {
 	return nullptr;
 }
 
+auto AbstractController::reactionsWhoReadIds() const
+-> std::shared_ptr<Api::WhoReadList> {
+	return key().reactionsWhoReadIds();
+}
+
+Data::ReactionId AbstractController::reactionsSelected() const {
+	return key().reactionsSelected();
+}
+
+FullMsgId AbstractController::reactionsContextId() const {
+	return key().reactionsContextId();
+}
+
 void AbstractController::showSection(
 		std::shared_ptr<Window::SectionMemento> memento,
 		const Window::SectionShow &params) {
diff --git a/Telegram/SourceFiles/info/info_controller.h b/Telegram/SourceFiles/info/info_controller.h
index 6d7d2e92b..4fad8ca05 100644
--- a/Telegram/SourceFiles/info/info_controller.h
+++ b/Telegram/SourceFiles/info/info_controller.h
@@ -7,10 +7,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #pragma once
 
+#include "data/data_message_reaction_id.h"
 #include "data/data_search_controller.h"
 #include "info/statistics/info_statistics_tag.h"
 #include "window/window_session_controller.h"
 
+namespace Api {
+struct WhoReadList;
+} // namespace Api
+
 namespace Data {
 class ForumTopic;
 } // namespace Data
@@ -67,6 +72,10 @@ public:
 	Key(Stories::Tag stories);
 	Key(Statistics::Tag statistics);
 	Key(not_null<PollData*> poll, FullMsgId contextId);
+	Key(
+		std::shared_ptr<Api::WhoReadList> whoReadIds,
+		Data::ReactionId selected,
+		FullMsgId contextId);
 
 	PeerData *peer() const;
 	Data::ForumTopic *topic() const;
@@ -77,12 +86,20 @@ public:
 	Statistics::Tag statisticsTag() const;
 	PollData *poll() const;
 	FullMsgId pollContextId() const;
+	std::shared_ptr<Api::WhoReadList> reactionsWhoReadIds() const;
+	Data::ReactionId reactionsSelected() const;
+	FullMsgId reactionsContextId() const;
 
 private:
 	struct PollKey {
 		not_null<PollData*> poll;
 		FullMsgId contextId;
 	};
+	struct ReactionsKey {
+		std::shared_ptr<Api::WhoReadList> whoReadIds;
+		Data::ReactionId selected;
+		FullMsgId contextId;
+	};
 	std::variant<
 		not_null<PeerData*>,
 		not_null<Data::ForumTopic*>,
@@ -90,7 +107,8 @@ private:
 		Downloads::Tag,
 		Stories::Tag,
 		Statistics::Tag,
-		PollKey> _value;
+		PollKey,
+		ReactionsKey> _value;
 
 };
 
@@ -107,6 +125,7 @@ public:
 		CommonGroups,
 		SimilarChannels,
 		RequestsList,
+		ReactionsList,
 		SavedSublists,
 		PeerGifts,
 		Members,
@@ -187,6 +206,10 @@ public:
 	[[nodiscard]] FullMsgId pollContextId() const {
 		return key().pollContextId();
 	}
+	[[nodiscard]] auto reactionsWhoReadIds() const
+		-> std::shared_ptr<Api::WhoReadList>;
+	[[nodiscard]] Data::ReactionId reactionsSelected() const;
+	[[nodiscard]] FullMsgId reactionsContextId() const;
 
 	virtual void setSearchEnabledByContent(bool enabled) {
 	}
diff --git a/Telegram/SourceFiles/info/info_memento.cpp b/Telegram/SourceFiles/info/info_memento.cpp
index e26bab419..7e38fb5d1 100644
--- a/Telegram/SourceFiles/info/info_memento.cpp
+++ b/Telegram/SourceFiles/info/info_memento.cpp
@@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "info/saved/info_saved_sublists_widget.h"
 #include "info/settings/info_settings_widget.h"
 #include "info/similar_channels/info_similar_channels_widget.h"
+#include "info/reactions_list/info_reactions_list_widget.h"
 #include "info/requests_list/info_requests_list_widget.h"
 #include "info/peer_gifts/info_peer_gifts_widget.h"
 #include "info/polls/info_polls_results_widget.h"
@@ -54,6 +55,13 @@ Memento::Memento(not_null<PollData*> poll, FullMsgId contextId)
 : Memento(DefaultStack(poll, contextId)) {
 }
 
+Memento::Memento(
+	std::shared_ptr<Api::WhoReadList> whoReadIds,
+	FullMsgId contextId,
+	Data::ReactionId selected)
+: Memento(DefaultStack(std::move(whoReadIds), contextId, selected)) {
+}
+
 Memento::Memento(std::vector<std::shared_ptr<ContentMemento>> stack)
 : _stack(std::move(stack)) {
 	auto topics = base::flat_set<not_null<Data::ForumTopic*>>();
@@ -113,6 +121,18 @@ std::vector<std::shared_ptr<ContentMemento>> Memento::DefaultStack(
 	return result;
 }
 
+std::vector<std::shared_ptr<ContentMemento>> Memento::DefaultStack(
+		std::shared_ptr<Api::WhoReadList> whoReadIds,
+		FullMsgId contextId,
+		Data::ReactionId selected) {
+	auto result = std::vector<std::shared_ptr<ContentMemento>>();
+	result.push_back(std::make_shared<ReactionsList::Memento>(
+		std::move(whoReadIds),
+		contextId,
+		selected));
+	return result;
+}
+
 Section Memento::DefaultSection(not_null<PeerData*> peer) {
 	if (peer->savedSublistsInfo()) {
 		return Section(Section::Type::SavedSublists);
diff --git a/Telegram/SourceFiles/info/info_memento.h b/Telegram/SourceFiles/info/info_memento.h
index 3d18296cb..dc50f2f89 100644
--- a/Telegram/SourceFiles/info/info_memento.h
+++ b/Telegram/SourceFiles/info/info_memento.h
@@ -13,12 +13,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "window/section_memento.h"
 #include "base/object_ptr.h"
 
+namespace Api {
+struct WhoReadList;
+} // namespace Api
+
 namespace Storage {
 enum class SharedMediaType : signed char;
 } // namespace Storage
 
 namespace Data {
 class ForumTopic;
+struct ReactionId;
 } // namespace Data
 
 namespace Ui {
@@ -46,6 +51,10 @@ public:
 	Memento(not_null<Data::ForumTopic*> topic, Section section);
 	Memento(Settings::Tag settings, Section section);
 	Memento(not_null<PollData*> poll, FullMsgId contextId);
+	Memento(
+		std::shared_ptr<Api::WhoReadList> whoReadIds,
+		FullMsgId contextId,
+		Data::ReactionId selected);
 	explicit Memento(std::vector<std::shared_ptr<ContentMemento>> stack);
 
 	object_ptr<Window::SectionWidget> createWidget(
@@ -91,6 +100,10 @@ private:
 	static std::vector<std::shared_ptr<ContentMemento>> DefaultStack(
 		not_null<PollData*> poll,
 		FullMsgId contextId);
+	static std::vector<std::shared_ptr<ContentMemento>> DefaultStack(
+		std::shared_ptr<Api::WhoReadList> whoReadIds,
+		FullMsgId contextId,
+		Data::ReactionId selected);
 
 	static std::shared_ptr<ContentMemento> DefaultContent(
 		not_null<PeerData*> peer,
diff --git a/Telegram/SourceFiles/info/polls/info_polls_results_inner_widget.cpp b/Telegram/SourceFiles/info/polls/info_polls_results_inner_widget.cpp
index f54e09ea5..d2b8b31b9 100644
--- a/Telegram/SourceFiles/info/polls/info_polls_results_inner_widget.cpp
+++ b/Telegram/SourceFiles/info/polls/info_polls_results_inner_widget.cpp
@@ -24,8 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "styles/style_layers.h"
 #include "styles/style_info.h"
 
-namespace Info {
-namespace Polls {
+namespace Info::Polls {
 namespace {
 
 constexpr auto kFirstPage = 15;
@@ -659,6 +658,4 @@ auto InnerWidget::showPeerInfoRequests() const
 	return _showPeerInfoRequests.events();
 }
 
-} // namespace Polls
-} // namespace Info
-
+} // namespace Info::Polls
diff --git a/Telegram/SourceFiles/info/polls/info_polls_results_inner_widget.h b/Telegram/SourceFiles/info/polls/info_polls_results_inner_widget.h
index f2fbf05af..c84e10ef9 100644
--- a/Telegram/SourceFiles/info/polls/info_polls_results_inner_widget.h
+++ b/Telegram/SourceFiles/info/polls/info_polls_results_inner_widget.h
@@ -16,10 +16,10 @@ class VerticalLayout;
 } // namespace Ui
 
 namespace Info {
-
 class Controller;
+} // namespace Info
 
-namespace Polls {
+namespace Info::Polls {
 
 class Memento;
 class ListController;
@@ -70,5 +70,4 @@ private:
 
 };
 
-} // namespace Polls
-} // namespace Info
+} // namespace Info::Polls
diff --git a/Telegram/SourceFiles/info/polls/info_polls_results_widget.cpp b/Telegram/SourceFiles/info/polls/info_polls_results_widget.cpp
index 02d3fe0a2..056eb35cf 100644
--- a/Telegram/SourceFiles/info/polls/info_polls_results_widget.cpp
+++ b/Telegram/SourceFiles/info/polls/info_polls_results_widget.cpp
@@ -13,8 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_poll.h"
 #include "ui/ui_utility.h"
 
-namespace Info {
-namespace Polls {
+namespace Info::Polls {
 
 Memento::Memento(not_null<PollData*> poll, FullMsgId contextId)
 : ContentMemento(poll, contextId) {
@@ -113,5 +112,4 @@ void Widget::restoreState(not_null<Memento*> memento) {
 	scrollTopRestore(memento->scrollTop());
 }
 
-} // namespace Polls
-} // namespace Info
+} // namespace Info::Polls
diff --git a/Telegram/SourceFiles/info/polls/info_polls_results_widget.h b/Telegram/SourceFiles/info/polls/info_polls_results_widget.h
index 6766da4f5..a3c4ac752 100644
--- a/Telegram/SourceFiles/info/polls/info_polls_results_widget.h
+++ b/Telegram/SourceFiles/info/polls/info_polls_results_widget.h
@@ -12,8 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 struct PeerListState;
 
-namespace Info {
-namespace Polls {
+namespace Info::Polls {
 
 class InnerWidget;
 
@@ -68,5 +67,4 @@ private:
 
 };
 
-} // namespace Polls
-} // namespace Info
+} // namespace Info::Polls
diff --git a/Telegram/SourceFiles/info/reactions_list/info_reactions_list_widget.cpp b/Telegram/SourceFiles/info/reactions_list/info_reactions_list_widget.cpp
new file mode 100644
index 000000000..a94f2f2a7
--- /dev/null
+++ b/Telegram/SourceFiles/info/reactions_list/info_reactions_list_widget.cpp
@@ -0,0 +1,352 @@
+/*
+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 "info/reactions_list/info_reactions_list_widget.h"
+
+#include "api/api_who_reacted.h"
+#include "boxes/peer_list_box.h"
+#include "data/data_channel.h"
+#include "history/view/reactions/history_view_reactions_list.h"
+#include "history/view/reactions/history_view_reactions_tabs.h"
+#include "info/info_controller.h"
+#include "ui/controls/who_reacted_context_action.h"
+#include "ui/widgets/scroll_area.h"
+#include "ui/search_field_controller.h"
+#include "ui/ui_utility.h"
+#include "lang/lang_keys.h"
+#include "styles/style_info.h"
+
+namespace Info::ReactionsList {
+namespace {
+
+} // namespace
+
+class InnerWidget final
+	: public Ui::RpWidget
+	, private PeerListContentDelegate {
+public:
+	InnerWidget(
+		QWidget *parent,
+		not_null<Controller*> controller,
+		std::shared_ptr<Api::WhoReadList> whoReadIds,
+		FullMsgId contextId,
+		Data::ReactionId selected);
+
+	[[nodiscard]] std::shared_ptr<Api::WhoReadList> whoReadIds() const;
+	[[nodiscard]] FullMsgId contextId() const;
+	[[nodiscard]] Data::ReactionId selected() const;
+
+	rpl::producer<Ui::ScrollToRequest> scrollToRequests() const;
+
+	int desiredHeight() const;
+
+	void saveState(not_null<Memento*> memento);
+	void restoreState(not_null<Memento*> memento);
+
+protected:
+	void visibleTopBottomUpdated(
+		int visibleTop,
+		int visibleBottom) override;
+
+private:
+	using ListWidget = PeerListContent;
+
+	// PeerListContentDelegate interface
+	void peerListSetTitle(rpl::producer<QString> title) override;
+	void peerListSetAdditionalTitle(rpl::producer<QString> title) override;
+	bool peerListIsRowChecked(not_null<PeerListRow*> row) override;
+	int peerListSelectedRowsCount() override;
+	void peerListScrollToTop() override;
+	void peerListAddSelectedPeerInBunch(not_null<PeerData*> peer) override;
+	void peerListAddSelectedRowInBunch(not_null<PeerListRow*> row) override;
+	void peerListFinishSelectedRowsBunch() override;
+	void peerListSetDescription(object_ptr<Ui::FlatLabel> description) override;
+	std::shared_ptr<Main::SessionShow> peerListUiShow() override;
+
+	object_ptr<ListWidget> setupList(
+		RpWidget *parent,
+		not_null<PeerListController*> controller);
+
+	const std::shared_ptr<Main::SessionShow> _show;
+	not_null<Controller*> _controller;
+	Data::ReactionId _selected;
+	not_null<HistoryView::Reactions::Tabs*> _tabs;
+	rpl::variable<int> _tabsHeight;
+	HistoryView::Reactions::PreparedFullList _full;
+	object_ptr<ListWidget> _list;
+
+	rpl::event_stream<Ui::ScrollToRequest> _scrollToRequests;
+};
+
+InnerWidget::InnerWidget(
+	QWidget *parent,
+	not_null<Controller*> controller,
+	std::shared_ptr<Api::WhoReadList> whoReadIds,
+	FullMsgId contextId,
+	Data::ReactionId selected)
+: RpWidget(parent)
+, _show(controller->uiShow())
+, _controller(controller)
+, _selected(selected)
+, _tabs(HistoryView::Reactions::CreateReactionsTabs(
+	this,
+	controller,
+	controller->reactionsContextId(),
+	_selected,
+	controller->reactionsWhoReadIds()))
+, _tabsHeight(_tabs->heightValue())
+, _full(HistoryView::Reactions::FullListController(
+	controller,
+	controller->reactionsContextId(),
+	_selected,
+	controller->reactionsWhoReadIds()))
+, _list(setupList(this, _full.controller.get())) {
+	setContent(_list.data());
+	_full.controller->setDelegate(static_cast<PeerListDelegate*>(this));
+	_tabs->changes(
+	) | rpl::start_with_next([=](Data::ReactionId reaction) {
+		_selected = reaction;
+		_full.switchTab(reaction);
+	}, _list->lifetime());
+}
+
+std::shared_ptr<Api::WhoReadList> InnerWidget::whoReadIds() const {
+	return _controller->reactionsWhoReadIds();
+}
+
+FullMsgId InnerWidget::contextId() const {
+	return _controller->reactionsContextId();
+}
+
+Data::ReactionId InnerWidget::selected() const {
+	return _selected;
+}
+
+void InnerWidget::visibleTopBottomUpdated(
+		int visibleTop,
+		int visibleBottom) {
+	setChildVisibleTopBottom(_list, visibleTop, visibleBottom);
+}
+
+void InnerWidget::saveState(not_null<Memento*> memento) {
+	memento->setListState(_full.controller->saveState());
+}
+
+void InnerWidget::restoreState(not_null<Memento*> memento) {
+	_full.controller->restoreState(memento->listState());
+}
+
+rpl::producer<Ui::ScrollToRequest> InnerWidget::scrollToRequests() const {
+	return _scrollToRequests.events();
+}
+
+int InnerWidget::desiredHeight() const {
+	auto desired = 0;
+	desired += _list->fullRowsCount() * st::infoMembersList.item.height;
+	return qMax(height(), desired);
+}
+
+object_ptr<InnerWidget::ListWidget> InnerWidget::setupList(
+		RpWidget *parent,
+		not_null<PeerListController*> controller) {
+	auto result = object_ptr<ListWidget>(parent, controller);
+	const auto raw = result.data();
+
+	raw->scrollToRequests(
+	) | rpl::start_with_next([this](Ui::ScrollToRequest request) {
+		const auto skip = _tabsHeight.current()
+			+ st::infoCommonGroupsMargin.top();
+		auto addmin = (request.ymin < 0) ? 0 : skip;
+		auto addmax = (request.ymax < 0) ? 0 : skip;
+		_scrollToRequests.fire({
+			request.ymin + addmin,
+			request.ymax + addmax });
+	}, raw->lifetime());
+
+	_tabs->move(0, 0);
+	_tabsHeight.value() | rpl::start_with_next([=](int tabs) {
+		raw->moveToLeft(0, tabs + st::infoCommonGroupsMargin.top());
+	}, raw->lifetime());
+
+	parent->widthValue(
+	) | rpl::start_with_next([=](int newWidth) {
+		_tabs->resizeToWidth(newWidth);
+		raw->resizeToWidth(newWidth);
+	}, raw->lifetime());
+
+	rpl::combine(
+		_tabsHeight.value(),
+		raw->heightValue()
+	) | rpl::start_with_next([parent](int tabsHeight, int listHeight) {
+		const auto newHeight = tabsHeight
+			+ st::infoCommonGroupsMargin.top()
+			+ listHeight
+			+ st::infoCommonGroupsMargin.bottom();
+		parent->resize(parent->width(), newHeight);
+	}, result->lifetime());
+
+	return result;
+}
+
+void InnerWidget::peerListSetTitle(rpl::producer<QString> title) {
+}
+
+void InnerWidget::peerListSetAdditionalTitle(rpl::producer<QString> title) {
+}
+
+bool InnerWidget::peerListIsRowChecked(not_null<PeerListRow*> row) {
+	return false;
+}
+
+int InnerWidget::peerListSelectedRowsCount() {
+	return 0;
+}
+
+void InnerWidget::peerListScrollToTop() {
+	_scrollToRequests.fire({ -1, -1 });
+}
+
+void InnerWidget::peerListAddSelectedPeerInBunch(not_null<PeerData*> peer) {
+	Unexpected("Item selection in Info::Profile::Members.");
+}
+
+void InnerWidget::peerListAddSelectedRowInBunch(not_null<PeerListRow*> row) {
+	Unexpected("Item selection in Info::Profile::Members.");
+}
+
+void InnerWidget::peerListFinishSelectedRowsBunch() {
+}
+
+void InnerWidget::peerListSetDescription(
+		object_ptr<Ui::FlatLabel> description) {
+	description.destroy();
+}
+
+std::shared_ptr<Main::SessionShow> InnerWidget::peerListUiShow() {
+	return _show;
+}
+
+Memento::Memento(
+	std::shared_ptr<Api::WhoReadList> whoReadIds,
+	FullMsgId contextId,
+	Data::ReactionId selected)
+: ContentMemento(std::move(whoReadIds), contextId, selected) {
+}
+
+Section Memento::section() const {
+	return Section(Section::Type::ReactionsList);
+}
+
+std::shared_ptr<Api::WhoReadList> Memento::whoReadIds() const {
+	return reactionsWhoReadIds();
+}
+
+FullMsgId Memento::contextId() const {
+	return reactionsContextId();
+}
+
+Data::ReactionId Memento::selected() const {
+	return reactionsSelected();
+}
+
+object_ptr<ContentWidget> Memento::createWidget(
+		QWidget *parent,
+		not_null<Controller*> controller,
+		const QRect &geometry) {
+	auto result = object_ptr<Widget>(
+		parent,
+		controller,
+		whoReadIds(),
+		contextId(),
+		selected());
+	result->setInternalState(geometry, this);
+	return result;
+}
+
+void Memento::setListState(std::unique_ptr<PeerListState> state) {
+	_listState = std::move(state);
+}
+
+std::unique_ptr<PeerListState> Memento::listState() {
+	return std::move(_listState);
+}
+
+Memento::~Memento() = default;
+
+Widget::Widget(
+	QWidget *parent,
+	not_null<Controller*> controller,
+	std::shared_ptr<Api::WhoReadList> whoReadIds,
+	FullMsgId contextId,
+	Data::ReactionId selected)
+: ContentWidget(parent, controller) {
+	_inner = setInnerWidget(object_ptr<InnerWidget>(
+		this,
+		controller,
+		std::move(whoReadIds),
+		contextId,
+		selected));
+}
+
+rpl::producer<QString> Widget::title() {
+	const auto ids = whoReadIds();
+	const auto count = ids ? int(ids->list.size()) : 0;
+	return !count
+		? tr::lng_manage_peer_reactions()
+		: (ids->type == Ui::WhoReadType::Seen)
+		? tr::lng_context_seen_text(lt_count, rpl::single(1. * count))
+		: (ids->type == Ui::WhoReadType::Listened)
+		? tr::lng_context_seen_listened(lt_count, rpl::single(1. * count))
+		: (ids->type == Ui::WhoReadType::Watched)
+		? tr::lng_context_seen_watched(lt_count, rpl::single(1. * count))
+		: tr::lng_manage_peer_reactions();
+}
+
+std::shared_ptr<Api::WhoReadList> Widget::whoReadIds() const {
+	return _inner->whoReadIds();
+}
+
+FullMsgId Widget::contextId() const {
+	return _inner->contextId();
+}
+
+Data::ReactionId Widget::selected() const {
+	return _inner->selected();
+}
+
+bool Widget::showInternal(not_null<ContentMemento*> memento) {
+	return false;
+}
+
+void Widget::setInternalState(
+		const QRect &geometry,
+		not_null<Memento*> memento) {
+	setGeometry(geometry);
+	Ui::SendPendingMoveResizeEvents(this);
+	restoreState(memento);
+}
+
+std::shared_ptr<ContentMemento> Widget::doCreateMemento() {
+	auto result = std::make_shared<Memento>(
+		whoReadIds(),
+		contextId(),
+		selected());
+	saveState(result.get());
+	return result;
+}
+
+void Widget::saveState(not_null<Memento*> memento) {
+	memento->setScrollTop(scrollTopSave());
+	_inner->saveState(memento);
+}
+
+void Widget::restoreState(not_null<Memento*> memento) {
+	_inner->restoreState(memento);
+	scrollTopRestore(memento->scrollTop());
+}
+
+} // namespace Info::ReactionsList
diff --git a/Telegram/SourceFiles/info/reactions_list/info_reactions_list_widget.h b/Telegram/SourceFiles/info/reactions_list/info_reactions_list_widget.h
new file mode 100644
index 000000000..a713c6e2b
--- /dev/null
+++ b/Telegram/SourceFiles/info/reactions_list/info_reactions_list_widget.h
@@ -0,0 +1,82 @@
+/*
+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 "info/info_content_widget.h"
+
+class ChannelData;
+struct PeerListState;
+
+namespace Api {
+struct WhoReadList;
+} // namespace Api
+
+namespace Info::ReactionsList {
+
+class InnerWidget;
+
+class Memento final : public ContentMemento {
+public:
+	Memento(
+		std::shared_ptr<Api::WhoReadList> whoReadIds,
+		FullMsgId contextId,
+		Data::ReactionId selected);
+
+	object_ptr<ContentWidget> createWidget(
+		QWidget *parent,
+		not_null<Controller*> controller,
+		const QRect &geometry) override;
+
+	Section section() const override;
+
+	[[nodiscard]] std::shared_ptr<Api::WhoReadList> whoReadIds() const;
+	[[nodiscard]] FullMsgId contextId() const;
+	[[nodiscard]] Data::ReactionId selected() const;
+
+	void setListState(std::unique_ptr<PeerListState> state);
+	std::unique_ptr<PeerListState> listState();
+
+	~Memento();
+
+private:
+	std::unique_ptr<PeerListState> _listState;
+
+};
+
+class Widget final : public ContentWidget {
+public:
+	Widget(
+		QWidget *parent,
+		not_null<Controller*> controller,
+		std::shared_ptr<Api::WhoReadList> whoReadIds,
+		FullMsgId contextId,
+		Data::ReactionId selected);
+
+	[[nodiscard]] std::shared_ptr<Api::WhoReadList> whoReadIds() const;
+	[[nodiscard]] FullMsgId contextId() const;
+	[[nodiscard]] Data::ReactionId selected() const;
+
+	bool showInternal(
+		not_null<ContentMemento*> memento) override;
+
+	void setInternalState(
+		const QRect &geometry,
+		not_null<Memento*> memento);
+
+	rpl::producer<QString> title() override;
+
+private:
+	void saveState(not_null<Memento*> memento);
+	void restoreState(not_null<Memento*> memento);
+
+	std::shared_ptr<ContentMemento> doCreateMemento() override;
+
+	InnerWidget *_inner = nullptr;
+};
+
+} // namespace Info::ReactionsList
diff --git a/Telegram/SourceFiles/info/requests_list/info_requests_list_widget.cpp b/Telegram/SourceFiles/info/requests_list/info_requests_list_widget.cpp
index ba68ea345..70f638e1c 100644
--- a/Telegram/SourceFiles/info/requests_list/info_requests_list_widget.cpp
+++ b/Telegram/SourceFiles/info/requests_list/info_requests_list_widget.cpp
@@ -7,22 +7,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "info/requests_list/info_requests_list_widget.h"
 
-#include "api/api_chat_participants.h"
-#include "apiwrap.h"
 #include "boxes/peers/edit_peer_requests_box.h"
-#include "boxes/peer_list_box.h"
 #include "data/data_channel.h"
-#include "data/data_peer_values.h"
-#include "data/data_session.h"
 #include "info/info_controller.h"
-#include "main/main_session.h"
 #include "ui/widgets/scroll_area.h"
 #include "ui/search_field_controller.h"
 #include "ui/ui_utility.h"
 #include "lang/lang_keys.h"
 #include "styles/style_info.h"
-#include "styles/style_widgets.h"
-#include "boxes/peers/edit_peer_requests_box.h"
 
 namespace Info::RequestsList {
 namespace {
diff --git a/Telegram/SourceFiles/info/requests_list/info_requests_list_widget.h b/Telegram/SourceFiles/info/requests_list/info_requests_list_widget.h
index d61fc938e..32e04ac5d 100644
--- a/Telegram/SourceFiles/info/requests_list/info_requests_list_widget.h
+++ b/Telegram/SourceFiles/info/requests_list/info_requests_list_widget.h
@@ -36,6 +36,7 @@ public:
 
 private:
 	std::unique_ptr<PeerListState> _listState;
+
 };
 
 class Widget final : public ContentWidget {