From 7905694b31f960e49318a09652c175f0d1984bdf Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Wed, 4 Oct 2017 13:39:59 +0100
Subject: [PATCH] Add tabs and other types links to Info::Media.

---
 .../history/history_inner_widget.cpp          |   2 +-
 .../history/history_inner_widget.h            |   2 +-
 .../SourceFiles/history/history_widget.cpp    |   4 +-
 Telegram/SourceFiles/info/info.style          |   6 +
 .../SourceFiles/info/info_layer_widget.cpp    |   1 +
 .../info/media/info_media_buttons.h           | 129 ++++++++++
 .../info/media/info_media_inner_widget.cpp    | 233 ++++++++++++++++--
 .../info/media/info_media_inner_widget.h      |  43 +++-
 .../info/media/info_media_list_widget.cpp     |  38 +++
 .../info/media/info_media_list_widget.h       |  60 +++++
 .../info/media/info_media_widget.cpp          |  12 +-
 .../profile/info_profile_inner_widget.cpp     |  79 ++----
 .../info/profile/info_profile_values.cpp      |  13 -
 .../info/profile/info_profile_values.h        |  14 --
 Telegram/SourceFiles/ui/wrap/slide_wrap.cpp   |  15 ++
 Telegram/SourceFiles/ui/wrap/slide_wrap.h     |  14 ++
 Telegram/gyp/telegram_sources.txt             |   3 +
 17 files changed, 550 insertions(+), 118 deletions(-)
 create mode 100644 Telegram/SourceFiles/info/media/info_media_buttons.h
 create mode 100644 Telegram/SourceFiles/info/media/info_media_list_widget.cpp
 create mode 100644 Telegram/SourceFiles/info/media/info_media_list_widget.h

diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp
index 74d6278af..6e3a27a87 100644
--- a/Telegram/SourceFiles/history/history_inner_widget.cpp
+++ b/Telegram/SourceFiles/history/history_inner_widget.cpp
@@ -1579,7 +1579,7 @@ void HistoryInner::keyPressEvent(QKeyEvent *e) {
 	}
 }
 
