From 2363a6bd4411505551fd8a66d9686ff1a93f0a5f Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Fri, 18 Aug 2017 22:14:31 +0300
Subject: [PATCH] Add SharedMediaSlice to observe shared media.

Start testing / using it in MediaView.
---
 Telegram/SourceFiles/apiwrap.cpp              | 153 ++++
 Telegram/SourceFiles/apiwrap.h                |  46 +-
 Telegram/SourceFiles/auth_session.h           |   2 +-
 Telegram/SourceFiles/base/observer.h          |   4 +-
 Telegram/SourceFiles/config.h                 |   1 -
 .../history/history_shared_media.cpp          | 409 +++++++++
 .../history/history_shared_media.h            | 316 +++++++
 Telegram/SourceFiles/mediaview.cpp            | 804 +++++++++---------
 Telegram/SourceFiles/mediaview.h              |  33 +-
 Telegram/SourceFiles/overviewwidget.cpp       |   2 +-
 .../SourceFiles/storage/storage_facade.cpp    |  27 +
 Telegram/SourceFiles/storage/storage_facade.h |   5 +
 .../storage/storage_shared_media.cpp          | 109 ++-
 .../storage/storage_shared_media.h            |  85 +-
 Telegram/gyp/telegram_sources.txt             |   2 +
 15 files changed, 1548 insertions(+), 450 deletions(-)
 create mode 100644 Telegram/SourceFiles/history/history_shared_media.cpp
 create mode 100644 Telegram/SourceFiles/history/history_shared_media.h

diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp
index 72dc4655a..9fd29eb9e 100644
--- a/Telegram/SourceFiles/apiwrap.cpp
+++ b/Telegram/SourceFiles/apiwrap.cpp
@@ -35,6 +35,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 #include "window/notifications_manager.h"
 #include "chat_helpers/message_field.h"
 #include "chat_helpers/stickers.h"
+#include "storage/storage_facade.h"
+#include "storage/storage_shared_media.h"
 
 namespace {
 
@@ -46,6 +48,7 @@ constexpr auto kStickersUpdateTimeout = 3600000; // update not more than once in
 constexpr auto kUnreadMentionsPreloadIfLess = 5;
 constexpr auto kUnreadMentionsFirstRequestLimit = 10;
 constexpr auto kUnreadMentionsNextRequestLimit = 100;
+constexpr auto kSharedMediaLimit = 10;
 
 } // namespace
 
@@ -1911,4 +1914,154 @@ void ApiWrap::sendSaveChatAdminsRequests(not_null<ChatData*> chat) {
 	requestSendDelayed();
 }
 
+void ApiWrap::requestSharedMedia(
+		not_null<PeerData*> peer,
+		SharedMediaType type,
+		MsgId messageId,
+		SliceType slice) {
+	auto key = std::make_tuple(peer, type, messageId, slice);
+	if (_sharedMediaRequests.contains(key)) {
+		return;
+	}
+
+	auto filter = [&] {
+		using Type = SharedMediaType;
+		switch (type) {
+		case Type::Photo: return MTP_inputMessagesFilterPhotos();
+		case Type::Video: return MTP_inputMessagesFilterVideo();
+		case Type::MusicFile: return MTP_inputMessagesFilterMusic();
+		case Type::File: return MTP_inputMessagesFilterDocument();
+		case Type::VoiceFile: return MTP_inputMessagesFilterVoice();
+		case Type::RoundVoiceFile: return MTP_inputMessagesFilterRoundVoice();
+		case Type::GIF: return MTP_inputMessagesFilterGif();
+		case Type::Link: return MTP_inputMessagesFilterUrl();
+		case Type::ChatPhoto: return MTP_inputMessagesFilterChatPhotos();
+		}
+		return MTP_inputMessagesFilterEmpty();
+	}();
+	if (filter.type() == mtpc_inputMessagesFilterEmpty) {
+		return;
+	}
+
+	auto minId = 0;
+	auto maxId = 0;
+	auto limit = messageId ? kSharedMediaLimit : 0;
+	auto offsetId = [&] {
+		switch (slice) {
+		case SliceType::Before:
+		case SliceType::Around: return messageId;
+		case SliceType::After: return messageId + 1;
+		}
+		Unexpected("Slice type in ApiWrap::requestSharedMedia");
+	}();
+	auto addOffset = [&] {
+		switch (slice) {
+		case SliceType::Before: return 0;
+		case SliceType::Around: return -limit / 2;
+		case SliceType::After: return -limit;
+		}
+		Unexpected("Slice type in ApiWrap::requestSharedMedia");
+	}();
+
+	LOG(("REQUESTING SHARED MEDIA: %1, %2, %3").arg(static_cast<int>(type)).arg(messageId).arg(static_cast<int>(slice)));
+	auto requestId = request(MTPmessages_Search(
+		MTP_flags(0),
+		peer->input,
+		MTPstring(),
+		MTP_inputUserEmpty(),
+		filter,
+		MTP_int(0),
+		MTP_int(0),
+		MTP_int(offsetId),
+		MTP_int(addOffset),
+		MTP_int(limit),
+		MTP_int(maxId),
+		MTP_int(minId)
+	)).done([this, peer, type, messageId, slice](const MTPmessages_Messages &result) {
+		_sharedMediaRequests.remove(std::make_tuple(peer, type, messageId, slice));
+		sharedMediaDone(peer, type, messageId, slice, result);
+	}).fail([this, key](const RPCError &error) {
+		_sharedMediaRequests.remove(key);
+	}).send();
+	_sharedMediaRequests.emplace(key, requestId);
+}
+
+void ApiWrap::sharedMediaDone(
+		not_null<PeerData*> peer,
+		SharedMediaType type,
+		MsgId messageId,
+		SliceType slice,
+		const MTPmessages_Messages &result) {
+
+	auto fullCount = 0;
+	auto &messages = *[&] {
+		switch (result.type()) {
+		case mtpc_messages_messages: {
+			auto &d = result.c_messages_messages();
+			App::feedUsers(d.vusers);
+			App::feedChats(d.vchats);
+			fullCount = d.vmessages.v.size();
+			return &d.vmessages.v;
+		} break;
+
+		case mtpc_messages_messagesSlice: {
+			auto &d = result.c_messages_messagesSlice();
+			App::feedUsers(d.vusers);
+			App::feedChats(d.vchats);
+			fullCount = d.vcount.v;
+			return &d.vmessages.v;
+		} break;
+
+		case mtpc_messages_channelMessages: {
+			auto &d = result.c_messages_channelMessages();
+			if (auto channel = peer->asChannel()) {
+				channel->ptsReceived(d.vpts.v);
+			} else {
+				LOG(("API Error: received messages.channelMessages when no channel was passed! (ApiWrap::sharedMediaDone)"));
+			}
+			App::feedUsers(d.vusers);
+			App::feedChats(d.vchats);
+			fullCount = d.vcount.v;
+			return &d.vmessages.v;
+		} break;
+		}
+		Unexpected("messages.Messages type in sharedMediaDone()");
+	}();
+
+	auto noSkipRange = MsgRange { messageId, messageId };
+	auto messageIds = std::vector<MsgId>();
+	auto addType = NewMessageExisting;
+	messageIds.reserve(messages.size());
+	for (auto &message : messages) {
+		if (auto item = App::histories().addNewMessage(message, addType)) {
+			if (item->sharedMediaTypes().test(type)) {
+				auto itemId = item->id;
+				messageIds.push_back(itemId);
+				accumulate_min(noSkipRange.from, itemId);
+				accumulate_max(noSkipRange.till, itemId);
+			}
+		}
+	}
+	if (messageId && messageIds.empty()) {
+		noSkipRange = [&]() -> MsgRange {
+			switch (slice) {
+			case SliceType::Before: // All old loaded.
+				return { 0, noSkipRange.till };
+			case SliceType::Around: // All loaded.
+				return { 0, ServerMaxMsgId };
+			case SliceType::After: // All new loaded.
+				return { noSkipRange.from, ServerMaxMsgId };
+			}
+			Unexpected("Slice type in ApiWrap::sharedMediaDone");
+		}();
+	}
+	Auth().storage().add(Storage::SharedMediaAddSlice(
+		peer->id,
+		type,
+		std::move(messageIds),
+		noSkipRange,
+		fullCount
+	));
+}
+
 ApiWrap::~ApiWrap() = default;
diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h
index 7b7a94b8f..d71493569 100644
--- a/Telegram/SourceFiles/apiwrap.h
+++ b/Telegram/SourceFiles/apiwrap.h
@@ -28,6 +28,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 
 class AuthSession;
 
+namespace Storage {
+enum class SharedMediaType : char;
+} // namespace Storage
+
 namespace Api {
 
 inline const MTPVector<MTPChat> *getChatsFromMessagesChats(const MTPmessages_Chats &chats) {
@@ -108,6 +112,22 @@ public:
 		bool adminsEnabled,
 		base::flat_set<not_null<UserData*>> &&admins);
 
+	enum class SliceType {
+		Around,
+		Before,
+		After,
+	};
+	void requestSharedMedia(
+		not_null<PeerData*> peer,
+		Storage::SharedMediaType type,
+		MsgId messageId,
+		SliceType slice);
+	void requestSharedMediaCount(
+			not_null<PeerData*> peer,
+			Storage::SharedMediaType type) {
+		requestSharedMedia(peer, type, 0, SliceType::Before);
+	}
+
 	~ApiWrap();
 
 private:
@@ -117,6 +137,7 @@ private:
 		Callbacks callbacks;
 	};
 	using MessageDataRequests = QMap<MsgId, MessageDataRequest>;
+	using SharedMediaType = Storage::SharedMediaType;
 
 	void requestAppChangelogs();
 	void addLocalChangelogs(int oldAppVersion);
@@ -153,6 +174,13 @@ private:
 	void saveChatAdmins(not_null<ChatData*> chat);
 	void sendSaveChatAdminsRequests(not_null<ChatData*> chat);
 
+	void sharedMediaDone(
+		not_null<PeerData*> peer,
+		SharedMediaType type,
+		MsgId messageId,
+		SliceType slice,
+		const MTPmessages_Messages &result);
+
 	not_null<AuthSession*> _session;
 	mtpRequestId _changelogSubscription = 0;
 
@@ -205,9 +233,21 @@ private:
 
 	base::flat_map<not_null<History*>, mtpRequestId> _unreadMentionsRequests;
 
-	base::flat_map<not_null<ChatData*>, mtpRequestId> _chatAdminsEnabledRequests;
-	base::flat_map<not_null<ChatData*>, base::flat_set<not_null<UserData*>>> _chatAdminsToSave;
-	base::flat_map<not_null<ChatData*>, base::flat_set<mtpRequestId>> _chatAdminsSaveRequests;
+	base::flat_map<
+		not_null<ChatData*>,
+		mtpRequestId> _chatAdminsEnabledRequests;
+	base::flat_map<
+		not_null<ChatData*>,
+		base::flat_set<not_null<UserData*>>> _chatAdminsToSave;
+	base::flat_map<
+		not_null<ChatData*>,
+		base::flat_set<mtpRequestId>> _chatAdminsSaveRequests;
+
+	base::flat_map<std::tuple<
+		not_null<PeerData*>,
+		SharedMediaType,
+		MsgId,
+		SliceType>, mtpRequestId> _sharedMediaRequests;
 
 	base::Observable<PeerData*> _fullPeerUpdated;
 
diff --git a/Telegram/SourceFiles/auth_session.h b/Telegram/SourceFiles/auth_session.h
index 9430b67c1..8f9134b0c 100644
--- a/Telegram/SourceFiles/auth_session.h
+++ b/Telegram/SourceFiles/auth_session.h
@@ -227,7 +227,7 @@ public:
 	void checkAutoLockIn(TimeMs time);
 
 	base::Observable<DocumentData*> documentUpdated;
-	base::Observable<std::pair<HistoryItem*, MsgId>> messageIdChanging;
+	base::Observable<std::pair<not_null<HistoryItem*>, MsgId>> messageIdChanging;
 
 	~AuthSession();
 
diff --git a/Telegram/SourceFiles/base/observer.h b/Telegram/SourceFiles/base/observer.h
index 362c6d42b..24de58399 100644
--- a/Telegram/SourceFiles/base/observer.h
+++ b/Telegram/SourceFiles/base/observer.h
@@ -366,9 +366,9 @@ class Observable : public internal::BaseObservable<EventType, Handler, base::typ
 public:
 	Observable() = default;
 	Observable(const Observable &other) = delete;
-	Observable(Observable &&other) = delete;
+	Observable(Observable &&other) = default;
 	Observable &operator=(const Observable &other) = delete;
-	Observable &operator=(Observable &&other) = delete;
+	Observable &operator=(Observable &&other) = default;
 
 };
 
diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h
index 633c4e4a6..0e87f9549 100644
--- a/Telegram/SourceFiles/config.h
+++ b/Telegram/SourceFiles/config.h
@@ -87,7 +87,6 @@ enum {
 	SearchManyPerPage = 100,
 	LinksOverviewPerPage = 12,
 	MediaOverviewStartPerPage = 5,
-	MediaOverviewPreloadCount = 4,
 
 	AudioVoiceMsgMaxLength = 100 * 60, // 100 minutes
 	AudioVoiceMsgUpdateView = 100, // 100ms
diff --git a/Telegram/SourceFiles/history/history_shared_media.cpp b/Telegram/SourceFiles/history/history_shared_media.cpp
new file mode 100644
index 000000000..774fb9d83
--- /dev/null
+++ b/Telegram/SourceFiles/history/history_shared_media.cpp
@@ -0,0 +1,409 @@
+/*
+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 "history/history_shared_media.h"
+
+#include "auth_session.h"
+#include "apiwrap.h"
+#include "storage/storage_facade.h"
+#include "storage/storage_shared_media.h"
+
+namespace {
+
+using Type = SharedMediaViewer::Type;
+
+inline MediaOverviewType SharedMediaTypeToOverview(Type type) {
+	switch (type) {
+	case Type::Photo: return OverviewPhotos;
+	case Type::Video: return OverviewVideos;
+	case Type::MusicFile: return OverviewMusicFiles;
+	case Type::File: return OverviewFiles;
+	case Type::VoiceFile: return OverviewVoiceFiles;
+	case Type::Link: return OverviewLinks;
+	default: break;
+	}
+	return OverviewCount;
+}
+
+not_null<History*> GetActualHistory(not_null<History*> history) {
+	if (auto to = history->peer->migrateTo()) {
+		return App::history(to);
+	}
+	return history;
+}
+
+History *GetMigratedHistory(
+		not_null<History*> passedHistory,
+		not_null<History*> actualHistory) {
+	if (actualHistory != passedHistory) {
+		return passedHistory;
+	} else if (auto from = actualHistory->peer->migrateFrom()) {
+		return App::history(from);
+	}
+	return nullptr;
+}
+
+} // namespace
+
+SharedMediaViewer::SharedMediaViewer(
+	Key key,
+	int limitBefore,
+	int limitAfter)
+	: _key(key)
+	, _limitBefore(limitBefore)
+	, _limitAfter(limitAfter)
+	, _data(_key) {
+}
+
+base::optional<Storage::SharedMediaType> SharedMediaOverviewType(
+		Storage::SharedMediaType type) {
+	if (SharedMediaTypeToOverview(type) != OverviewCount) {
+		return type;
+	}
+	return base::none;
+}
+
+void SharedMediaShowOverview(
+		Storage::SharedMediaType type,
+		not_null<History*> history) {
+	if (SharedMediaOverviewType(type)) {
+		Ui::showPeerOverview(history, SharedMediaTypeToOverview(type));
+	}
+}
+
+void SharedMediaViewer::start() {
+	auto applyUpdateCallback = [this](auto &update) {
+		this->applyUpdate(update);
+	};
+	subscribe(Auth().storage().sharedMediaSliceUpdated(), applyUpdateCallback);
+	subscribe(Auth().storage().sharedMediaOneRemoved(), applyUpdateCallback);
+	subscribe(Auth().storage().sharedMediaAllRemoved(), applyUpdateCallback);
+
+	loadInitial();
+}
+
+void SharedMediaViewer::loadInitial() {
+	auto weak = base::make_weak_unique(this);
+	Auth().storage().query(Storage::SharedMediaQuery(
+		_key,
+		_limitBefore,
+		_limitAfter), [weak](Storage::SharedMediaResult &&result) {
+		if (weak) {
+			weak->applyStoredResult(std::move(result));
+		}
+	});
+}
+
+void SharedMediaViewer::applyStoredResult(Storage::SharedMediaResult &&result) {
+	mergeSliceData(
+		result.count,
+		result.messageIds,
+		result.skippedBefore,
+		result.skippedAfter);
+}
+
+void SharedMediaViewer::mergeSliceData(
+		base::optional<int> count,
+		const base::flat_set<MsgId> &messageIds,
+		base::optional<int> skippedBefore,
+		base::optional<int> skippedAfter) {
+	if (messageIds.empty()) {
+		if (count && *_data._fullCount != *count) {
+			_data._fullCount = count;
+			updated.notify(_data);
+		}
+		return;
+	}
+	if (count) {
+		_data._fullCount = count;
+	}
+	auto wasMinId = _data._ids.empty() ? -1 : _data._ids.front();
+	auto wasMaxId = _data._ids.empty() ? -1 : _data._ids.back();
+	_data._ids.merge(messageIds.begin(), messageIds.end());
+
+	auto adjustSkippedBefore = [&](MsgId oldId, int oldSkippedBefore) {
+		auto it = _data._ids.find(oldId);
+		Assert(it != _data._ids.end());
+		_data._skippedBefore = oldSkippedBefore - (it - _data._ids.begin());
+		accumulate_max(*_data._skippedBefore, 0);
+	};
+	if (skippedBefore) {
+		adjustSkippedBefore(messageIds.front(), *skippedBefore);
+	} else if (wasMinId >= 0 && _data._skippedBefore) {
+		adjustSkippedBefore(wasMinId, *_data._skippedBefore);
+	} else {
+		_data._skippedBefore = base::none;
+	}
+
+	auto adjustSkippedAfter = [&](MsgId oldId, int oldSkippedAfter) {
+		auto it = _data._ids.find(oldId);
+		Assert(it != _data._ids.end());
+		_data._skippedAfter = oldSkippedAfter - (_data._ids.end() - it - 1);
+		accumulate_max(*_data._skippedAfter, 0);
+	};
+	if (skippedAfter) {
+		adjustSkippedAfter(messageIds.back(), *skippedAfter);
+	} else if (wasMaxId >= 0 && _data._skippedAfter) {
+		adjustSkippedAfter(wasMaxId, *_data._skippedAfter);
+	} else {
+		_data._skippedAfter = base::none;
+	}
+
+	if (_data._fullCount) {
+		if (_data._skippedBefore && !_data._skippedAfter) {
+			_data._skippedAfter = *_data._fullCount
+				- *_data._skippedBefore
+				- int(_data._ids.size());
+		} else if (_data._skippedAfter && !_data._skippedBefore) {
+			_data._skippedBefore = *_data._fullCount
+				- *_data._skippedAfter
+				- int(_data._ids.size());
+		}
+	}
+
+	sliceToLimits();
+
+	updated.notify(_data);
+}
+
+void SharedMediaViewer::applyUpdate(const SliceUpdate &update) {
+	if (update.peerId != _key.peerId || update.type != _key.type) {
+		return;
+	}
+	auto intersects = [](MsgRange range1, MsgRange range2) {
+		return (range1.from <= range2.till) && (range2.from <= range1.till);
+	};
+	if (!intersects(update.range, {
+		_data._ids.empty() ? _key.messageId : _data._ids.front(),
+		_data._ids.empty() ? _key.messageId : _data._ids.back() })) {
+		return;
+	}
+	auto skippedBefore = (update.range.from == 0)
+		? 0
+		: base::optional<int> {};
+	auto skippedAfter = (update.range.till == ServerMaxMsgId)
+		? 0
+		: base::optional<int> {};
+	mergeSliceData(
+		update.count,
+		update.messages ? *update.messages : base::flat_set<MsgId> {},
+		skippedBefore,
+		skippedAfter);
+}
+
+void SharedMediaViewer::applyUpdate(const OneRemoved &update) {
+	if (update.peerId != _key.peerId || !update.types.test(_key.type)) {
+		return;
+	}
+	auto changed = false;
+	if (_data._fullCount && *_data._fullCount > 0) {
+		--*_data._fullCount;
+		changed = true;
+	}
+	if (_data._ids.contains(update.messageId)) {
+		_data._ids.remove(update.messageId);
+		changed = true;
+	} else if (!_data._ids.empty()) {
+		if (_data._ids.front() > update.messageId
+			&& _data._skippedBefore
+			&& *_data._skippedBefore > 0) {
+			--*_data._skippedBefore;
+			changed = true;
+		} else if (_data._ids.back() < update.messageId
+			&& _data._skippedAfter
+			&& *_data._skippedAfter > 0) {
+			--*_data._skippedAfter;
+			changed = true;
+		}
+	}
+	if (changed) {
+		updated.notify(_data);
+	}
+}
+
+void SharedMediaViewer::applyUpdate(const AllRemoved &update) {
+	if (update.peerId != _key.peerId) {
+		return;
+	}
+	_data = SharedMediaSlice(_key, 0);
+	updated.notify(_data);
+}
+
+void SharedMediaViewer::sliceToLimits() {
+	auto aroundIt = base::lower_bound(_data._ids, _key.messageId);
+	auto removeFromBegin = (aroundIt - _data._ids.begin() - _limitBefore);
+	auto removeFromEnd = (_data._ids.end() - aroundIt - _limitAfter - 1);
+	if (removeFromBegin > 0) {
+		_data._ids.erase(_data._ids.begin(), _data._ids.begin() + removeFromBegin);
+		if (_data._skippedBefore) {
+			*_data._skippedBefore += removeFromBegin;
+		}
+	} else if (removeFromBegin < 0 && (!_data._skippedBefore || *_data._skippedBefore > 0)) {
+		requestMessages(RequestDirection::Before);
+	}
+	if (removeFromEnd > 0) {
+		_data._ids.erase(_data._ids.end() - removeFromEnd, _data._ids.end());
+		if (_data._skippedAfter) {
+			*_data._skippedAfter += removeFromEnd;
+		}
+	} else if (removeFromEnd < 0 && (!_data._skippedAfter || *_data._skippedAfter > 0)) {
+		requestMessages(RequestDirection::After);
+	}
+}
+
+void SharedMediaViewer::requestMessages(RequestDirection direction) {
+	using SliceType = ApiWrap::SliceType;
+	auto requestAroundData = [&]() -> std::pair<MsgId, SliceType> {
+		if (_data._ids.empty()) {
+			return { _key.messageId, SliceType::Around };
+		} else if (direction == RequestDirection::Before) {
+			return { _data._ids.front(), SliceType::Before };
+		}
+		return { _data._ids.back(), SliceType::After };
+	}();
+	Auth().api().requestSharedMedia(
+		App::peer(_key.peerId),
+		_key.type,
+		requestAroundData.first,
+		requestAroundData.second);
+}
+
+//
+//base::optional<int> SharedMediaViewerMerged::Data::fullCount() const {
+//	if (_historyCount && _migratedCount) {
+//		return (*_historyCount + *_migratedCount);
+//	}
+//	return base::none;
+//}
+//base::optional<int> SharedMediaViewerMerged::Data::skippedBefore() const {
+//	if (_ids.empty()) {
+//		return base::none;
+//	} else if (!IsServerMsgId(_ids.front())) {
+//		return _migratedSkippedBefore;
+//	} else if (_historySkippedBefore && _migratedCount) {
+//		return *_historySkippedBefore + *_migratedCount;
+//	}
+//	return base::none;
+//}
+//
+//base::optional<int> SharedMediaViewerMerged::Data::skippedAfter() const {
+//	if (_ids.empty()) {
+//		return base::none;
+//	} else if (IsServerMsgId(_ids.back())) {
+//		return _historySkippedAfter;
+//	} else if (_migratedSkippedAfter && _historyCount) {
+//		return *_migratedSkippedAfter + *_historyCount;
+//	}
+//	return base::none;
+//}
+//
+//SharedMediaViewerMerged::SharedMediaViewerMerged(
+//	Type type,
+//	not_null<History*> history,
+//	MsgId aroundId,
+//	int limitBefore,
+//	int limitAfter)
+//: _type(type)
+//, _history(GetActualHistory(history))
+//, _migrated(GetMigratedHistory(history, _history))
+//, _universalAroundId((_history == _migrated) ? -aroundId : aroundId)
+//, _limitBefore(limitBefore)
+//, _limitAfter(limitAfter)
+//, _data(_history, _migrated) {
+//}
+//
+//bool SharedMediaViewerMerged::hasOverview() const {
+//	return SharedMediaTypeToOverview(_type) != OverviewCount;
+//}
+//
+//void SharedMediaViewerMerged::showOverview() const {
+//	if (hasOverview()) {
+//		Ui::showPeerOverview(_history, SharedMediaTypeToOverview(_type));
+//	}
+//}
+//
+//bool SharedMediaViewerMerged::moveTo(const SharedMediaViewerMerged &other) {
+//	if (_history != other._history || _type != other._type) {
+//		return false;
+//	}
+//	_universalAroundId = other._universalAroundId;
+//	if (!containsAroundId()) {
+//		clearAfterMove();
+//	}
+//	load();
+//	return true;
+//}
+//
+//bool SharedMediaViewerMerged::containsAroundId() const {
+//	if (_data._ids.empty()) {
+//		return false;
+//	}
+//	auto min = _data._ids.front();
+//	auto max = _data._ids.back();
+//	if (IsServerMsgId(_universalAroundId)) {
+//		return (!IsServerMsgId(min) || min <= aroundId())
+//			&& (IsServerMsgId(max) && max >= aroundId());
+//	}
+//	return (!IsServerMsgId(min) && -min <= aroundId())
+//		&& (IsServerMsgId(max) || -max >= aroundId());
+//}
+//
+//bool SharedMediaViewerMerged::amAroundMigrated() const {
+//	return !IsServerMsgId(_universalAroundId);
+//}
+//
+//not_null<History*> SharedMediaViewerMerged::aroundHistory() const {
+//	return amAroundMigrated() ? _migrated : _history;
+//}
+//
+//MsgId SharedMediaViewerMerged::aroundId() const {
+//	return amAroundMigrated() ? -_universalAroundId : _universalAroundId;
+//}
+//
+//void SharedMediaViewerMerged::clearAfterMove() {
+//	_data = Data(_history, _migrated, _data._historyCount, _data._migratedCount);
+//}
+//
+//void SharedMediaViewerMerged::load() {
+//	auto weak = base::make_weak_unique(this);
+//	auto peer = aroundHistory()->peer;
+//	Auth().storage().query(Storage::SharedMediaQuery(
+//			peer->id,
+//			_type,
+//			aroundId(),
+//			_limitBefore,
+//			_limitAfter), [weak, peer](Storage::SharedMediaResult &&result) {
+//		if (weak) {
+//			weak->applyStoredResult(peer, std::move(result));
+//		}
+//	});
+//}
+//
+//void SharedMediaViewerMerged::applyStoredResult(
+//		not_null<PeerData*> peer,
+//		Storage::SharedMediaResult &&result) {
+//	if (aroundHistory()->peer != peer) {
+//		return;
+//	}
+//	auto aroundMigrated = amAroundMigrated();
+//	if (result.count) {
+//		(aroundMigrated ? _data._migratedCount : _data._historyCount) = result.count;
+//	}
+//}
diff --git a/Telegram/SourceFiles/history/history_shared_media.h b/Telegram/SourceFiles/history/history_shared_media.h
new file mode 100644
index 000000000..bc94d74ca
--- /dev/null
+++ b/Telegram/SourceFiles/history/history_shared_media.h
@@ -0,0 +1,316 @@
+/*
+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 "storage/storage_shared_media.h"
+#include "mtproto/sender.h"
+#include "base/weak_unique_ptr.h"
+
+base::optional<Storage::SharedMediaType> SharedMediaOverviewType(
+	Storage::SharedMediaType type);
+void SharedMediaShowOverview(
+	Storage::SharedMediaType type,
+	not_null<History*> history);
+
+class SharedMediaViewer;
+class SharedMediaSlice {
+public:
+	using Key = Storage::SharedMediaKey;
+
+	SharedMediaSlice(
+		Key key,
+		base::optional<int> fullCount = base::none)
+		: _key(key)
+		, _fullCount(fullCount) {
+	}
+
+	base::optional<int> fullCount() const {
+		return _fullCount;
+	}
+	base::optional<int> skippedBefore() const {
+		return _skippedBefore;
+	}
+	base::optional<int> skippedAfter() const {
+		return _skippedAfter;
+	}
+	base::optional<int> indexOf(MsgId msgId) const {
+		auto it = _ids.find(msgId);
+		if (it != _ids.end()) {
+			return (it - _ids.begin());
+		}
+		return base::none;
+	}
+	int size() const {
+		return _ids.size();
+	}
+
+	using iterator = base::flat_set<MsgId>::const_iterator;
+
+	iterator begin() const {
+		return _ids.begin();
+	}
+	iterator end() const {
+		return _ids.end();
+	}
+	iterator cbegin() const {
+		return begin();
+	}
+	iterator cend() const {
+		return end();
+	}
+
+	base::optional<int> distance(const Key &a, const Key &b) const {
+		if (a.type != _key.type
+			|| b.type != _key.type
+			|| a.peerId != _key.peerId
+			|| b.peerId != _key.peerId) {
+			return base::none;
+		}
+		auto i = _ids.find(a.messageId);
+		auto j = _ids.find(b.messageId);
+		if (i == _ids.end() || j == _ids.end()) {
+			return base::none;
+		}
+		return j - i;
+	}
+
+private:
+	Key _key;
+	base::flat_set<MsgId> _ids;
+	MsgRange _range;
+	base::optional<int> _fullCount;
+	base::optional<int> _skippedBefore;
+	base::optional<int> _skippedAfter;
+
+	friend class SharedMediaViewer;
+
+};
+
+class SharedMediaViewer :
+	private base::Subscriber,
+	public base::enable_weak_from_this {
+public:
+	using Type = Storage::SharedMediaType;
+	using Key = Storage::SharedMediaKey;
+
+	SharedMediaViewer(
+		Key key,
+		int limitBefore,
+		int limitAfter);
+
+	void start();
+
+	base::Observable<SharedMediaSlice> updated;
+
+private:
+	using InitialResult = Storage::SharedMediaResult;
+	using SliceUpdate = Storage::SharedMediaSliceUpdate;
+	using OneRemoved = Storage::SharedMediaRemoveOne;
+	using AllRemoved = Storage::SharedMediaRemoveAll;
+
+	void loadInitial();
+	enum class RequestDirection {
+		Before,
+		After,
+	};
+	void requestMessages(RequestDirection direction);
+	void applyStoredResult(InitialResult &&result);
+	void applyUpdate(const SliceUpdate &update);
+	void applyUpdate(const OneRemoved &update);
+	void applyUpdate(const AllRemoved &update);
+	void sliceToLimits();
+
+	void mergeSliceData(
+		base::optional<int> count,
+		const base::flat_set<MsgId> &messageIds,
+		base::optional<int> skippedBefore = base::none,
+		base::optional<int> skippedAfter = base::none);
+
+
+	Key _key;
+	int _limitBefore = 0;
+	int _limitAfter = 0;
+	mtpRequestId _beforeRequestId = 0;
+	mtpRequestId _afterRequestId = 0;
+	SharedMediaSlice _data;
+
+};
+//
+//class SharedMediaSliceMerged :
+//	private MTP::Sender,
+//	private base::Subscriber,
+//	public base::enable_weak_from_this {
+//public:
+//	class Data;
+//
+//private:
+//	friend class Data;
+//	using UniversalMsgId = MsgId;
+//
+//public:
+//	using Type = Storage::SharedMediaType;
+//
+//	SharedMediaSliceMerged(
+//		Type type,
+//		not_null<History*> history,
+//		MsgId aroundId,
+//		int limitBefore,
+//		int limitAfter);
+//
+//	bool hasOverview() const;
+//	void showOverview() const;
+//	bool moveTo(const SharedMediaSliceMerged &other);
+//
+//	void load();
+//
+//	class Data {
+//	public:
+//		base::optional<int> fullCount() const;
+//		base::optional<int> skippedBefore() const;
+//		base::optional<int> skippedAfter() const;
+//		int size() const {
+//			return _ids.size();
+//		}
+//
+//		class iterator {
+//		public:
+//			FullMsgId operator*() const {
+//				auto id = _data->_ids[_index];
+//				Assert(IsServerMsgId(id)
+//					|| (_data->_migrated != nullptr && IsServerMsgId(-id)));
+//				return IsServerMsgId(id)
+//					? FullMsgId(_data->_history->channelId(), id)
+//					: FullMsgId(_data->_migrated->channelId(), -id);
+//			}
+//			iterator &operator--() {
+//				--_index;
+//				return *this;
+//			}
+//			iterator operator--(int) {
+//				auto result = *this;
+//				--*this;
+//				return result;
+//			}
+//			iterator &operator++() {
+//				++_index;
+//				return *this;
+//			}
+//			iterator operator++(int) {
+//				auto result = *this;
+//				++*this;
+//				return result;
+//			}
+//			iterator &operator+=(int offset) {
+//				_index += offset;
+//				return *this;
+//			}
+//			iterator operator+(int offset) const {
+//				auto result = *this;
+//				return result += offset;
+//			}
+//			bool operator==(iterator other) const {
+//				return (_data == other._data) && (_index == other._index);
+//			}
+//			bool operator!=(iterator other) const {
+//				return !(*this == other);
+//			}
+//			bool operator<(iterator other) const {
+//				return (_data < other._data)
+//					|| (_data == other._data && _index < other._index);
+//			}
+//
+//		private:
+//			friend class Data;
+//
+//			iterator(not_null<const Data*> data, int index)
+//				: _data(data)
+//				, _index(index) {
+//			}
+//
+//			not_null<const Data*> _data;
+//			int _index = 0;
+//
+//		};
+//
+//		iterator begin() const {
+//			return iterator(this, 0);
+//		}
+//		iterator end() const {
+//			iterator(this, _ids.size());
+//		}
+//		iterator cbegin() const {
+//			return begin();
+//		}
+//		iterator cend() const {
+//			return end();
+//		}
+//
+//	private:
+//		friend class iterator;
+//		friend class SharedMediaSliceMerged;
+//
+//		Data(
+//			not_null<History*> history,
+//			History *migrated,
+//			base::optional<int> historyCount = base::none,
+//			base::optional<int> migratedCount = base::none)
+//			: _history(history)
+//			, _migrated(migrated)
+//			, _historyCount(historyCount)
+//			, _migratedCount(migratedCount) {
+//			if (!_migrated) {
+//				_migratedCount = 0;
+//			}
+//		}
+//
+//		not_null<History*> _history;
+//		History *_migrated = nullptr;
+//		std::vector<UniversalMsgId> _ids;
+//		base::optional<int> _historyCount;
+//		base::optional<int> _historySkippedBefore;
+//		base::optional<int> _historySkippedAfter;
+//		base::optional<int> _migratedCount;
+//		base::optional<int> _migratedSkippedBefore;
+//		base::optional<int> _migratedSkippedAfter;
+//
+//	};
+//	base::Observable<Data> updated;
+//
+//private:
+//	bool amAroundMigrated() const;
+//	not_null<History*> aroundHistory() const;
+//	MsgId aroundId() const;
+//
+//	void applyStoredResult(
+//		not_null<PeerData*> peer,
+//		Storage::SharedMediaResult &&result);
+//	bool containsAroundId() const;
+//	void clearAfterMove();
+//
+//	Type _type = Type::kCount;
+//	not_null<History*> _history;
+//	History *_migrated = nullptr;
+//	UniversalMsgId _universalAroundId = 0;
+//	int _limitBefore = 0;
+//	int _limitAfter = 0;
+//	Data _data;
+//
+//};
diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp
index f530946ff..97457cfdd 100644
--- a/Telegram/SourceFiles/mediaview.cpp
+++ b/Telegram/SourceFiles/mediaview.cpp
@@ -44,6 +44,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 
 namespace {
 
+constexpr auto kPreloadCount = 4;
+
 TextParseOptions _captionTextOptions = {
 	TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText, // flags
 	0, // maxw
@@ -57,21 +59,24 @@ TextParseOptions _captionBotOptions = {
 	Qt::LayoutDirectionAuto, // dir
 };
 
-bool typeHasMediaOverview(MediaOverviewType type) {
-	switch (type) {
-	case OverviewPhotos:
-	case OverviewVideos:
-	case OverviewMusicFiles:
-	case OverviewFiles:
-	case OverviewVoiceFiles:
-	case OverviewLinks: return true;
-	default: break;
-	}
-	return false;
-}
+// Preload X message ids before and after current.
+constexpr auto kIdsLimit = 32;
+
+// Preload next messages if we went further from current than that.
+constexpr auto kIdsPreloadAfter = 28;
 
 } // namespace
 
+struct MediaView::SharedMedia {
+	SharedMedia(SharedMediaViewer::Key key)
+		: key(key)
+		, slice(key, kIdsLimit, kIdsLimit) {
+	}
+
+	SharedMediaViewer::Key key;
+	SharedMediaViewer slice;
+};
+
 MediaView::MediaView() : TWidget(nullptr)
 , _transparentBrush(style::transparentPlaceholderBrush())
 , _animStarted(getms())
@@ -111,9 +116,11 @@ MediaView::MediaView() : TWidget(nullptr)
 					documentUpdated(document);
 				}
 			});
-			subscribe(Auth().messageIdChanging, [this](std::pair<HistoryItem*, MsgId> update) {
+			subscribe(Auth().messageIdChanging, [this](std::pair<not_null<HistoryItem*>, MsgId> update) {
 				changingMsgId(update.first, update.second);
 			});
+		} else {
+			_sharedMedia = nullptr;
 		}
 	};
 	subscribe(Messenger::Instance().authSessionChanged(), [handleAuthSessionChange] {
@@ -121,11 +128,6 @@ MediaView::MediaView() : TWidget(nullptr)
 	});
 	handleAuthSessionChange();
 
-	auto observeEvents = Notify::PeerUpdate::Flag::SharedMediaChanged;
-	subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(observeEvents, [this](const Notify::PeerUpdate &update) {
-		mediaOverviewUpdated(update);
-	}));
-
 	setWindowFlags(Qt::FramelessWindowHint | Qt::BypassWindowManagerHint | Qt::Tool | Qt::NoDropShadowWindowHint);
 	moveToScreen();
 	setAttribute(Qt::WA_NoSystemBackground, true);
@@ -190,54 +192,41 @@ void MediaView::moveToScreen() {
 	_saveMsg.moveTo((width() - _saveMsg.width()) / 2, (height() - _saveMsg.height()) / 2);
 }
 
-void MediaView::mediaOverviewUpdated(const Notify::PeerUpdate &update) {
-	if (isHidden() || (!_photo && !_doc)) return;
-	if (_photo && _overview == OverviewChatPhotos && _history && !_history->peer->isUser()) {
-		auto lastChatPhoto = computeLastOverviewChatPhoto();
-		if (_index < 0 && _photo == lastChatPhoto.photo && _photo == _additionalChatPhoto) {
-			auto firstOpened = _firstOpenedPeerPhoto;
-			showPhoto(_photo, lastChatPhoto.item);
-			_firstOpenedPeerPhoto = firstOpened;
-			return;
-		}
-		computeAdditionalChatPhoto(_history->peer, lastChatPhoto.photo);
+void MediaView::handleSharedMediaUpdate(const SharedMediaSlice &update) {
+	if (isHidden() || (!_photo && !_doc) || !_sharedMedia) {
+		_index = _fullIndex = _fullCount = base::none;
+		return;
 	}
+	//if (_photo && _overview == OverviewChatPhotos && _history && !_history->peer->isUser()) { // TODO chat
+	//	auto lastChatPhoto = computeLastOverviewChatPhoto();
+	//	if (_index < 0 && _photo == lastChatPhoto.photo && _photo == _additionalChatPhoto) {
+	//		auto firstOpened = _firstOpenedPeerPhoto;
+	//		showPhoto(_photo, lastChatPhoto.item);
+	//		_firstOpenedPeerPhoto = firstOpened;
+	//		return;
+	//	}
+	//	computeAdditionalChatPhoto(_history->peer, lastChatPhoto.photo);
+	//}
 
-	if (_history && (_history->peer == update.peer || (_migrated && _migrated->peer == update.peer)) && (update.mediaTypesMask & (1 << _overview)) && _msgid) {
-		_index = -1;
-		auto i = 0;
-		if (_msgmigrated) {
-			for_const (auto msgId, _migrated->overview(_overview)) {
-				if (msgId == _msgid) {
-					_index = i;
-					break;
-				}
-				++i;
-			}
-		} else {
-			for_const (auto msgId, _history->overview(_overview)) {
-				if (msgId == _msgid) {
-					_index = i;
-					break;
-				}
-				++i;
-			}
-		}
-		updateControls();
-		preloadData(0);
-	} else if (_user == update.peer && update.mediaTypesMask & (1 << OverviewCount)) {
-		if (!_photo) return;
+	_sharedMediaData = update;
 
-		_index = -1;
-		for (int i = 0, l = _user->photos.size(); i < l; ++i) {
-			if (_user->photos[i] == _photo) {
-				_index = i;
-				break;
-			}
-		}
-		updateControls();
-		preloadData(0);
-	}
+	findCurrent();
+	updateControls();
+	preloadData(0);
+
+	//if (_user == update.peer && update.mediaTypesMask & (1 << OverviewCount)) {
+	//	if (!_photo) return;
+
+	//	_index = -1;
+	//	for (int i = 0, l = _user->photos.size(); i < l; ++i) {
+	//		if (_user->photos[i] == _photo) {
+	//			_index = i;
+	//			break;
+	//		}
+	//	}
+	//	updateControls();
+	//	preloadData(0);
+	//} // TODO user
 }
 
 bool MediaView::fileShown() const {
@@ -285,17 +274,10 @@ void MediaView::documentUpdated(DocumentData *doc) {
 	}
 }
 
-void MediaView::changingMsgId(HistoryItem *row, MsgId newId) {
+void MediaView::changingMsgId(not_null<HistoryItem*> row, MsgId newId) {
 	if (row->id == _msgid) {
 		_msgid = newId;
-	}
-
-	// Send a fake update.
-	if (!isHidden()) {
-		Notify::PeerUpdate update(row->history()->peer);
-		update.flags |= Notify::PeerUpdate::Flag::SharedMediaChanged;
-		update.mediaTypesMask |= (1 << _overview);
-		mediaOverviewUpdated(update);
+		validateSharedMedia();
 	}
 }
 
@@ -332,6 +314,17 @@ void MediaView::updateDocSize() {
 	}
 }
 
+void MediaView::refreshNavVisibility() {
+	if (_sharedMediaData) {
+		_leftNavVisible = _index && (*_index > 0);
+		_rightNavVisible = _index && (*_index + 1 < _sharedMediaData->size());
+	} else {
+		_leftNavVisible = false;
+		_rightNavVisible = false;
+	}
+	// TODO user
+}
+
 void MediaView::updateControls() {
 	if (_doc && fileBubbleShown()) {
 		if (_doc->loading()) {
@@ -393,28 +386,7 @@ void MediaView::updateControls() {
 		_dateNav = myrtlrect(st::mediaviewTextLeft, height() - st::mediaviewTextTop, st::mediaviewFont->width(_dateText), st::mediaviewFont->height);
 	}
 	updateHeader();
-	if (_photo || (_history && _overview != OverviewCount)) {
-		_leftNavVisible = (_index > 0) || (_index == 0 && (
-			(!_msgmigrated && _history && _history->overview(_overview).size() < _history->overviewCount(_overview)) ||
-			(_msgmigrated && _migrated && _migrated->overview(_overview).size() < _migrated->overviewCount(_overview)) ||
-			(!_msgmigrated && _history && _migrated && (!_migrated->overview(_overview).isEmpty() || _migrated->overviewCount(_overview) > 0)))) ||
-			(_index < 0 && _photo == _additionalChatPhoto &&
-				((_history && _history->overviewCount(_overview) > 0) ||
-				(_migrated && _history->overviewLoaded(_overview) && _migrated->overviewCount(_overview) > 0))
-			);
-		_rightNavVisible = (_index >= 0) && (
-			(!_msgmigrated && _history && _index + 1 < _history->overview(_overview).size()) ||
-			(_msgmigrated && _migrated && _index + 1 < _migrated->overview(_overview).size()) ||
-			(_msgmigrated && _migrated && _history && (!_history->overview(_overview).isEmpty() || _history->overviewCount(_overview) > 0)) ||
-			(!_msgmigrated && _history && _index + 1 == _history->overview(_overview).size() && _additionalChatPhoto) ||
-			(_msgmigrated && _migrated && _index + 1 == _migrated->overview(_overview).size() && _history->overviewCount(_overview) == 0 && _additionalChatPhoto) ||
-			(!_history && _user && (_index + 1 < _user->photos.size() || _index + 1 < _user->photosCount)));
-		if (_msgmigrated && !_history->overviewLoaded(_overview)) {
-			_leftNavVisible = _rightNavVisible = false;
-		}
-	} else {
-		_leftNavVisible = _rightNavVisible = false;
-	}
+	refreshNavVisibility();
 
 	if (!_caption.isEmpty()) {
 		int32 skipw = qMax(_dateNav.left() + _dateNav.width(), _headerNav.left() + _headerNav.width());
@@ -453,7 +425,10 @@ void MediaView::updateActions() {
 		_actions.push_back({ lang(lng_mediaview_delete), SLOT(onDelete()) });
 	}
 	_actions.push_back({ lang(lng_mediaview_save_as), SLOT(onSaveAs()) });
-	if (_history && typeHasMediaOverview(_overview)) {
+
+	if (auto overviewType =
+		sharedMediaType()
+		| SharedMediaOverviewType) {
 		_actions.push_back({ lang(_doc ? lng_mediaview_files_all : lng_mediaview_photos_all), SLOT(onOverview()) });
 	}
 }
@@ -687,6 +662,7 @@ void MediaView::updateMixerVideoVolume() const {
 }
 
 void MediaView::close() {
+	_sharedMedia = nullptr;
 	if (_menu) _menu->hideMenu(true);
 	Messenger::Instance().hideMediaView();
 }
@@ -1002,12 +978,13 @@ void MediaView::onDelete() {
 
 void MediaView::onOverview() {
 	if (_menu) _menu->hideMenu(true);
-	if (!_history || !typeHasMediaOverview(_overview)) {
-		update();
-		return;
+	update();
+	if (auto overviewType =
+		sharedMediaType()
+		| SharedMediaOverviewType) {
+		close();
+		SharedMediaShowOverview(*overviewType, _history);
 	}
-	close();
-	if (_history->peer) App::main()->showMediaOverview(_history->peer, _overview);
 }
 
 void MediaView::onCopy() {
@@ -1025,7 +1002,81 @@ void MediaView::onCopy() {
 	}
 }
 
-void MediaView::showPhoto(PhotoData *photo, HistoryItem *context) {
+base::optional<MediaView::SharedMediaType> MediaView::sharedMediaType() const {
+	using Type = SharedMediaType;
+	auto channelId = _msgmigrated ? NoChannel : _channel;
+	if (auto item = App::histItemById(channelId, _msgid)) {
+		if (_photo) {
+			if (item->toHistoryMessage()) {
+				return Type::Photo;
+			}
+			return Type::ChatPhoto;
+		} else if (_doc) {
+			if (_doc->isGifv()) {
+				return Type::GIF;
+			} else if (_doc->isVideo()) {
+				return Type::Video;
+			}
+			return Type::File;
+		}
+	}
+	return base::none;
+}
+
+base::optional<MediaView::SharedMediaKey> MediaView::sharedMediaKey() const {
+	auto keyForType = [this](SharedMediaType type) -> SharedMediaKey {
+		return { (_msgmigrated ? _migrated : _history)->peer->id, type, _msgid };
+	};
+	return
+		sharedMediaType()
+		| keyForType;
+}
+
+bool MediaView::validSharedMedia() const {
+	if (auto key = sharedMediaKey()) {
+		if (!_sharedMedia) {
+			return false;
+		}
+		auto countDistanceInData = [](const auto &a, const auto &b) {
+			return [&](const SharedMediaSlice &data) {
+				return data.distance(a, b);
+			};
+		};
+
+		auto distance = (key == _sharedMedia->key) ? 0 :
+			_sharedMediaData
+			| countDistanceInData(*key, _sharedMedia->key)
+			| base::abs;
+		if (distance) {
+			return (*distance < kIdsPreloadAfter);
+		}
+	}
+	return (_sharedMedia == nullptr);
+}
+
+void MediaView::validateSharedMedia() {
+	if (auto key = sharedMediaKey()) {
+		_sharedMedia = std::make_unique<SharedMedia>(*key);
+		subscribe(_sharedMedia->slice.updated, [this](const SharedMediaSlice &data) {
+			handleSharedMediaUpdate(data);
+		});
+		_sharedMedia->slice.start();
+	} else {
+		_sharedMedia = nullptr;
+		_sharedMediaData = base::none;
+	}
+}
+
+void MediaView::refreshSharedMedia() {
+	if (!validSharedMedia()) {
+		validateSharedMedia();
+	}
+	findCurrent();
+	updateControls();
+	preloadData(0);
+}
+
+void MediaView::showPhoto(not_null<PhotoData*> photo, HistoryItem *context) {
 	_history = context ? context->history().get() : nullptr;
 	_migrated = nullptr;
 	if (_history) {
@@ -1052,23 +1103,20 @@ void MediaView::showPhoto(PhotoData *photo, HistoryItem *context) {
 	}
 	if (!_animOpacities.isEmpty()) _animOpacities.clear();
 
-	_index = -1;
 	_msgid = context ? context->id : 0;
 	_msgmigrated = context ? (context->history() == _migrated) : false;
 	_channel = _history ? _history->channelId() : NoChannel;
 	_canForward = context ? context->canForward() : false;
 	_canDelete = context ? context->canDelete() : false;
 	_photo = photo;
+
+	validateSharedMedia();
 	if (_history) {
 		if (context && !context->toHistoryMessage()) {
-			_overview = OverviewChatPhotos;
 			if (!_history->peer->isUser()) {
 				computeAdditionalChatPhoto(_history->peer, computeLastOverviewChatPhoto().photo);
 			}
-		} else {
-			_overview = OverviewPhotos;
 		}
-		findCurrent();
 	}
 
 	displayPhoto(photo, context);
@@ -1076,7 +1124,7 @@ void MediaView::showPhoto(PhotoData *photo, HistoryItem *context) {
 	activateControls();
 }
 
-void MediaView::showPhoto(PhotoData *photo, PeerData *context) {
+void MediaView::showPhoto(not_null<PhotoData*> photo, PeerData *context) {
 	_history = _migrated = nullptr;
 	_additionalChatPhoto = nullptr;
 	_firstOpenedPeerPhoto = true;
@@ -1096,23 +1144,23 @@ void MediaView::showPhoto(PhotoData *photo, PeerData *context) {
 	_msgmigrated = false;
 	_channel = NoChannel;
 	_canForward = _canDelete = false;
-	_index = -1;
 	_photo = photo;
-	_overview = OverviewCount;
-	if (_user) {
-		if (_user->photos.isEmpty() && _user->photosCount < 0 && _user->photoId && _user->photoId != UnknownPeerPhotoId) {
-			_index = 0;
-		}
-		for (int i = 0, l = _user->photos.size(); i < l; ++i) {
-			if (_user->photos.at(i) == photo) {
-				_index = i;
-				break;
-			}
-		}
 
-		if (_user->photosCount < 0) {
-			loadBack();
-		}
+	validateSharedMedia();
+	if (_user) {
+		//if (_user->photos.isEmpty() && _user->photosCount < 0 && _user->photoId && _user->photoId != UnknownPeerPhotoId) {
+		//	_fullIndex = 0;
+		//}
+		//for (int i = 0, l = _user->photos.size(); i < l; ++i) {
+		//	if (_user->photos.at(i) == photo) {
+		//		_fullIndex = i;
+		//		break;
+		//	}
+		//}
+
+		//if (_user->photosCount < 0) {
+		//	loadBack();
+		//} // TODO user
 	} else if ((_history = App::historyLoaded(_peer))) {
 		if (_history->peer->migrateFrom()) {
 			_migrated = App::history(_history->peer->migrateFrom()->id);
@@ -1129,20 +1177,20 @@ void MediaView::showPhoto(PhotoData *photo, PeerData *context) {
 		}
 
 		computeAdditionalChatPhoto(_history->peer, lastChatPhoto.photo);
-		if (_additionalChatPhoto == _photo) {
-			_overview = OverviewChatPhotos;
-			findCurrent();
-		} else {
+		//if (_additionalChatPhoto == _photo) { // TODO chat
+		//	_overview = OverviewChatPhotos;
+		//	findCurrent();
+		//} else {
 			_additionalChatPhoto = nullptr;
 			_history = _migrated = nullptr;
-		}
+		//}
 	}
 	displayPhoto(photo, 0);
 	preloadData(0);
 	activateControls();
 }
 
-void MediaView::showDocument(DocumentData *doc, HistoryItem *context) {
+void MediaView::showDocument(not_null<DocumentData*> document, HistoryItem *context) {
 	_photo = 0;
 	_history = context ? context->history().get() : nullptr;
 	_migrated = nullptr;
@@ -1169,25 +1217,21 @@ void MediaView::showDocument(DocumentData *doc, HistoryItem *context) {
 	}
 	if (!_animOpacities.isEmpty()) _animOpacities.clear();
 
-	_index = -1;
 	_msgid = context ? context->id : 0;
 	_msgmigrated = context ? (context->history() == _migrated) : false;
 	_channel = _history ? _history->channelId() : NoChannel;
 	_canForward = context ? context->canForward() : false;
 	_canDelete = context ? context->canDelete() : false;
-	if (_history) {
-		_overview = doc->isGifv() ? OverviewGIFs : doc->isVideo() ? OverviewVideos : OverviewFiles;
-		findCurrent();
+
+	if (document->isVideo() || document->isRoundVideo()) {
+		_autoplayVideoDocument = document;
 	}
-	if (doc->isVideo() || doc->isRoundVideo()) {
-		_autoplayVideoDocument = doc;
-	}
-	displayDocument(doc, context);
+	displayDocument(document, context);
 	preloadData(0);
 	activateControls();
 }
 
-void MediaView::displayPhoto(PhotoData *photo, HistoryItem *item) {
+void MediaView::displayPhoto(not_null<PhotoData*> photo, HistoryItem *item) {
 	stopGif();
 	destroyThemePreview();
 	_doc = nullptr;
@@ -1195,6 +1239,8 @@ void MediaView::displayPhoto(PhotoData *photo, HistoryItem *item) {
 	_photo = photo;
 	_radial.stop();
 
+	validateSharedMedia();
+
 	_photoRadialRect = QRect(QPoint((width() - st::radialSize.width()) / 2, (height() - st::radialSize.height()) / 2), st::radialSize);
 
 	_zoom = 0;
@@ -1268,6 +1314,8 @@ void MediaView::displayDocument(DocumentData *doc, HistoryItem *item) { // empty
 	_photo = nullptr;
 	_radial.stop();
 
+	validateSharedMedia();
+
 	if (_autoplayVideoDocument && _doc != _autoplayVideoDocument) {
 		_autoplayVideoDocument = nullptr;
 	}
@@ -2160,180 +2208,185 @@ void MediaView::setZoomLevel(int newZoom) {
 }
 
 bool MediaView::moveToNext(int32 delta) {
-	if (_index < 0) {
-		if (delta == -1 && _photo == _additionalChatPhoto) {
-			auto lastChatPhoto = computeLastOverviewChatPhoto();
-			if (lastChatPhoto.item) {
-				if (lastChatPhoto.item->history() == _history) {
-					_index = _history->overview(_overview).size() - 1;
-					_msgmigrated = false;
-				} else {
-					_index = _migrated->overview(_overview).size() - 1;
-					_msgmigrated = true;
-				}
-				_msgid = lastChatPhoto.item->id;
-				_channel = _history ? _history->channelId() : NoChannel;
-				_canForward = lastChatPhoto.item->canForward();
-				_canDelete = lastChatPhoto.item->canDelete();
-				displayPhoto(lastChatPhoto.photo, lastChatPhoto.item);
-				preloadData(delta);
-				return true;
-			} else if (_history && (_history->overviewCount(OverviewChatPhotos) != 0 || (
-				_migrated && _migrated->overviewCount(OverviewChatPhotos) != 0))) {
-				loadBack();
-				return true;
-			}
-		}
+	if (!_index) {
 		return false;
 	}
-	if (_overview == OverviewCount && (_history || !_user)) {
+	auto newIndex = *_index + delta;
+	if (newIndex < 0 || newIndex >= _sharedMediaData->size()) {
 		return false;
 	}
-	if (_msgmigrated && !_history->overviewLoaded(_overview)) {
-		return true;
-	}
-
-	int32 newIndex = _index + delta;
-	if (_history && _overview != OverviewCount) {
-		bool newMigrated = _msgmigrated;
-		if (!newMigrated && newIndex < 0 && _migrated) {
-			newIndex += _migrated->overview(_overview).size();
-			newMigrated = true;
-		} else if (newMigrated && newIndex >= _migrated->overview(_overview).size()) {
-			newIndex -= _migrated->overview(_overview).size() + (_history->overviewCount(_overview) - _history->overview(_overview).size());
-			newMigrated = false;
-		}
-		if (newIndex >= 0 && newIndex < (newMigrated ? _migrated : _history)->overview(_overview).size()) {
-			if (auto item = App::histItemById(newMigrated ? 0 : _channel, getMsgIdFromOverview(newMigrated ? _migrated : _history, newIndex))) {
-				_index = newIndex;
-				_msgid = item->id;
-				_msgmigrated = (item->history() == _migrated);
-				_channel = _history ? _history->channelId() : NoChannel;
-				_canForward = item->canForward();
-				_canDelete = item->canDelete();
-				stopGif();
-				if (auto media = item->getMedia()) {
-					switch (media->type()) {
-					case MediaTypePhoto: displayPhoto(static_cast<HistoryPhoto*>(item->getMedia())->photo(), item); preloadData(delta); break;
-					case MediaTypeFile:
-					case MediaTypeVideo:
-					case MediaTypeGif:
-					case MediaTypeSticker: displayDocument(media->getDocument(), item); preloadData(delta); break;
-					}
-				} else {
-					displayDocument(nullptr, item);
-					preloadData(delta);
-				}
+	if (auto item = App::histItemById(_history->channelId(), *(_sharedMediaData->begin() + newIndex))) {
+		_index = newIndex;
+		_msgid = item->id;
+		_msgmigrated = (item->history() == _migrated);
+		_channel = _history ? _history->channelId() : NoChannel;
+		_canForward = item->canForward();
+		_canDelete = item->canDelete();
+		stopGif();
+		if (auto media = item->getMedia()) {
+			switch (media->type()) {
+			case MediaTypePhoto: displayPhoto(static_cast<HistoryPhoto*>(item->getMedia())->photo(), item); preloadData(delta); break;
+			case MediaTypeFile:
+			case MediaTypeVideo:
+			case MediaTypeGif:
+			case MediaTypeSticker: displayDocument(media->getDocument(), item); preloadData(delta); break;
 			}
-		} else if (!newMigrated && newIndex == _history->overview(_overview).size() && _additionalChatPhoto) {
-			_index = -1;
-			_msgid = 0;
-			_msgmigrated = false;
-			_canForward = false;
-			_canDelete = false;
-			displayPhoto(_additionalChatPhoto, 0);
-		}
-		if (delta < 0 && _index < MediaOverviewStartPerPage) {
-			loadBack();
-		}
-	} else if (_user) {
-		if (newIndex >= 0 && newIndex < _user->photos.size()) {
-			_index = newIndex;
-			displayPhoto(_user->photos[_index], 0);
+		} else {
+			displayDocument(nullptr, item);
 			preloadData(delta);
 		}
-		if (delta > 0 && _index > _user->photos.size() - MediaOverviewStartPerPage) {
-			loadBack();
-		}
+		return true;
 	}
+	return false;
+
+	//if (_index < 0) { // TODO chat
+	//	if (delta == -1 && _photo == _additionalChatPhoto) {
+	//		auto lastChatPhoto = computeLastOverviewChatPhoto();
+	//		if (lastChatPhoto.item) {
+	//			if (lastChatPhoto.item->history() == _history) {
+	//				_index = _history->overview(_overview).size() - 1;
+	//				_msgmigrated = false;
+	//			} else {
+	//				_index = _migrated->overview(_overview).size() - 1;
+	//				_msgmigrated = true;
+	//			}
+	//			_msgid = lastChatPhoto.item->id;
+	//			_channel = _history ? _history->channelId() : NoChannel;
+	//			_canForward = lastChatPhoto.item->canForward();
+	//			_canDelete = lastChatPhoto.item->canDelete();
+	//			displayPhoto(lastChatPhoto.photo, lastChatPhoto.item);
+	//			preloadData(delta);
+	//			return true;
+	//		} else if (_history && (_history->overviewCount(OverviewChatPhotos) != 0 || (
+	//			_migrated && _migrated->overviewCount(OverviewChatPhotos) != 0))) {
+	//			loadBack();
+	//			return true;
+	//		}
+	//	}
+	//	return false;
+	//}
+	//if (_overview == OverviewCount && (_history || !_user)) {
+	//	return false;
+	//}
+	//if (_msgmigrated && !_history->overviewLoaded(_overview)) {
+	//	return true;
+	//}
+
+	//int32 newIndex = _index + delta;
+	//if (_history && _overview != OverviewCount) {
+	//	bool newMigrated = _msgmigrated;
+	//	if (!newMigrated && newIndex < 0 && _migrated) {
+	//		newIndex += _migrated->overview(_overview).size();
+	//		newMigrated = true;
+	//	} else if (newMigrated && newIndex >= _migrated->overview(_overview).size()) {
+	//		newIndex -= _migrated->overview(_overview).size() + (_history->overviewCount(_overview) - _history->overview(_overview).size());
+	//		newMigrated = false;
+	//	}
+	//	if (newIndex >= 0 && newIndex < (newMigrated ? _migrated : _history)->overview(_overview).size()) {
+	//		if (auto item = App::histItemById(newMigrated ? 0 : _channel, getMsgIdFromOverview(newMigrated ? _migrated : _history, newIndex))) {
+	//			_index = newIndex;
+	//			_msgid = item->id;
+	//			_msgmigrated = (item->history() == _migrated);
+	//			_channel = _history ? _history->channelId() : NoChannel;
+	//			_canForward = item->canForward();
+	//			_canDelete = item->canDelete();
+	//			stopGif();
+	//			if (auto media = item->getMedia()) {
+	//				switch (media->type()) {
+	//				case MediaTypePhoto: displayPhoto(static_cast<HistoryPhoto*>(item->getMedia())->photo(), item); preloadData(delta); break;
+	//				case MediaTypeFile:
+	//				case MediaTypeVideo:
+	//				case MediaTypeGif:
+	//				case MediaTypeSticker: displayDocument(media->getDocument(), item); preloadData(delta); break;
+	//				}
+	//			} else {
+	//				displayDocument(nullptr, item);
+	//				preloadData(delta);
+	//			}
+	//		}
+	//	} else if (!newMigrated && newIndex == _history->overview(_overview).size() && _additionalChatPhoto) {
+	//		_index = -1;
+	//		_msgid = 0;
+	//		_msgmigrated = false;
+	//		_canForward = false;
+	//		_canDelete = false;
+	//		displayPhoto(_additionalChatPhoto, 0);
+	//	}
+	//	if (delta < 0 && _index < MediaOverviewStartPerPage) {
+	//		loadBack();
+	//	}
+	//} else if (_user) {
+	//	if (newIndex >= 0 && newIndex < _user->photos.size()) {
+	//		_index = newIndex;
+	//		displayPhoto(_user->photos[_index], 0);
+	//		preloadData(delta);
+	//	}
+	//	if (delta > 0 && _index > _user->photos.size() - MediaOverviewStartPerPage) {
+	//		loadBack();
+	//	}
+	//}
 	return true;
 }
 
 void MediaView::preloadData(int32 delta) {
-	int indexInOverview = _index;
-	bool indexOfMigratedItem = _msgmigrated;
-	if (_index < 0) {
-		if (_overview != OverviewChatPhotos || !_history) return;
-		indexInOverview = _history->overview(OverviewChatPhotos).size();
-		indexOfMigratedItem = false;
+	if (!_index) {
+		return;
 	}
-	if (!_user && _overview == OverviewCount) return;
+	auto from = *_index + (delta ? delta : -1);
+	auto till = *_index + (delta ? delta * kPreloadCount : 1);
+	if (from > till) std::swap(from, till);
 
-	auto from = indexInOverview + (delta ? delta : -1);
-	auto to = indexInOverview + (delta ? delta * MediaOverviewPreloadCount : 1);
-	if (from > to) qSwap(from, to);
-	if (_history && _overview != OverviewCount) {
-		auto forgetIndex = indexInOverview - delta * 2;
-		auto forgetHistory = indexOfMigratedItem ? _migrated : _history;
-		if (_migrated) {
-			if (indexOfMigratedItem && forgetIndex >= _migrated->overview(_overview).size()) {
-				forgetHistory = _history;
-				forgetIndex -= _migrated->overview(_overview).size() + (_history->overviewCount(_overview) - _history->overview(_overview).size());
-			} else if (!indexOfMigratedItem && forgetIndex < 0) {
-				forgetHistory = _migrated;
-				forgetIndex += _migrated->overview(_overview).size();
+	auto forgetIndex = *_index - delta * 2;
+	if (forgetIndex >= 0 && forgetIndex < _sharedMediaData->size()) {
+		if (auto item = App::histItemById(_history->channelId(), *(_sharedMediaData->begin() + forgetIndex))) {
+			if (auto media = item->getMedia()) {
+				switch (media->type()) {
+				case MediaTypePhoto: static_cast<HistoryPhoto*>(media)->photo()->forget(); break;
+				case MediaTypeFile:
+				case MediaTypeVideo:
+				case MediaTypeGif:
+				case MediaTypeSticker: media->getDocument()->forget(); break;
+				}
 			}
 		}
-		if (forgetIndex >= 0 && forgetIndex < forgetHistory->overview(_overview).size() && (forgetHistory != (indexOfMigratedItem ? _migrated : _history) || forgetIndex != indexInOverview)) {
-			if (auto item = App::histItemById(forgetHistory->channelId(), getMsgIdFromOverview(forgetHistory, forgetIndex))) {
+	}
+
+	for (auto index = from; index != till; ++index) {
+		if (index >= 0 && index < _sharedMediaData->size()) {
+			if (auto item = App::histItemById(_history->channelId(), *(_sharedMediaData->begin() + index))) {
 				if (auto media = item->getMedia()) {
 					switch (media->type()) {
-					case MediaTypePhoto: static_cast<HistoryPhoto*>(media)->photo()->forget(); break;
+					case MediaTypePhoto: static_cast<HistoryPhoto*>(media)->photo()->download(); break;
 					case MediaTypeFile:
 					case MediaTypeVideo:
-					case MediaTypeGif:
-					case MediaTypeSticker: media->getDocument()->forget(); break;
+					case MediaTypeGif: {
+						auto doc = media->getDocument();
+						doc->thumb->load();
+						doc->automaticLoad(item);
+					} break;
+					case MediaTypeSticker: media->getDocument()->sticker()->img->load(); break;
 					}
 				}
 			}
 		}
-
-		for (int32 i = from; i <= to; ++i) {
-			History *previewHistory = indexOfMigratedItem ? _migrated : _history;
-			int32 previewIndex = i;
-			if (_migrated) {
-				if (indexOfMigratedItem && previewIndex >= _migrated->overview(_overview).size()) {
-					previewHistory = _history;
-					previewIndex -= _migrated->overview(_overview).size() + (_history->overviewCount(_overview) - _history->overview(_overview).size());
-				} else if (!indexOfMigratedItem && previewIndex < 0) {
-					previewHistory = _migrated;
-					previewIndex += _migrated->overview(_overview).size();
-				}
-			}
-			if (previewIndex >= 0 && previewIndex < previewHistory->overview(_overview).size() && (previewHistory != (indexOfMigratedItem ? _migrated : _history) || previewIndex != indexInOverview)) {
-				if (auto item = App::histItemById(previewHistory->channelId(), getMsgIdFromOverview(previewHistory, previewIndex))) {
-					if (auto media = item->getMedia()) {
-						switch (media->type()) {
-						case MediaTypePhoto: static_cast<HistoryPhoto*>(media)->photo()->download(); break;
-						case MediaTypeFile:
-						case MediaTypeVideo:
-						case MediaTypeGif: {
-							auto doc = media->getDocument();
-							doc->thumb->load();
-							doc->automaticLoad(item);
-						} break;
-						case MediaTypeSticker: media->getDocument()->sticker()->img->load(); break;
-						}
-					}
-				}
-			}
-		}
-	} else if (_user) {
-		for (int32 i = from; i <= to; ++i) {
-			if (i >= 0 && i < _user->photos.size() && i != indexInOverview) {
-				_user->photos[i]->thumb->load();
-			}
-		}
-		for (int32 i = from; i <= to; ++i) {
-			if (i >= 0 && i < _user->photos.size() && i != indexInOverview) {
-				_user->photos[i]->download();
-			}
-		}
-		int32 forgetIndex = indexInOverview - delta * 2;
-		if (forgetIndex >= 0 && forgetIndex < _user->photos.size() && forgetIndex != indexInOverview) {
-			_user->photos[forgetIndex]->forget();
-		}
 	}
+
+	//} else if (_user) {
+	//	for (int32 i = from; i <= to; ++i) {
+	//		if (i >= 0 && i < _user->photos.size() && i != indexInOverview) {
+	//			_user->photos[i]->thumb->load();
+	//		}
+	//	}
+	//	for (int32 i = from; i <= to; ++i) {
+	//		if (i >= 0 && i < _user->photos.size() && i != indexInOverview) {
+	//			_user->photos[i]->download();
+	//		}
+	//	}
+	//	int32 forgetIndex = indexInOverview - delta * 2;
+	//	if (forgetIndex >= 0 && forgetIndex < _user->photos.size() && forgetIndex != indexInOverview) {
+	//		_user->photos[forgetIndex]->forget();
+	//	}
+	//} // TODO user
 }
 
 void MediaView::mousePressEvent(QMouseEvent *e) {
@@ -2763,69 +2816,81 @@ void MediaView::updateImage() {
 }
 
 void MediaView::findCurrent() {
-	auto i = 0;
-	if (_msgmigrated) {
-		for (auto msgId : _migrated->overview(_overview)) {
-			if (msgId == _msgid) {
-				_index = i;
-				break;
-			}
-			++i;
-		}
-		if (!_history->overviewCountLoaded(_overview)) {
-			loadBack();
-		} else if (_history->overviewLoaded(_overview) && !_migrated->overviewLoaded(_overview)) { // all loaded
-			if (!_migrated->overviewCountLoaded(_overview) || (_index < 2 && _migrated->overviewCount(_overview) > 0)) {
-				loadBack();
-			}
-		}
-	} else {
-		for (auto msgId : _history->overview(_overview)) {
-			if (msgId == _msgid) {
-				_index = i;
-				break;
-			}
-			++i;
-		}
-		if (!_history->overviewLoaded(_overview)) {
-			if (!_history->overviewCountLoaded(_overview) || (_index < 2 && _history->overviewCount(_overview) > 0) || (_index < 1 && _migrated && !_migrated->overviewLoaded(_overview))) {
-				loadBack();
-			}
-		} else if (_index < 1 && _migrated && !_migrated->overviewLoaded(_overview)) {
-			loadBack();
-		}
-		if (_migrated && !_migrated->overviewCountLoaded(_overview)) {
-			App::main()->preloadOverview(_migrated->peer, _overview);
-		}
+	if (!_sharedMediaData) {
+		_index = _fullIndex = _fullCount = base::none;
+		return;
 	}
+	_index = _sharedMediaData->indexOf(_msgid);
+	if (_index && _sharedMediaData->skippedBefore()) {
+		_fullIndex = (*_index + *_sharedMediaData->skippedBefore());
+	} else {
+		_fullIndex = base::none;
+	}
+	_fullCount = _sharedMediaData->fullCount();
+
+	//auto i = 0;
+	//if (_msgmigrated) {
+	//	for (auto msgId : _migrated->overview(_overview)) {
+	//		if (msgId == _msgid) {
+	//			_index = i;
+	//			break;
+	//		}
+	//		++i;
+	//	}
+	//	if (!_history->overviewCountLoaded(_overview)) {
+	//		loadBack();
+	//	} else if (_history->overviewLoaded(_overview) && !_migrated->overviewLoaded(_overview)) { // all loaded
+	//		if (!_migrated->overviewCountLoaded(_overview) || (_index < 2 && _migrated->overviewCount(_overview) > 0)) {
+	//			loadBack();
+	//		}
+	//	}
+	//} else {
+	//	for (auto msgId : _history->overview(_overview)) {
+	//		if (msgId == _msgid) {
+	//			_index = i;
+	//			break;
+	//		}
+	//		++i;
+	//	}
+	//	if (!_history->overviewLoaded(_overview)) {
+	//		if (!_history->overviewCountLoaded(_overview) || (_index < 2 && _history->overviewCount(_overview) > 0) || (_index < 1 && _migrated && !_migrated->overviewLoaded(_overview))) {
+	//			loadBack();
+	//		}
+	//	} else if (_index < 1 && _migrated && !_migrated->overviewLoaded(_overview)) {
+	//		loadBack();
+	//	}
+	//	if (_migrated && !_migrated->overviewCountLoaded(_overview)) {
+	//		App::main()->preloadOverview(_migrated->peer, _overview);
+	//	}
+	//} // TODO user
 }
 
 void MediaView::loadBack() {
-	if (_loadRequest || (_overview == OverviewCount && !_user)) {
-		return;
-	}
-	if (_index < 0 && (!_additionalChatPhoto || _photo != _additionalChatPhoto || !_history)) {
-		return;
-	}
+	//if (_loadRequest || (_overview == OverviewCount && !_user)) {
+	//	return;
+	//}
+	//if (_index < 0 && (!_additionalChatPhoto || _photo != _additionalChatPhoto || !_history)) {
+	//	return;
+	//}
 
-	if (_history && _overview != OverviewCount && (!_history->overviewLoaded(_overview) || (_migrated && !_migrated->overviewLoaded(_overview)))) {
-		if (App::main()) {
-			if (_msgmigrated || (_migrated && _index == 0 && _history->overviewLoaded(_overview))) {
-				App::main()->loadMediaBack(_migrated->peer, _overview);
-			} else {
-				App::main()->loadMediaBack(_history->peer, _overview);
-				if (_migrated && _index == 0 && (_migrated->overviewCount(_overview) < 0 || _migrated->overview(_overview).isEmpty()) && !_migrated->overviewLoaded(_overview)) {
-					App::main()->loadMediaBack(_migrated->peer, _overview);
-				}
-			}
-			if (_msgmigrated && !_history->overviewCountLoaded(_overview)) {
-				App::main()->preloadOverview(_history->peer, _overview);
-			}
-		}
-	} else if (_user && _user->photosCount != 0) {
-		int32 limit = (_index < MediaOverviewStartPerPage && _user->photos.size() > MediaOverviewStartPerPage) ? SearchPerPage : MediaOverviewStartPerPage;
-		_loadRequest = MTP::send(MTPphotos_GetUserPhotos(_user->inputUser, MTP_int(_user->photos.size()), MTP_long(0), MTP_int(limit)), rpcDone(&MediaView::userPhotosLoaded, _user));
-	}
+	//if (_history && _overview != OverviewCount && (!_history->overviewLoaded(_overview) || (_migrated && !_migrated->overviewLoaded(_overview)))) {
+	//	if (App::main()) {
+	//		if (_msgmigrated || (_migrated && _index == 0 && _history->overviewLoaded(_overview))) {
+	//			App::main()->loadMediaBack(_migrated->peer, _overview);
+	//		} else {
+	//			App::main()->loadMediaBack(_history->peer, _overview);
+	//			if (_migrated && _index == 0 && (_migrated->overviewCount(_overview) < 0 || _migrated->overview(_overview).isEmpty()) && !_migrated->overviewLoaded(_overview)) {
+	//				App::main()->loadMediaBack(_migrated->peer, _overview);
+	//			}
+	//		}
+	//		if (_msgmigrated && !_history->overviewCountLoaded(_overview)) {
+	//			App::main()->preloadOverview(_history->peer, _overview);
+	//		}
+	//	}
+	//} else if (_user && _user->photosCount != 0) {
+	//	int32 limit = (_index < MediaOverviewStartPerPage && _user->photos.size() > MediaOverviewStartPerPage) ? SearchPerPage : MediaOverviewStartPerPage;
+	//	_loadRequest = MTP::send(MTPphotos_GetUserPhotos(_user->inputUser, MTP_int(_user->photos.size()), MTP_long(0), MTP_int(limit)), rpcDone(&MediaView::userPhotosLoaded, _user));
+	//} // TODO user
 }
 
 MediaView::LastChatPhoto MediaView::computeLastOverviewChatPhoto() {
@@ -2903,34 +2968,11 @@ void MediaView::userPhotosLoaded(UserData *u, const MTPphotos_Photos &photos, mt
 }
 
 void MediaView::updateHeader() {
-	int32 index = _index, count = 0, addcount = (_migrated && _overview != OverviewCount) ? _migrated->overviewCount(_overview) : 0;
+	auto index = _fullIndex ? *_fullIndex : -1;
+	auto count = _fullCount ? *_fullCount : -1;
 	if (_history) {
-		if (_overview != OverviewCount) {
-			bool lastOverviewPhotoLoaded = (!_history->overview(_overview).isEmpty() || (
-				_migrated && _history->overviewCount(_overview) == 0 && !_migrated->overview(_overview).isEmpty()));
-			count = _history->overviewCount(_overview);
-			if (addcount >= 0 && count >= 0) {
-				count += addcount;
-			}
-			if (index >= 0 && (_msgmigrated ? (count >= 0 && addcount >= 0 && _history->overviewLoaded(_overview)) : (count >= 0))) {
-				if (_msgmigrated) {
-					index += addcount - _migrated->overview(_overview).size();
-				} else {
-					index += count - _history->overview(_overview).size();
-				}
-				if (_additionalChatPhoto && lastOverviewPhotoLoaded) {
-					++count;
-				}
-			} else if (index < 0 && _additionalChatPhoto && _photo == _additionalChatPhoto && lastOverviewPhotoLoaded) {
-				// Additional chat photo (not in the list => place it at the end of the list).
-				index = count;
-				++count;
-			} else {
-				count = 0; // unknown yet
-			}
-		}
 	} else if (_user) {
-		count = _user->photosCount ? _user->photosCount : _user->photos.size();
+		count = _user->photosCount ? _user->photosCount : _user->photos.size(); // TODO user
 	}
 	if (index >= 0 && index < count && count > 1) {
 		if (_doc) {
@@ -2951,8 +2993,10 @@ void MediaView::updateHeader() {
 			_headerText = lang(lng_mediaview_single_photo);
 		}
 	}
-	_headerHasLink = _history && typeHasMediaOverview(_overview);
-	int32 hwidth = st::mediaviewThickFont->width(_headerText);
+	_headerHasLink = (
+		sharedMediaType()
+		| SharedMediaOverviewType) != base::none;
+	auto hwidth = st::mediaviewThickFont->width(_headerText);
 	if (hwidth > width() / 3) {
 		hwidth = width() / 3;
 		_headerText = st::mediaviewThickFont->elided(_headerText, hwidth, Qt::ElideMiddle);
@@ -2966,13 +3010,13 @@ float64 MediaView::overLevel(OverState control) const {
 }
 
 MsgId MediaView::getMsgIdFromOverview(not_null<History*> history, int index) const {
-	auto &overview = history->overview(_overview);
-	if (index >= 0 && index < overview.size()) {
-		auto it = overview.begin();
-		for (auto i = 0; i != index; ++i) {
-			++it;
-		}
-		return *it;
-	}
+	//auto &overview = history->overview(_overview);
+	//if (index >= 0 && index < overview.size()) {
+	//	auto it = overview.begin();
+	//	for (auto i = 0; i != index; ++i) {
+	//		++it;
+	//	}
+	//	return *it;
+	//}
 	return 0;
 }
diff --git a/Telegram/SourceFiles/mediaview.h b/Telegram/SourceFiles/mediaview.h
index b75eac532..c17df973d 100644
--- a/Telegram/SourceFiles/mediaview.h
+++ b/Telegram/SourceFiles/mediaview.h
@@ -22,6 +22,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 
 #include "ui/widgets/dropdown_menu.h"
 #include "ui/effects/radial_animation.h"
+#include "history/history_shared_media.h"
 
 namespace Media {
 namespace Player {
@@ -58,9 +59,9 @@ public:
 
 	void updateOver(QPoint mpos);
 
-	void showPhoto(PhotoData *photo, HistoryItem *context);
-	void showPhoto(PhotoData *photo, PeerData *context);
-	void showDocument(DocumentData *doc, HistoryItem *context);
+	void showPhoto(not_null<PhotoData*> photo, HistoryItem *context);
+	void showPhoto(not_null<PhotoData*> photo, PeerData *context);
+	void showDocument(not_null<DocumentData*> document, HistoryItem *context);
 	void moveToScreen();
 	bool moveToNext(int32 delta);
 	void preloadData(int32 delta);
@@ -154,13 +155,25 @@ private:
 	void showSaveMsgFile();
 	void updateMixerVideoVolume() const;
 
+	struct SharedMedia;
+	using SharedMediaType = SharedMediaViewer::Type;
+	using SharedMediaKey = SharedMediaViewer::Key;
+	base::optional<SharedMediaType> sharedMediaType() const;
+	base::optional<SharedMediaKey> sharedMediaKey() const;
+	void validateSharedMedia();
+	bool validSharedMedia() const;
+	std::unique_ptr<SharedMedia> createSharedMedia() const;
+	void refreshSharedMedia();
+	void handleSharedMediaUpdate(const SharedMediaSlice &update);
+	void refreshNavVisibility();
+
 	void dropdownHidden();
 	void updateDocSize();
 	void updateControls();
 	void updateActions();
 
-	void displayPhoto(PhotoData *photo, HistoryItem *item);
-	void displayDocument(DocumentData *doc, HistoryItem *item);
+	void displayPhoto(not_null<PhotoData*> photo, HistoryItem *item);
+	void displayDocument(DocumentData *document, HistoryItem *item);
 	void displayFinished();
 	void findCurrent();
 	void loadBack();
@@ -184,7 +197,7 @@ private:
 	void updateThemePreviewGeometry();
 
 	void documentUpdated(DocumentData *doc);
-	void changingMsgId(HistoryItem *row, MsgId newId);
+	void changingMsgId(not_null<HistoryItem*> row, MsgId newId);
 
 	// Radial animation interface.
 	float64 radialProgress() const;
@@ -230,7 +243,9 @@ private:
 
 	PhotoData *_photo = nullptr;
 	DocumentData *_doc = nullptr;
-	MediaOverviewType _overview = OverviewCount;
+	std::unique_ptr<SharedMedia> _sharedMedia;
+	base::optional<SharedMediaSlice> _sharedMediaData;
+
 	QRect _closeNav, _closeNavIcon;
 	QRect _leftNav, _leftNavIcon, _rightNav, _rightNavIcon;
 	QRect _headerNav, _nameNav, _dateNav;
@@ -308,7 +323,9 @@ private:
 	PeerData *_from = nullptr;
 	Text _fromName;
 
-	int _index = -1; // index in photos or files array, -1 if just photo
+	base::optional<int> _index; // Index in current _sharedMedia data.
+	base::optional<int> _fullIndex; // Index in full shared media.
+	base::optional<int> _fullCount;
 	MsgId _msgid = 0; // msgId of current photo or file
 	bool _msgmigrated = false; // msgId is from _migrated history
 	ChannelId _channel = NoChannel;
diff --git a/Telegram/SourceFiles/overviewwidget.cpp b/Telegram/SourceFiles/overviewwidget.cpp
index 71f994483..d05e0bb6a 100644
--- a/Telegram/SourceFiles/overviewwidget.cpp
+++ b/Telegram/SourceFiles/overviewwidget.cpp
@@ -96,7 +96,7 @@ OverviewInner::OverviewInner(OverviewWidget *overview, Ui::ScrollArea *scroll, P
 	subscribe(App::wnd()->dragFinished(), [this] {
 		dragActionUpdate(QCursor::pos());
 	});
-	subscribe(Auth().messageIdChanging, [this](std::pair<HistoryItem*, MsgId> update) {
+	subscribe(Auth().messageIdChanging, [this](std::pair<not_null<HistoryItem*>, MsgId> update) {
 		changingMsgId(update.first, update.second);
 	});
 
diff --git a/Telegram/SourceFiles/storage/storage_facade.cpp b/Telegram/SourceFiles/storage/storage_facade.cpp
index 201accd64..4a5683f01 100644
--- a/Telegram/SourceFiles/storage/storage_facade.cpp
+++ b/Telegram/SourceFiles/storage/storage_facade.cpp
@@ -35,6 +35,10 @@ public:
 		SharedMediaQuery &&query,
 		base::lambda_once<void(SharedMediaResult&&)> &&callback);
 
+	base::Observable<SharedMediaSliceUpdate> &sharedMediaSliceUpdated();
+	base::Observable<SharedMediaRemoveOne> &sharedMediaOneRemoved();
+	base::Observable<SharedMediaRemoveAll> &sharedMediaAllRemoved();
+
 private:
 	SharedMedia _sharedMedia;
 
@@ -66,6 +70,17 @@ void Facade::Impl::query(
 	_sharedMedia.query(query, std::move(callback));
 }
 
+base::Observable<SharedMediaSliceUpdate> &Facade::Impl::sharedMediaSliceUpdated() {
+	return _sharedMedia.sliceUpdated;
+}
+
+base::Observable<SharedMediaRemoveOne> &Facade::Impl::sharedMediaOneRemoved() {
+	return _sharedMedia.oneRemoved;
+}
+
+base::Observable<SharedMediaRemoveAll> &Facade::Impl::sharedMediaAllRemoved() {
+	return _sharedMedia.allRemoved;
+}
 
 Facade::Facade() : _impl(std::make_unique<Impl>()) {
 }
@@ -96,6 +111,18 @@ void Facade::query(
 	_impl->query(std::move(query), std::move(callback));
 }
 
+base::Observable<SharedMediaSliceUpdate> &Facade::sharedMediaSliceUpdated() {
+	return _impl->sharedMediaSliceUpdated();
+}
+
+base::Observable<SharedMediaRemoveOne> &Facade::sharedMediaOneRemoved() {
+	return _impl->sharedMediaOneRemoved();
+}
+
+base::Observable<SharedMediaRemoveAll> &Facade::sharedMediaAllRemoved() {
+	return _impl->sharedMediaAllRemoved();
+}
+
 Facade::~Facade() = default;
 
 } // namespace Storage
diff --git a/Telegram/SourceFiles/storage/storage_facade.h b/Telegram/SourceFiles/storage/storage_facade.h
index 52e9d3476..fb7a074cb 100644
--- a/Telegram/SourceFiles/storage/storage_facade.h
+++ b/Telegram/SourceFiles/storage/storage_facade.h
@@ -31,6 +31,7 @@ struct SharedMediaRemoveOne;
 struct SharedMediaRemoveAll;
 struct SharedMediaQuery;
 struct SharedMediaResult;
+struct SharedMediaSliceUpdate;
 
 class Facade {
 public:
@@ -45,6 +46,10 @@ public:
 		SharedMediaQuery &&query,
 		base::lambda_once<void(SharedMediaResult&&)> &&callback);
 
+	base::Observable<SharedMediaSliceUpdate> &sharedMediaSliceUpdated();
+	base::Observable<SharedMediaRemoveOne> &sharedMediaOneRemoved();
+	base::Observable<SharedMediaRemoveAll> &sharedMediaAllRemoved();
+
 	~Facade();
 
 private:
diff --git a/Telegram/SourceFiles/storage/storage_shared_media.cpp b/Telegram/SourceFiles/storage/storage_shared_media.cpp
index e80e0bcc7..abfe86eb2 100644
--- a/Telegram/SourceFiles/storage/storage_shared_media.cpp
+++ b/Telegram/SourceFiles/storage/storage_shared_media.cpp
@@ -47,15 +47,16 @@ void SharedMedia::List::Slice::merge(
 
 template <typename Range>
 int SharedMedia::List::uniteAndAdd(
+		SliceUpdate &update,
 		base::flat_set<Slice>::iterator uniteFrom,
 		base::flat_set<Slice>::iterator uniteTill,
 		const Range &messages,
 		MsgRange noSkipRange) {
+	auto uniteFromIndex = uniteFrom - _slices.begin();
 	auto was = uniteFrom->messages.size();
 	_slices.modify(uniteFrom, [&](Slice &slice) {
 		slice.merge(messages, noSkipRange);
 	});
-	auto result = uniteFrom->messages.size() - was;
 	auto firstToErase = uniteFrom + 1;
 	if (firstToErase != uniteTill) {
 		for (auto it = firstToErase; it != uniteTill; ++it) {
@@ -64,21 +65,21 @@ int SharedMedia::List::uniteAndAdd(
 			});
 		}
 		_slices.erase(firstToErase, uniteTill);
+		uniteFrom = _slices.begin() + uniteFromIndex;
 	}
-	return result;
+	update.messages = &uniteFrom->messages;
+	update.range = uniteFrom->range;
+	return uniteFrom->messages.size() - was;
 }
 
 template <typename Range>
 int SharedMedia::List::addRangeItemsAndCount(
+		SliceUpdate &update,
 		const Range &messages,
 		MsgRange noSkipRange,
 		base::optional<int> count) {
 	Expects((noSkipRange.from < noSkipRange.till)
 		|| (noSkipRange.from == noSkipRange.till && messages.begin() == messages.end()));
-
-	if (count) {
-		_count = count;
-	}
 	if (noSkipRange.from == noSkipRange.till) {
 		return 0;
 	}
@@ -92,7 +93,7 @@ int SharedMedia::List::addRangeItemsAndCount(
 		noSkipRange.till,
 		[](MsgId till, const Slice &slice) { return till < slice.range.from; });
 	if (uniteFrom < uniteTill) {
-		return uniteAndAdd(uniteFrom, uniteTill, messages, noSkipRange);
+		return uniteAndAdd(update, uniteFrom, uniteTill, messages, noSkipRange);
 	}
 
 	auto sliceMessages = base::flat_set<MsgId> {
@@ -101,29 +102,39 @@ int SharedMedia::List::addRangeItemsAndCount(
 	auto slice = _slices.emplace(
 		std::move(sliceMessages),
 		noSkipRange);
+	update.messages = &slice->messages;
+	update.range = slice->range;
 	return slice->messages.size();
 }
 
 template <typename Range>
-int SharedMedia::List::addRange(
+void SharedMedia::List::addRange(
 		const Range &messages,
 		MsgRange noSkipRange,
-		base::optional<int> count) {
-	auto result = addRangeItemsAndCount(messages, noSkipRange, count);
+		base::optional<int> count,
+		bool incrementCount) {
+	Expects(!count || !incrementCount);
+
+	auto wasCount = _count;
+	auto update = SliceUpdate();
+	auto result = addRangeItemsAndCount(update, messages, noSkipRange, count);
+	if (count) {
+		_count = count;
+	} else if (incrementCount && _count && result > 0) {
+		*_count += result;
+	}
 	if (_slices.size() == 1) {
 		if (_slices.front().range == MsgRange { 0, ServerMaxMsgId }) {
 			_count = _slices.front().messages.size();
 		}
 	}
-	return result;
+	update.count = _count;
+	sliceUpdated.notify(update, true);
 }
 
 void SharedMedia::List::addNew(MsgId messageId) {
 	auto range = { messageId };
-	auto added = addRange(range, { messageId, ServerMaxMsgId }, base::none);
-	if (added > 0 && _count) {
-		*_count += added;
-	}
+	addRange(range, { messageId, ServerMaxMsgId }, base::none, true);
 }
 
 void SharedMedia::List::addExisting(
@@ -169,9 +180,9 @@ void SharedMedia::List::query(
 
 	auto slice = base::lower_bound(
 		_slices,
-		query.messageId,
+		query.key.messageId,
 		[](const Slice &slice, MsgId id) { return slice.range.till < id; });
-	if (slice != _slices.end() && slice->range.from <= query.messageId) {
+	if (slice != _slices.end() && slice->range.from <= query.key.messageId) {
 		result = queryFromSlice(query, *slice);
 	} else {
 		result.count = _count;
@@ -189,18 +200,20 @@ SharedMediaResult SharedMedia::List::queryFromSlice(
 		const SharedMediaQuery &query,
 		const Slice &slice) {
 	auto result = SharedMediaResult {};
-	auto position = base::lower_bound(slice.messages, query.messageId);
-	auto haveBefore = position - slice.messages.begin();
-	auto haveEqualOrAfter = slice.messages.end() - position;
+	auto position = base::lower_bound(slice.messages, query.key.messageId);
+	auto haveBefore = int(position - slice.messages.begin());
+	auto haveEqualOrAfter = int(slice.messages.end() - position);
 	auto before = qMin(haveBefore, query.limitBefore);
 	auto equalOrAfter = qMin(haveEqualOrAfter, query.limitAfter + 1);
-	result.messageIds.reserve(before + equalOrAfter);
+	auto ids = std::vector<MsgId>();
+	ids.reserve(before + equalOrAfter);
 	for (
 		auto from = position - before, till = position + equalOrAfter;
 		from != till;
 		++from) {
-		result.messageIds.push_back(*from);
+		ids.push_back(*from);
 	}
+	result.messageIds.merge(ids.begin(), ids.end());
 	if (slice.range.from == 0) {
 		result.skippedBefore = haveBefore - before;
 	}
@@ -212,21 +225,41 @@ SharedMediaResult SharedMedia::List::queryFromSlice(
 		if (!result.skippedBefore && result.skippedAfter) {
 			result.skippedBefore = *result.count
 				- *result.skippedAfter
-				- result.messageIds.size();
+				- int(result.messageIds.size());
 		} else if (!result.skippedAfter && result.skippedBefore) {
 			result.skippedAfter = *result.count
 				- *result.skippedBefore
-				- result.messageIds.size();
+				- int(result.messageIds.size());
 		}
 	}
 	return result;
 }
 
-void SharedMedia::add(SharedMediaAddNew &&query) {
-	auto peerIt = _lists.find(query.peerId);
-	if (peerIt == _lists.end()) {
-		peerIt = _lists.emplace(query.peerId, Lists {}).first;
+std::map<PeerId, SharedMedia::Lists>::iterator
+		SharedMedia::enforceLists(PeerId peer) {
+	auto result = _lists.find(peer);
+	if (result != _lists.end()) {
+		return result;
 	}
+	result = _lists.emplace(peer, Lists {}).first;
+	for (auto index = 0; index != kSharedMediaTypeCount; ++index) {
+		auto &list = result->second[index];
+		auto type = static_cast<SharedMediaType>(index);
+		subscribe(list.sliceUpdated, [this, type, peer](const SliceUpdate &update) {
+			sliceUpdated.notify(SharedMediaSliceUpdate(
+				peer,
+				type,
+				update.messages,
+				update.range,
+				update.count), true);
+		});
+	}
+	return result;
+}
+
+void SharedMedia::add(SharedMediaAddNew &&query) {
+	auto peer = query.peerId;
+	auto peerIt = enforceLists(peer);
 	for (auto index = 0; index != kSharedMediaTypeCount; ++index) {
 		auto type = static_cast<SharedMediaType>(index);
 		if (query.types.test(type)) {
@@ -236,10 +269,7 @@ void SharedMedia::add(SharedMediaAddNew &&query) {
 }
 
 void SharedMedia::add(SharedMediaAddExisting &&query) {
-	auto peerIt = _lists.find(query.peerId);
-	if (peerIt == _lists.end()) {
-		peerIt = _lists.emplace(query.peerId, Lists {}).first;
-	}
+	auto peerIt = enforceLists(query.peerId);
 	for (auto index = 0; index != kSharedMediaTypeCount; ++index) {
 		auto type = static_cast<SharedMediaType>(index);
 		if (query.types.test(type)) {
@@ -250,10 +280,7 @@ void SharedMedia::add(SharedMediaAddExisting &&query) {
 
 void SharedMedia::add(SharedMediaAddSlice &&query) {
 	Expects(IsValidSharedMediaType(query.type));
-	auto peerIt = _lists.find(query.peerId);
-	if (peerIt == _lists.end()) {
-		peerIt = _lists.emplace(query.peerId, Lists {}).first;
-	}
+	auto peerIt = enforceLists(query.peerId);
 	auto index = static_cast<int>(query.type);
 	peerIt->second[index].addSlice(std::move(query.messageIds), query.noSkipRange, query.count);
 }
@@ -265,6 +292,7 @@ void SharedMedia::remove(SharedMediaRemoveOne &&query) {
 			auto type = static_cast<SharedMediaType>(index);
 			if (query.types.test(type)) {
 				peerIt->second[index].removeOne(query.messageId);
+				oneRemoved.notify(query, true);
 			}
 		}
 	}
@@ -276,18 +304,19 @@ void SharedMedia::remove(SharedMediaRemoveAll &&query) {
 		for (auto index = 0; index != kSharedMediaTypeCount; ++index) {
 			peerIt->second[index].removeAll();
 		}
+		allRemoved.notify(query, true);
 	}
 }
 
 void SharedMedia::query(
 		const SharedMediaQuery &query,
 		base::lambda_once<void(SharedMediaResult&&)> &&callback) {
-	Expects(IsValidSharedMediaType(query.type));
-	auto peerIt = _lists.find(query.peerId);
+	Expects(IsValidSharedMediaType(query.key.type));
+	auto peerIt = _lists.find(query.key.peerId);
 	if (peerIt != _lists.end()) {
-		auto index = static_cast<int>(query.type);
+		auto index = static_cast<int>(query.key.type);
 		peerIt->second[index].query(query, std::move(callback));
 	}
 }
 
-} // namespace Storage
\ No newline at end of file
+} // namespace Storage
diff --git a/Telegram/SourceFiles/storage/storage_shared_media.h b/Telegram/SourceFiles/storage/storage_shared_media.h
index 1a40daac4..22b1b0707 100644
--- a/Telegram/SourceFiles/storage/storage_shared_media.h
+++ b/Telegram/SourceFiles/storage/storage_shared_media.h
@@ -122,25 +122,44 @@ struct SharedMediaRemoveAll {
 
 };
 
-struct SharedMediaQuery {
-	SharedMediaQuery(
+struct SharedMediaKey {
+	SharedMediaKey(
 		PeerId peerId,
 		SharedMediaType type,
-		MsgId messageId,
-		int limitBefore,
-		int limitAfter)
+		MsgId messageId)
 		: peerId(peerId)
-		, messageId(messageId)
-		, limitBefore(limitBefore)
-		, limitAfter(limitAfter)
-		, type(type) {
+		, type(type)
+		, messageId(messageId) {
+	}
+
+	bool operator==(const SharedMediaKey &other) const {
+		return (peerId == other.peerId)
+			&& (type == other.type)
+			&& (messageId == other.messageId);
+	}
+	bool operator!=(const SharedMediaKey &other) const {
+		return !(*this == other);
 	}
 
 	PeerId peerId = 0;
+	SharedMediaType type = SharedMediaType::kCount;
 	MsgId messageId = 0;
+
+};
+
+struct SharedMediaQuery {
+	SharedMediaQuery(
+		SharedMediaKey key,
+		int limitBefore,
+		int limitAfter)
+		: key(key)
+		, limitBefore(limitBefore)
+		, limitAfter(limitAfter) {
+	}
+
+	SharedMediaKey key;
 	int limitBefore = 0;
 	int limitAfter = 0;
-	SharedMediaType type = SharedMediaType::kCount;
 
 };
 
@@ -148,10 +167,31 @@ struct SharedMediaResult {
 	base::optional<int> count;
 	base::optional<int> skippedBefore;
 	base::optional<int> skippedAfter;
-	std::vector<MsgId> messageIds;
+	base::flat_set<MsgId> messageIds;
 };
 
-class SharedMedia {
+struct SharedMediaSliceUpdate {
+	SharedMediaSliceUpdate(
+		PeerId peerId,
+		SharedMediaType type,
+		const base::flat_set<MsgId> *messages,
+		MsgRange range,
+		base::optional<int> count)
+		: peerId(peerId)
+		, type(type)
+		, messages(messages)
+		, range(range)
+		, count(count) {
+	}
+
+	PeerId peerId = 0;
+	SharedMediaType type = SharedMediaType::kCount;
+	const base::flat_set<MsgId> *messages = nullptr;
+	MsgRange range;
+	base::optional<int> count;
+};
+
+class SharedMedia : private base::Subscriber {
 public:
 	using Type = SharedMediaType;
 
@@ -164,6 +204,10 @@ public:
 		const SharedMediaQuery &query,
 		base::lambda_once<void(SharedMediaResult&&)> &&callback);
 
+	base::Observable<SharedMediaSliceUpdate> sliceUpdated;
+	base::Observable<SharedMediaRemoveOne> oneRemoved;
+	base::Observable<SharedMediaRemoveAll> allRemoved;
+
 private:
 	class List {
 	public:
@@ -179,6 +223,13 @@ private:
 			const SharedMediaQuery &query,
 			base::lambda_once<void(SharedMediaResult&&)> &&callback);
 
+		struct SliceUpdate {
+			const base::flat_set<MsgId> *messages = nullptr;
+			MsgRange range;
+			base::optional<int> count;
+		};
+		base::Observable<SliceUpdate> sliceUpdated;
+
 	private:
 		struct Slice {
 			Slice(base::flat_set<MsgId> &&messages, MsgRange range);
@@ -197,20 +248,23 @@ private:
 
 		template <typename Range>
 		int uniteAndAdd(
+			SliceUpdate &update,
 			base::flat_set<Slice>::iterator uniteFrom,
 			base::flat_set<Slice>::iterator uniteTill,
 			const Range &messages,
 			MsgRange noSkipRange);
 		template <typename Range>
 		int addRangeItemsAndCount(
+			SliceUpdate &update,
 			const Range &messages,
 			MsgRange noSkipRange,
 			base::optional<int> count);
 		template <typename Range>
-		int addRange(
+		void addRange(
 			const Range &messages,
 			MsgRange noSkipRange,
-			base::optional<int> count);
+			base::optional<int> count,
+			bool incrementCount = false);
 
 		SharedMediaResult queryFromSlice(
 			const SharedMediaQuery &query,
@@ -220,8 +274,11 @@ private:
 		base::flat_set<Slice> _slices;
 
 	};
+	using SliceUpdate = List::SliceUpdate;
 	using Lists = std::array<List, kSharedMediaTypeCount>;
 
+	std::map<PeerId, Lists>::iterator enforceLists(PeerId peerId);
+
 	std::map<PeerId, Lists> _lists;
 
 };
diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt
index ef644e171..a16df536d 100644
--- a/Telegram/gyp/telegram_sources.txt
+++ b/Telegram/gyp/telegram_sources.txt
@@ -186,6 +186,8 @@
 <(src_loc)/history/history_service.h
 <(src_loc)/history/history_service_layout.cpp
 <(src_loc)/history/history_service_layout.h
+<(src_loc)/history/history_shared_media.cpp
+<(src_loc)/history/history_shared_media.h
 <(src_loc)/history/history_widget.cpp
 <(src_loc)/history/history_widget.h
 <(src_loc)/inline_bots/inline_bot_layout_internal.cpp