-void HistoryInner::recountHeight() {
+void HistoryInner::recountHistoryGeometry() {
 	int visibleHeight = _scroll->height();
 	int oldHistoryPaddingTop = qMax(visibleHeight - historyHeight() - st::historyPaddingBottom, 0);
 	if (_botAbout && !_botAbout->info->text.isEmpty()) {
diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h
index 0836f4513..b2bb2069b 100644
--- a/Telegram/SourceFiles/history/history_inner_widget.h
+++ b/Telegram/SourceFiles/history/history_inner_widget.h
@@ -47,7 +47,7 @@ public:
 	void touchScrollUpdated(const QPoint &screenPos);
 	QPoint mapPointToItem(QPoint p, HistoryItem *item);
 
-	void recountHeight();
+	void recountHistoryGeometry();
 	void updateSize();
 
 	void repaintItem(const HistoryItem *item);
diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp
index c0d93e99a..0c12be8a9 100644
--- a/Telegram/SourceFiles/history/history_widget.cpp
+++ b/Telegram/SourceFiles/history/history_widget.cpp
@@ -4785,7 +4785,7 @@ int HistoryWidget::countAutomaticScrollTop() {
 void HistoryWidget::updateHistoryGeometry(bool initial, bool loadedDown, const ScrollChange &change) {
 	if (!_history || (initial && _historyInited) || (!initial && !_historyInited)) return;
 	if (_firstLoadRequest || _a_show.animating()) {
-		return; // scrollTopMax etc are not working after recountHeight()
+		return; // scrollTopMax etc are not working after recountHistoryGeometry()
 	}
 
 	auto newScrollHeight = height() - _topBar->height();
@@ -4868,7 +4868,7 @@ void HistoryWidget::updateHistoryGeometry(bool initial, bool loadedDown, const S
 }
 
 void HistoryWidget::updateListSize() {
-	_list->recountHeight();
+	_list->recountHistoryGeometry();
 	auto washidden = _scroll->isHidden();
 	if (washidden) {
 		_scroll->show();
diff --git a/Telegram/SourceFiles/info/info.style b/Telegram/SourceFiles/info/info.style
index 482f7e911..2dc5a4062 100644
--- a/Telegram/SourceFiles/info/info.style
+++ b/Telegram/SourceFiles/info/info.style
@@ -173,9 +173,15 @@ infoIconMembers: icon {{ "info_members", infoIconFg }};
 infoIconNotifications: icon {{ "info_notifications", infoIconFg }};
 infoIconActions: icon {{ "info_actions", infoIconFg }};
 infoIconMediaPhoto: icon {{ "info_media_photo", infoIconFg }};
+infoIconMediaAudio: icon {{ "info_media_audio", infoIconFg }};
+infoIconMediaLink: icon {{ "info_media_link", infoIconFg }};
+infoIconMediaGroup: icon {{ "info_common_groups", infoIconFg }};
+infoIconMediaVoice: icon {{ "info_media_voice", infoIconFg }};
+infoIconMediaRound: icon {{ "info_media_round", infoIconFg }};
 infoInformationIconPosition: point(25px, 12px);
 infoNotificationsIconPosition: point(20px, 5px);
 infoSharedMediaIconPosition: point(20px, 24px);
+infoSharedMediaButtonIconPosition: point(20px, 3px);
 infoIconPosition: point(20px, 15px);
 
 infoLabeledOneLine: FlatLabel(defaultFlatLabel) {
diff --git a/Telegram/SourceFiles/info/info_layer_widget.cpp b/Telegram/SourceFiles/info/info_layer_widget.cpp
index 131bc6b1a..38b4abbba 100644
--- a/Telegram/SourceFiles/info/info_layer_widget.cpp
+++ b/Telegram/SourceFiles/info/info_layer_widget.cpp
@@ -57,6 +57,7 @@ LayerWidget::LayerWidget(
 void LayerWidget::setupHeightConsumers() {
 	_content->desiredHeightValue()
 		| rpl::start_with_next([this](int height) {
+			if (!_content) return;
 			accumulate_max(_desiredHeight, height);
 			resizeToWidth(width());
 			_content->forceContentRepaint();
diff --git a/Telegram/SourceFiles/info/media/info_media_buttons.h b/Telegram/SourceFiles/info/media/info_media_buttons.h
new file mode 100644
index 000000000..b03488512
--- /dev/null
+++ b/Telegram/SourceFiles/info/media/info_media_buttons.h
@@ -0,0 +1,129 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop version of Telegram messaging app, see https://telegram.org
+
+Telegram Desktop is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+It is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+In addition, as a special exception, the copyright holders give permission
+to link the code of portions of this program with the OpenSSL library.
+
+Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
+Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
+*/
+#pragma once
+
+#include <rpl/mappers.h>
+#include <rpl/map.h>
+#include "lang/lang_keys.h"
+#include "storage/storage_shared_media.h"
+#include "info/info_memento.h"
+#include "info/profile/info_profile_button.h"
+#include "info/profile/info_profile_values.h"
+#include "ui/wrap/slide_wrap.h"
+#include "ui/wrap/vertical_layout.h"
+#include "window/window_controller.h"
+#include "styles/style_info.h"
+
+namespace Info {
+namespace Media {
+
+using Type = Storage::SharedMediaType;
+
+inline auto MediaTextPhrase(Type type) {
+	switch (type) {
+	case Type::Photo: return lng_profile_photos;
+	case Type::Video: return lng_profile_videos;
+	case Type::File: return lng_profile_files;
+	case Type::MusicFile: return lng_profile_songs;
+	case Type::Link: return lng_profile_shared_links;
+	case Type::VoiceFile: return lng_profile_audios;
+	case Type::RoundFile: return lng_profile_rounds;
+	}
+	Unexpected("Type in setupSharedMedia()");
+};
+
+inline auto MediaText(Type type) {
+	return [phrase = MediaTextPhrase(type)](int count) {
+		return phrase(lt_count, count);
+	};
+}
+
+template <typename Count, typename Text>
+inline auto AddCountedButton(
+		Ui::VerticalLayout *parent,
+		Count &&count,
+		Text &&textFromCount,
+		Ui::MultiSlideTracker &tracker) {
+	using namespace rpl::mappers;
+
+	using Button = Profile::Button;
+	auto forked = std::move(count)
+		| start_spawning(parent->lifetime());
+	auto text = rpl::duplicate(forked)
+				| rpl::map([textFromCount](int count) {
+					return (count > 0)
+						? textFromCount(count)
+						: QString();
+				});
+	auto button = parent->add(object_ptr<Ui::SlideWrap<Button>>(
+		parent,
+		object_ptr<Button>(
+			parent,
+			std::move(text),
+			st::infoSharedMediaButton))
+	)->toggleOn(
+		rpl::duplicate(forked)
+		| rpl::map($1 > 0));
+	tracker.track(button);
+	return button;
+};
+
+inline auto AddButton(
+		Ui::VerticalLayout *parent,
+		not_null<Window::Controller*> controller,
+		not_null<PeerData*> peer,
+		Type type,
+		Ui::MultiSlideTracker &tracker) {
+	auto result = AddCountedButton(
+		parent,
+		Profile::SharedMediaCountValue(peer, type),
+		MediaText(type),
+		tracker)->entity();
+	result->addClickHandler([controller, peer, type] {
+		controller->showSection(
+			Info::Memento(peer->id, Section(type)));
+	});
+	return std::move(result);
+};
+
+inline auto AddCommonGroupsButton(
+		Ui::VerticalLayout *parent,
+		not_null<Window::Controller*> controller,
+		not_null<UserData*> user,
+		Ui::MultiSlideTracker &tracker) {
+	auto result = AddCountedButton(
+		parent,
+		Profile::CommonGroupsCountValue(user),
+		[](int count) {
+			return lng_profile_common_groups(lt_count, count);
+		},
+		tracker)->entity();
+	result->addClickHandler([controller, user] {
+		controller->showSection(
+			Info::Memento(
+				user->id,
+				Section::Type::CommonGroups));
+	});
+	return std::move(result);
+};
+
+} // namespace Media
+} // namespace Info
\ No newline at end of file
diff --git a/Telegram/SourceFiles/info/media/info_media_inner_widget.cpp b/Telegram/SourceFiles/info/media/info_media_inner_widget.cpp
index ba34fa3e6..ec84443ad 100644
--- a/Telegram/SourceFiles/info/media/info_media_inner_widget.cpp
+++ b/Telegram/SourceFiles/info/media/info_media_inner_widget.cpp
@@ -20,28 +20,160 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 */
 #include "info/media/info_media_inner_widget.h"
 
-#include "ui/widgets/labels.h"
+#include "boxes/abstract_box.h"
+#include "info/media/info_media_list_widget.h"
+#include "info/media/info_media_buttons.h"
+#include "info/profile/info_profile_button.h"
+#include "info/profile/info_profile_icon.h"
+#include "ui/widgets/discrete_sliders.h"
+#include "ui/wrap/vertical_layout.h"
+#include "styles/style_info.h"
+#include "lang/lang_keys.h"
 
 namespace Info {
 namespace Media {
+namespace {
+
+using Type = InnerWidget::Type;
+
+base::optional<int> TypeToTabIndex(Type type) {
+	switch (type) {
+	case Type::Photo: return 0;
+	case Type::Video: return 1;
+	case Type::File: return 2;
+	}
+	return base::none;
+}
+
+Type TabIndexToType(int index) {
+	switch (index) {
+	case 0: return Type::Photo;
+	case 1: return Type::Video;
+	case 2: return Type::File;
+	}
+	Unexpected("Index in Info::Media::TabIndexToType()");
+}
+
+} // namespace
 
 InnerWidget::InnerWidget(
 	QWidget *parent,
+	rpl::producer<Wrap> &&wrap,
+	not_null<Window::Controller*> controller,
 	not_null<PeerData*> peer,
 	Type type)
-: RpWidget(parent)
-, _peer(peer)
-, _type(type) {
-	auto text = qsl("Media Overview\n\n");
-	auto label = object_ptr<Ui::FlatLabel>(this);
-	label->setText(text.repeated(50));
-	widthValue() | rpl::start_with_next([inner = label.data()](int w) {
-		inner->resizeToWidth(w);
-	}, lifetime());
-	label->heightValue() | rpl::start_with_next([this](int h) {
-		_rowsHeightFake = h;
-		resizeToWidth(width());
-	}, lifetime());
+: RpWidget(parent) {
+	_list = setupList(controller, peer, type);
+	setupOtherTypes(std::move(wrap));
+}
+
+void InnerWidget::setupOtherTypes(rpl::producer<Wrap> &&wrap) {
+	std::move(wrap)
+		| rpl::start_with_next([this](Wrap value) {
+			if (value == Wrap::Side
+				&& TypeToTabIndex(type())) {
+				createOtherTypes();
+			} else {
+				_otherTabs = nullptr;
+				_otherTypes.destroy();
+				refreshHeight();
+			}
+		}, lifetime());
+}
+
+void InnerWidget::createOtherTypes() {
+	_otherTabs = nullptr;
+	_otherTypes.create(this);
+
+	createTypeButtons();
+	_otherTypes->add(object_ptr<BoxContentDivider>(_otherTypes));
+	createTabs();
+
+	_otherTypes->heightValue()
+		| rpl::start_with_next(
+			[this] { refreshHeight(); },
+			_otherTypes->lifetime());
+}
+
+void InnerWidget::createTypeButtons() {
+	auto wrap = _otherTypes->add(object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
+		_otherTypes,
+		object_ptr<Ui::VerticalLayout>(_otherTypes)));
+	auto content = wrap->entity();
+	content->add(object_ptr<Ui::FixedHeightWidget>(
+		content,
+		st::infoProfileSkip));
+
+	auto tracker = Ui::MultiSlideTracker();
+	auto addMediaButton = [&](
+			Type type,
+			const style::icon &icon) {
+		auto result = AddButton(
+			content,
+			controller(),
+			peer(),
+			type,
+			tracker);
+		object_ptr<Profile::FloatingIcon>(
+			result,
+			icon,
+			st::infoSharedMediaButtonIconPosition);
+	};
+	auto addCommonGroupsButton = [&](
+			not_null<UserData*> user,
+			const style::icon &icon) {
+		auto result = AddCommonGroupsButton(
+			content,
+			controller(),
+			user,
+			tracker);
+		object_ptr<Profile::FloatingIcon>(
+			result,
+			icon,
+			st::infoSharedMediaButtonIconPosition);
+	};
+
+	addMediaButton(Type::MusicFile, st::infoIconMediaAudio);
+	addMediaButton(Type::Link, st::infoIconMediaLink);
+	if (auto user = peer()->asUser()) {
+		addCommonGroupsButton(user, st::infoIconMediaGroup);
+	}
+	addMediaButton(Type::VoiceFile, st::infoIconMediaVoice);
+	addMediaButton(Type::RoundFile, st::infoIconMediaRound);
+
+	content->add(object_ptr<Ui::FixedHeightWidget>(
+		content,
+		st::infoProfileSkip));
+	wrap->toggleOn(tracker.atLeastOneShownValue());
+	wrap->finishAnimating();
+}
+
+void InnerWidget::createTabs() {
+	_otherTabs = _otherTypes->add(object_ptr<Ui::SettingsSlider>(
+		this,
+		st::infoTabs));
+	auto sections = QStringList();
+	sections.push_back(lang(lng_media_type_photos).toUpper());
+	sections.push_back(lang(lng_media_type_videos).toUpper());
+	sections.push_back(lang(lng_media_type_files).toUpper());
+	_otherTabs->setSections(sections);
+	_otherTabs->sectionActivated()
+		| rpl::map([](int index) { return TabIndexToType(index); })
+		| rpl::start_with_next(
+			[this](Type type) {
+				if (_list->type() != type) {
+					switchToTab(Memento(peer()->id, type));
+				}
+			},
+			_otherTabs->lifetime());
+}
+
+not_null<PeerData*> InnerWidget::peer() const {
+	return _list->peer();
+}
+
+Type InnerWidget::type() const {
+	return _list->type();
 }
 
 void InnerWidget::visibleTopBottomUpdated(
@@ -51,6 +183,50 @@ void InnerWidget::visibleTopBottomUpdated(
 	_visibleBottom = visibleBottom;
 }
 
+bool InnerWidget::showInternal(not_null<Memento*> memento) {
+	if (memento->peerId() != peer()->id) {
+		return false;
+	}
+	auto mementoType = memento->section().mediaType();
+	if (mementoType == type()) {
+		restoreState(memento);
+		return true;
+	} else if (_otherTypes) {
+		if (TypeToTabIndex(mementoType)) {
+			switchToTab(std::move(*memento));
+			return true;
+		}
+	}
+	return false;
+}
+
+void InnerWidget::switchToTab(Memento &&memento) {
+	auto type = memento.section().mediaType();
+	_list = setupList(controller(), peer(), type);
+	restoreState(&memento);
+	_otherTabs->setActiveSection(*TypeToTabIndex(type));
+}
+
+not_null<Window::Controller*> InnerWidget::controller() const {
+	return _list->controller();
+}
+
+object_ptr<ListWidget> InnerWidget::setupList(
+		not_null<Window::Controller*> controller,
+		not_null<PeerData*> peer,
+		Type type) {
+	auto result = object_ptr<ListWidget>(
+		this,
+		controller,
+		peer,
+		type);
+	result->heightValue()
+		| rpl::start_with_next(
+			[this] { refreshHeight(); },
+			result->lifetime());
+	return result;
+}
+
 void InnerWidget::saveState(not_null<Memento*> memento) {
 }
 
@@ -58,7 +234,34 @@ void InnerWidget::restoreState(not_null<Memento*> memento) {
 }
 
 int InnerWidget::resizeGetHeight(int newWidth) {
-	return _rowsHeightFake;
+	_inResize = true;
+	auto guard = gsl::finally([this] { _inResize = false; });
+
+	if (_otherTypes) {
+		_otherTypes->resizeToWidth(newWidth);
+	}
+	_list->resizeToWidth(newWidth);
+	return recountHeight();
+}
+
+void InnerWidget::refreshHeight() {
+	if (_inResize) {
+		return;
+	}
+	resize(width(), recountHeight());
+}
+
+int InnerWidget::recountHeight() {
+	auto top = 0;
+	if (_otherTypes) {
+		_otherTypes->moveToLeft(0, top);
+		top += _otherTypes->heightNoMargins();
+	}
+	if (_list) {
+		_list->moveToLeft(0, top);
+		top += _list->heightNoMargins();
+	}
+	return top;
 }
 
 } // namespace Media
diff --git a/Telegram/SourceFiles/info/media/info_media_inner_widget.h b/Telegram/SourceFiles/info/media/info_media_inner_widget.h
index 07b75cf33..33c3bbe77 100644
--- a/Telegram/SourceFiles/info/media/info_media_inner_widget.h
+++ b/Telegram/SourceFiles/info/media/info_media_inner_widget.h
@@ -23,23 +23,31 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 #include "ui/rp_widget.h"
 #include "info/media/info_media_widget.h"
 
+namespace Ui {
+class SettingsSlider;
+class VerticalLayout;
+} // namespace Ui
+
 namespace Info {
 namespace Media {
 
+class Memento;
+class ListWidget;
+
 class InnerWidget final : public Ui::RpWidget {
 public:
 	using Type = Widget::Type;
 	InnerWidget(
 		QWidget *parent,
+		rpl::producer<Wrap> &&wrap,
+		not_null<Window::Controller*> controller,
 		not_null<PeerData*> peer,
 		Type type);
 
-	not_null<PeerData*> peer() const {
-		return _peer;
-	}
-	Type type() const {
-		return _type;
-	}
+	not_null<PeerData*> peer() const;
+	Type type() const;
+
+	bool showInternal(not_null<Memento*> memento);
 
 	void saveState(not_null<Memento*> memento);
 	void restoreState(not_null<Memento*> memento);
@@ -51,10 +59,27 @@ protected:
 		int visibleBottom) override;
 
 private:
-	not_null<PeerData*> _peer;
-	Type _type = Type::Photo;
+	int recountHeight();
+	void refreshHeight();
+	void setupOtherTypes(rpl::producer<Wrap> &&wrap);
+	void createOtherTypes();
+	void createTypeButtons();
+	void createTabs();
+	void switchToTab(Memento &&memento);
+
+	not_null<Window::Controller*> controller() const;
+
+	object_ptr<ListWidget> setupList(
+		not_null<Window::Controller*> controller,
+		not_null<PeerData*> peer,
+		Type type);
+
+	bool _inResize = false;
+
+	Ui::SettingsSlider *_otherTabs = nullptr;
+	object_ptr<Ui::VerticalLayout> _otherTypes = { nullptr };
+	object_ptr<ListWidget> _list = { nullptr };
 
-	int _rowsHeightFake = 0;
 	int _visibleTop = 0;
 	int _visibleBottom = 0;
 
diff --git a/Telegram/SourceFiles/info/media/info_media_list_widget.cpp b/Telegram/SourceFiles/info/media/info_media_list_widget.cpp
new file mode 100644
index 000000000..696f6fba1
--- /dev/null
+++ b/Telegram/SourceFiles/info/media/info_media_list_widget.cpp
@@ -0,0 +1,38 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop version of Telegram messaging app, see https://telegram.org
+
+Telegram Desktop is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+It is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+In addition, as a special exception, the copyright holders give permission
+to link the code of portions of this program with the OpenSSL library.
+
+Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
+Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
+*/
+#include "info/media/info_media_list_widget.h"
+
+namespace Info {
+namespace Media {
+
+ListWidget::ListWidget(
+	QWidget *parent,
+	not_null<Window::Controller*> controller,
+	not_null<PeerData*> peer,
+	Type type)
+: RpWidget(parent)
+, _controller(controller)
+, _peer(peer)
+, _type(type) {
+}
+
+} // namespace Media
+} // namespace Info
diff --git a/Telegram/SourceFiles/info/media/info_media_list_widget.h b/Telegram/SourceFiles/info/media/info_media_list_widget.h
new file mode 100644
index 000000000..141d2eabc
--- /dev/null
+++ b/Telegram/SourceFiles/info/media/info_media_list_widget.h
@@ -0,0 +1,60 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop version of Telegram messaging app, see https://telegram.org
+
+Telegram Desktop is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+It is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+In addition, as a special exception, the copyright holders give permission
+to link the code of portions of this program with the OpenSSL library.
+
+Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
+Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
+*/
+#pragma once
+
+#include "ui/rp_widget.h"
+#include "info/media/info_media_widget.h"
+
+namespace Window {
+class Controller;
+} // namespace Window
+
+namespace Info {
+namespace Media {
+
+class ListWidget : public Ui::RpWidget {
+public:
+	using Type = Widget::Type;
+	ListWidget(
+		QWidget *parent,
+		not_null<Window::Controller*> controller,
+		not_null<PeerData*> peer,
+		Type type);
+
+	not_null<Window::Controller*> controller() const {
+		return _controller;
+	}
+	not_null<PeerData*> peer() const {
+		return _peer;
+	}
+	Type type() const {
+		return _type;
+	}
+
+private:
+	not_null<Window::Controller*> _controller;
+	not_null<PeerData*> _peer;
+	Type _type = Type::Photo;
+
+};
+
+} // namespace Media
+} // namespace Info
diff --git a/Telegram/SourceFiles/info/media/info_media_widget.cpp b/Telegram/SourceFiles/info/media/info_media_widget.cpp
index ba1296903..aa3a0f805 100644
--- a/Telegram/SourceFiles/info/media/info_media_widget.cpp
+++ b/Telegram/SourceFiles/info/media/info_media_widget.cpp
@@ -47,8 +47,13 @@ Widget::Widget(
 	not_null<Window::Controller*> controller,
 	not_null<PeerData*> peer,
 	Type type)
-: ContentWidget(parent, std::move(wrap), controller, peer) {
-	_inner = setInnerWidget(object_ptr<InnerWidget>(this, peer, type));
+: ContentWidget(parent, rpl::duplicate(wrap), controller, peer) {
+	_inner = setInnerWidget(object_ptr<InnerWidget>(
+		this,
+		std::move(wrap),
+		controller,
+		peer,
+		type));
 }
 
 Section Widget::section() const {
@@ -61,8 +66,7 @@ Widget::Type Widget::type() const {
 
 bool Widget::showInternal(not_null<ContentMemento*> memento) {
 	if (auto mediaMemento = dynamic_cast<Memento*>(memento.get())) {
-		if (mediaMemento->peerId() == peer()->id) {
-			restoreState(mediaMemento);
+		if (_inner->showInternal(mediaMemento)) {
 			return true;
 		}
 	}
diff --git a/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp b/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp
index 214eb567f..ec76957e8 100644
--- a/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp
+++ b/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp
@@ -30,6 +30,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 #include "info/profile/info_profile_cover.h"
 #include "info/profile/info_profile_icon.h"
 #include "info/profile/info_profile_members.h"
+#include "info/media/info_media_buttons.h"
 #include "boxes/abstract_box.h"
 #include "boxes/add_contact_box.h"
 #include "boxes/confirm_box.h"
@@ -151,7 +152,7 @@ object_ptr<Ui::RpWidget> InnerWidget::setupDetails(
 object_ptr<Ui::RpWidget> InnerWidget::setupInfo(
 		RpWidget *parent) const {
 	auto result = object_ptr<Ui::VerticalLayout>(parent);
-	auto tracker = MultiLineTracker();
+	auto tracker = Ui::MultiSlideTracker();
 	auto addInfoLine = [&](
 			LangKey label,
 			rpl::producer<TextWithEntities> &&text,
@@ -222,7 +223,7 @@ void InnerWidget::setupUserButtons(
 		Ui::VerticalLayout *wrap,
 		not_null<UserData*> user) const {
 	using namespace rpl::mappers;
-	auto tracker = MultiLineTracker();
+	auto tracker = Ui::MultiSlideTracker();
 	auto topSkip = wrap->add(createSlideSkipWidget(wrap));
 	auto addButton = [&](auto &&text) {
 		auto result = wrap->add(object_ptr<Ui::SlideWrap<Button>>(
@@ -264,67 +265,26 @@ void InnerWidget::setupUserButtons(
 object_ptr<Ui::RpWidget> InnerWidget::setupSharedMedia(
 		RpWidget *parent) {
 	using namespace rpl::mappers;
+	using MediaType = Media::Type;
 
 	auto content = object_ptr<Ui::VerticalLayout>(parent);
-	auto tracker = MultiLineTracker();
-	auto addButton = [&](
-			auto &&count,
-			auto textFromCount) {
-		auto forked = std::move(count)
-			| start_spawning(content->lifetime());
-		auto button = content->add(object_ptr<Ui::SlideWrap<Button>>(
-			content,
-			object_ptr<Button>(
-				content,
-				rpl::duplicate(forked)
-					| rpl::map([textFromCount](int count) {
-						return (count > 0)
-							? textFromCount(count)
-							: QString();
-					}),
-				st::infoSharedMediaButton))
-		)->toggleOn(
-			rpl::duplicate(forked)
-				| rpl::map($1 > 0));
-		tracker.track(button);
-		return button;
-	};
-	using MediaType = Storage::SharedMediaType;
-	auto mediaText = [](MediaType type) {
-		switch (type) {
-		case MediaType::Photo: return lng_profile_photos;
-		case MediaType::Video: return lng_profile_videos;
-		case MediaType::File: return lng_profile_files;
-		case MediaType::MusicFile: return lng_profile_songs;
-		case MediaType::Link: return lng_profile_shared_links;
-		case MediaType::VoiceFile: return lng_profile_audios;
-		case MediaType::RoundFile: return lng_profile_rounds;
-		}
-		Unexpected("Type in setupSharedMedia()");
-	};
+	auto tracker = Ui::MultiSlideTracker();
 	auto addMediaButton = [&](MediaType type) {
-		return addButton(
-			SharedMediaCountValue(_peer, type),
-			[phrase = mediaText(type)](int count) {
-				return phrase(lt_count, count);
-			}
-		)->entity()->addClickHandler([this, peer = _peer, type] {
-			_controller->showSection(
-				Info::Memento(peer->id, Section(type)));
-		});
+		return Media::AddButton(
+			content,
+			_controller,
+			peer(),
+			type,
+			tracker);
 	};
 	auto addCommonGroupsButton = [&](not_null<UserData*> user) {
-		return addButton(
-			CommonGroupsCountValue(user),
-			[](int count) {
-				return lng_profile_common_groups(lt_count, count);
-			}
-		)->entity()->addClickHandler([this, peer = _peer] {
-			_controller->showSection(
-				::Profile::CommonGroups::SectionMemento(
-					peer->asUser()));
-		});
+		return Media::AddCommonGroupsButton(
+			content,
+			_controller,
+			user,
+			tracker);
 	};
+
 	addMediaButton(MediaType::Photo);
 	addMediaButton(MediaType::Video);
 	addMediaButton(MediaType::File);
@@ -343,8 +303,9 @@ object_ptr<Ui::RpWidget> InnerWidget::setupSharedMedia(
 	result->toggleOn(tracker.atLeastOneShownValue());
 	auto layout = result->entity();
 
-	layout->add(object_ptr<BoxContentDivider>(result));
-	_sharedMediaCover = layout->add(object_ptr<SharedMediaCover>(layout));
+	layout->add(object_ptr<BoxContentDivider>(layout));
+	_sharedMediaCover = layout->add(
+		object_ptr<SharedMediaCover>(layout));
 	if (canHideDetailsEver()) {
 		_sharedMediaCover->setToggleShown(canHideDetails());
 		_sharedMediaWrap = layout->add(object_ptr<Ui::SlideWrap<>>(
diff --git a/Telegram/SourceFiles/info/profile/info_profile_values.cpp b/Telegram/SourceFiles/info/profile/info_profile_values.cpp
index c3d8d1cab..2c74b8365 100644
--- a/Telegram/SourceFiles/info/profile/info_profile_values.cpp
+++ b/Telegram/SourceFiles/info/profile/info_profile_values.cpp
@@ -240,18 +240,5 @@ rpl::producer<bool> CanAddMemberValue(
 	return rpl::single(false);
 }
 
-rpl::producer<bool> MultiLineTracker::atLeastOneShownValue() const {
-	auto shown = std::vector<rpl::producer<bool>>();
-	shown.reserve(_widgets.size());
-	for (auto &widget : _widgets) {
-		shown.push_back(widget->toggledValue());
-	}
-	return rpl::combine(
-		std::move(shown),
-		[](const std::vector<bool> &values) {
-			return base::find(values, true) != values.end();
-		});
-}
-
 } // namespace Profile
 } // namespace Info
diff --git a/Telegram/SourceFiles/info/profile/info_profile_values.h b/Telegram/SourceFiles/info/profile/info_profile_values.h
index cc0c9ba63..0b5817962 100644
--- a/Telegram/SourceFiles/info/profile/info_profile_values.h
+++ b/Telegram/SourceFiles/info/profile/info_profile_values.h
@@ -77,19 +77,5 @@ rpl::producer<int> CommonGroupsCountValue(
 rpl::producer<bool> CanAddMemberValue(
 	not_null<PeerData*> peer);
 
-class MultiLineTracker {
-public:
-	template <typename Widget>
-	void track(const Ui::SlideWrap<Widget> *wrap) {
-		_widgets.push_back(wrap);
-	}
-
-	rpl::producer<bool> atLeastOneShownValue() const;
-
-private:
-	std::vector<const Ui::SlideWrap<Ui::RpWidget>*> _widgets;
-
-};
-
 } // namespace Profile
 } // namespace Info
diff --git a/Telegram/SourceFiles/ui/wrap/slide_wrap.cpp b/Telegram/SourceFiles/ui/wrap/slide_wrap.cpp
index 412822f4d..7e652626b 100644
--- a/Telegram/SourceFiles/ui/wrap/slide_wrap.cpp
+++ b/Telegram/SourceFiles/ui/wrap/slide_wrap.cpp
@@ -20,6 +20,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 */
 #include "ui/wrap/slide_wrap.h"
 
+#include <rpl/combine.h>
+
 namespace Ui {
 
 SlideWrap<RpWidget>::SlideWrap(
@@ -144,5 +146,18 @@ void SlideWrap<RpWidget>::wrappedSizeUpdated(QSize size) {
 	}
 }
 
+rpl::producer<bool> MultiSlideTracker::atLeastOneShownValue() const {
+	auto shown = std::vector<rpl::producer<bool>>();
+	shown.reserve(_widgets.size());
+	for (auto &widget : _widgets) {
+		shown.push_back(widget->toggledValue());
+	}
+	return rpl::combine(
+		std::move(shown),
+		[](const std::vector<bool> &values) {
+			return base::find(values, true) != values.end();
+		});
+}
+
 } // namespace Ui
 
diff --git a/Telegram/SourceFiles/ui/wrap/slide_wrap.h b/Telegram/SourceFiles/ui/wrap/slide_wrap.h
index 70414fc77..811e53f7b 100644
--- a/Telegram/SourceFiles/ui/wrap/slide_wrap.h
+++ b/Telegram/SourceFiles/ui/wrap/slide_wrap.h
@@ -136,5 +136,19 @@ inline object_ptr<SlideWrap<>> CreateSlideSkipWidget(
 		QMargins(0, 0, 0, skip));
 }
 
+class MultiSlideTracker {
+public:
+	template <typename Widget>
+	void track(const Ui::SlideWrap<Widget> *wrap) {
+		_widgets.push_back(wrap);
+	}
+
+	rpl::producer<bool> atLeastOneShownValue() const;
+
+private:
+	std::vector<const Ui::SlideWrap<Ui::RpWidget>*> _widgets;
+
+};
+
 } // namespace Ui
 
diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt
index dfd6b606b..47b2b3685 100644
--- a/Telegram/gyp/telegram_sources.txt
+++ b/Telegram/gyp/telegram_sources.txt
@@ -221,8 +221,11 @@
 <(src_loc)/info/info_top_bar.h
 <(src_loc)/info/info_wrap_widget.cpp
 <(src_loc)/info/info_wrap_widget.h
+<(src_loc)/info/media/info_media_buttons.h
 <(src_loc)/info/media/info_media_inner_widget.cpp
 <(src_loc)/info/media/info_media_inner_widget.h
+<(src_loc)/info/media/info_media_list_widget.cpp
+<(src_loc)/info/media/info_media_list_widget.h
 <(src_loc)/info/media/info_media_widget.cpp
 <(src_loc)/info/media/info_media_widget.h
 <(src_loc)/info/profile/info_profile_button.cpp