From 5c87b4213525057c6fc89eb340bec9c9abbf09e9 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Thu, 22 Jun 2017 02:54:38 +0300
Subject: [PATCH] Use plain HistoryItem in channel admin events log.

Instead of using a complex AdminLog::Item use just HistoryItem*.
---
 Telegram/SourceFiles/auth_session.h           |  12 +
 Telegram/SourceFiles/facades.cpp              |   6 +-
 .../history/history_admin_log_inner.cpp       | 205 +++++++++++-------
 .../history/history_admin_log_inner.h         |  24 +-
 .../history/history_admin_log_item.cpp        | 163 ++++----------
 .../history/history_admin_log_item.h          |  71 +++---
 .../history/history_inner_widget.cpp          |  12 +-
 .../history/history_inner_widget.h            |   2 +-
 Telegram/SourceFiles/history/history_item.cpp |  56 +++--
 Telegram/SourceFiles/history/history_item.h   |  37 +++-
 Telegram/SourceFiles/historywidget.cpp        |  28 +--
 Telegram/SourceFiles/historywidget.h          |   2 +-
 Telegram/SourceFiles/mainwidget.cpp           |  11 -
 Telegram/SourceFiles/mainwidget.h             |   3 -
 14 files changed, 313 insertions(+), 319 deletions(-)

diff --git a/Telegram/SourceFiles/auth_session.h b/Telegram/SourceFiles/auth_session.h
index 1599a66f3..d67675671 100644
--- a/Telegram/SourceFiles/auth_session.h
+++ b/Telegram/SourceFiles/auth_session.h
@@ -63,6 +63,16 @@ public:
 	base::Observable<gsl::not_null<const HistoryItem*>> &repaintLogEntry() {
 		return _repaintLogEntry;
 	}
+	base::Observable<void> &pendingHistoryResize() {
+		return _pendingHistoryResize;
+	}
+	struct ItemVisibilityQuery {
+		gsl::not_null<HistoryItem*> item;
+		gsl::not_null<bool*> isVisible;
+	};
+	base::Observable<ItemVisibilityQuery> &queryItemVisibility() {
+		return _queryItemVisibility;
+	}
 
 	void copyFrom(const AuthSessionData &other) {
 		_variables = other._variables;
@@ -139,6 +149,8 @@ private:
 	base::Observable<void> _savedGifsUpdated;
 	base::Observable<gsl::not_null<History*>> _historyCleared;
 	base::Observable<gsl::not_null<const HistoryItem*>> _repaintLogEntry;
+	base::Observable<void> _pendingHistoryResize;
+	base::Observable<ItemVisibilityQuery> _queryItemVisibility;
 	Variables _variables;
 	TimeMs _lastTimeVideoPlayedAt = 0;
 
diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp
index ca17f1085..2215999ad 100644
--- a/Telegram/SourceFiles/facades.cpp
+++ b/Telegram/SourceFiles/facades.cpp
@@ -25,6 +25,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 #include "mainwindow.h"
 #include "mainwidget.h"
 #include "messenger.h"
+#include "auth_session.h"
 #include "boxes/confirm_box.h"
 #include "layerwidget.h"
 #include "lang/lang_keys.h"
@@ -368,9 +369,8 @@ void historyMuteUpdated(History *history) {
 }
 
 void handlePendingHistoryUpdate() {
-	if (auto main = App::main()) {
-		main->notify_handlePendingHistoryUpdate();
-	}
+	AuthSession::Current().data().pendingHistoryResize().notify(true);
+
 	for (auto item : base::take(Global::RefPendingRepaintItems())) {
 		Ui::repaintHistoryItem(item);
 
diff --git a/Telegram/SourceFiles/history/history_admin_log_inner.cpp b/Telegram/SourceFiles/history/history_admin_log_inner.cpp
index 2fa9c13f6..6b0542886 100644
--- a/Telegram/SourceFiles/history/history_admin_log_inner.cpp
+++ b/Telegram/SourceFiles/history/history_admin_log_inner.cpp
@@ -22,7 +22,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 
 #include "styles/style_history.h"
 #include "history/history_media_types.h"
-#include "history/history_admin_log_item.h"
 #include "history/history_admin_log_section.h"
 #include "mainwindow.h"
 #include "window/window_controller.h"
@@ -46,9 +45,18 @@ InnerWidget::InnerWidget(QWidget *parent, gsl::not_null<Window::Controller*> con
 	setMouseTracking(true);
 	_scrollDateHideTimer.setCallback([this] { scrollDateHideByTimer(); });
 	subscribe(AuthSession::Current().data().repaintLogEntry(), [this](gsl::not_null<const HistoryItem*> historyItem) {
-		auto it = _itemsByHistoryItems.find(historyItem);
-		if (it != _itemsByHistoryItems.cend()) {
-			repaintItem(it->second);
+		if (_history == historyItem->history()) {
+			repaintItem(historyItem);
+		}
+	});
+	subscribe(AuthSession::Current().data().pendingHistoryResize(), [this] { handlePendingHistoryResize(); });
+	subscribe(AuthSession::Current().data().queryItemVisibility(), [this](const AuthSessionData::ItemVisibilityQuery &query) {
+		if (_history != query.item->history() || !query.item->isLogEntry() || !isVisible()) {
+			return;
+		}
+		auto top = itemTop(query.item);
+		if (top >= 0 && top + query.item->height() > _visibleTop && top < _visibleBottom) {
+			*query.isVisible = true;
 		}
 	});
 }
@@ -64,11 +72,11 @@ void InnerWidget::setVisibleTopBottom(int visibleTop, int visibleBottom) {
 void InnerWidget::updateVisibleTopItem() {
 	auto start = std::rbegin(_items), end = std::rend(_items);
 	auto from = std::upper_bound(start, end, _visibleTop, [](int top, auto &elem) {
-		return top <= elem->top() + elem->height();
+		return top <= elem->y() + elem->height();
 	});
 	if (from != end) {
-		_visibleTopItem = from->get();
-		_visibleTopFromItem = _visibleTop - _visibleTopItem->top();
+		_visibleTopItem = *from;
+		_visibleTopFromItem = _visibleTop - _visibleTopItem->y();
 	} else {
 		_visibleTopItem = nullptr;
 		_visibleTopFromItem = _visibleTop;
@@ -144,13 +152,15 @@ void InnerWidget::applyFilter(MTPDchannelAdminLogEventsFilter::Flags flags, cons
 
 QString InnerWidget::tooltipText() const {
 	if (_mouseCursorState == HistoryInDateCursorState && _mouseAction == MouseAction::None) {
-		if (_itemOver) {
-			auto dateText = _itemOver->date().toString(QLocale::system().dateTimeFormat(QLocale::LongFormat));
+		if (auto item = App::hoveredItem()) {
+			auto dateText = item->date.toString(QLocale::system().dateTimeFormat(QLocale::LongFormat));
 			return dateText;
 		}
 	} else if (_mouseCursorState == HistoryInForwardedCursorState && _mouseAction == MouseAction::None) {
-		if (_itemOver) {
-			auto forwarded = _itemOver->getForwardedInfoText();
+		if (auto item = App::hoveredItem()) {
+			if (auto forwarded = item->Get<HistoryMessageForwarded>()) {
+				return forwarded->_text.originalText(AllTextSelection, ExpandLinksNone);
+			}
 		}
 	} else if (auto lnk = ClickHandler::getActive()) {
 		return lnk->tooltip();
@@ -226,11 +236,26 @@ void InnerWidget::preloadMore(Direction direction) {
 			_items.reserve(_items.size() + events.size());
 			for_const (auto &event, events) {
 				t_assert(event.type() == mtpc_channelAdminLogEvent);
-				_items.push_back(std::make_unique<Item>(_history, _idManager, event.c_channelAdminLogEvent()));
+				auto &data = event.c_channelAdminLogEvent();
+				auto count = 0;
+				GenerateItems(_history, _idManager, data, [this, id = data.vid.v, &count](HistoryItemOwned item) {
+					_items.push_back(std::move(item));
+					_itemsByIds.emplace(id, item.get());
+					++count;
+				});
+				if (count > 1) {
+					// Reverse the inner order of the added messages, because we load events
+					// from bottom to top but inside one event they go from top to bottom.
+					auto full = _items.size();
+					auto from = full - count;
+					for (auto i = 0, toReverse = count / 2; i != toReverse; ++i) {
+						std::swap(_items[from + i], _items[full - i - 1]);
+					}
+				}
 			}
 			if (!_items.empty()) {
-				_maxId = _items.front()->id();
-				_minId = _items.back()->id();
+				_maxId = (--_itemsByIds.end())->first;
+				_minId = _itemsByIds.begin()->first;
 				if (_minId == 1) {
 					_upLoaded = true;
 				}
@@ -262,7 +287,7 @@ int InnerWidget::resizeGetHeight(int newWidth) {
 
 	auto newHeight = 0;
 	for (auto &item : base::reversed(_items)) {
-		item->setTop(newHeight);
+		item->setY(newHeight);
 		newHeight += item->resizeGetHeight(newWidth);
 	}
 	_itemsHeight = newHeight;
@@ -357,7 +382,10 @@ void InnerWidget::enterEventHook(QEvent *e) {
 }
 
 void InnerWidget::leaveEventHook(QEvent *e) {
-	repaintItem(base::take(_itemOver));
+	if (auto item = App::hoveredItem()) {
+		repaintItem(item);
+		App::hoveredItem(nullptr);
+	}
 	ClickHandler::clearActive();
 	Ui::Tooltip::Hide();
 	if (!ClickHandler::getPressed() && _cursor != style::cur_default) {
@@ -372,14 +400,14 @@ void InnerWidget::mouseActionStart(const QPoint &screenPos, Qt::MouseButton butt
 	if (button != Qt::LeftButton) return;
 
 	ClickHandler::pressed();
-	if (_itemPressed != _itemOver) {
-		repaintItem(_itemPressed);
-		_itemPressed = _itemOver;
-		repaintItem(_itemPressed);
+	if (App::pressedItem() != App::hoveredItem()) {
+		repaintItem(App::pressedItem());
+		App::pressedItem(App::hoveredItem());
+		repaintItem(App::pressedItem());
 	}
 
 	_mouseAction = MouseAction::None;
-	_mouseActionItem = _itemNearest;
+	_mouseActionItem = App::mousedItem();
 	_dragStartPosition = mapPointToItem(mapFromGlobal(screenPos), _mouseActionItem);
 	_pressWasInactive = _controller->window()->wasInactivePress();
 	if (_pressWasInactive) _controller->window()->setInactivePress(false);
@@ -392,7 +420,7 @@ void InnerWidget::mouseActionStart(const QPoint &screenPos, Qt::MouseButton butt
 		if (_trippleClickTimer.isActive() && (screenPos - _trippleClickPoint).manhattanLength() < QApplication::startDragDistance()) {
 			HistoryStateRequest request;
 			request.flags = Text::StateRequest::Flag::LookupSymbol;
-			dragState = _mouseActionItem->getState(_dragStartPosition, request).data;
+			dragState = _mouseActionItem->getState(_dragStartPosition, request);
 			if (dragState.cursor == HistoryInTextCursorState) {
 				auto selection = TextSelection { dragState.symbol, dragState.symbol };
 				repaintItem(std::exchange(_selectedItem, _mouseActionItem));
@@ -403,13 +431,13 @@ void InnerWidget::mouseActionStart(const QPoint &screenPos, Qt::MouseButton butt
 				mouseActionUpdate(_mousePosition);
 				_trippleClickTimer.callOnce(QApplication::doubleClickInterval());
 			}
-		} else if (_itemPressed) {
+		} else if (App::pressedItem()) {
 			HistoryStateRequest request;
 			request.flags = Text::StateRequest::Flag::LookupSymbol;
-			dragState = _mouseActionItem->getState(_dragStartPosition, request).data;
+			dragState = _mouseActionItem->getState(_dragStartPosition, request);
 		}
 		if (_mouseSelectType != TextSelectType::Paragraphs) {
-			if (_itemPressed) {
+			if (App::pressedItem()) {
 				_mouseTextSymbol = dragState.symbol;
 				auto uponSelected = (dragState.cursor == HistoryInTextCursorState);
 				if (uponSelected) {
@@ -422,16 +450,16 @@ void InnerWidget::mouseActionStart(const QPoint &screenPos, Qt::MouseButton butt
 				if (uponSelected) {
 					_mouseAction = MouseAction::PrepareDrag; // start text drag
 				} else if (!_pressWasInactive) {
-					//if (dynamic_cast<HistorySticker*>(_itemPressed->getMedia())) {
-					//	_mouseAction = MouseAction::PrepareDrag; // start sticker drag or by-date drag
-					//} else { // TODO
+					if (dynamic_cast<HistorySticker*>(App::pressedItem()->getMedia())) {
+						_mouseAction = MouseAction::PrepareDrag; // start sticker drag or by-date drag
+					} else {
 						if (dragState.afterSymbol) ++_mouseTextSymbol;
 						auto selection = TextSelection { _mouseTextSymbol, _mouseTextSymbol };
 						repaintItem(std::exchange(_selectedItem, _mouseActionItem));
 						_selectedText = selection;
 						_mouseAction = MouseAction::Selecting;
 						repaintItem(_mouseActionItem);
-					//} // TODO
+					}
 				}
 			}
 		}
@@ -464,7 +492,10 @@ void InnerWidget::mouseActionFinish(const QPoint &screenPos, Qt::MouseButton but
 	if (_mouseAction == MouseAction::Dragging) {
 		activated.clear();
 	}
-	repaintItem(base::take(_itemPressed));
+	if (App::pressedItem()) {
+		repaintItem(App::pressedItem());
+		App::pressedItem(nullptr);
+	}
 
 	_wasSelectedText = false;
 
@@ -504,26 +535,27 @@ void InnerWidget::updateSelected() {
 	auto from = (point.y() >= _itemsTop && point.y() < _itemsTop + _itemsHeight) ? std::upper_bound(start, end, point.y(), [this](int top, auto &elem) {
 		return top <= itemTop(elem.get()) + elem->height();
 	}) : end;
-	if (from != end) {
-		_itemNearest = from->get();
-		itemPoint = mapPointToItem(point, _itemNearest);
-		if (_itemNearest->hasPoint(itemPoint)) {
-			if (_itemOver != _itemNearest) {
-				repaintItem(std::exchange(_itemOver, _itemNearest));
-				repaintItem(_itemNearest);
+	auto item = (from != end) ? from->get() : nullptr;
+	if (item) {
+		App::mousedItem(item);
+		itemPoint = mapPointToItem(point, item);
+		if (item->hasPoint(itemPoint)) {
+			if (App::hoveredItem() != item) {
+				repaintItem(App::hoveredItem());
+				App::hoveredItem(item);
+				repaintItem(App::hoveredItem());
 			}
-		} else {
-			repaintItem(base::take(_itemOver));
+		} else if (App::hoveredItem()) {
+			repaintItem(App::hoveredItem());
+			App::hoveredItem(nullptr);
 		}
-	} else {
-		_itemNearest = nullptr;
 	}
 
-	Item::TextState dragState;
+	HistoryTextState dragState;
 	ClickHandlerHost *lnkhost = nullptr;
-	auto selectingText = (_itemNearest == _mouseActionItem && _itemNearest == _itemOver && _selectedItem);
-	if (_itemNearest) {
-		if (_itemNearest != _mouseActionItem || (itemPoint - _dragStartPosition).manhattanLength() >= QApplication::startDragDistance()) {
+	auto selectingText = (item == _mouseActionItem && item == App::hoveredItem() && _selectedItem);
+	if (item) {
+		if (item != _mouseActionItem || (itemPoint - _dragStartPosition).manhattanLength() >= QApplication::startDragDistance()) {
 			if (_mouseAction == MouseAction::PrepareDrag) {
 				_mouseAction = MouseAction::Dragging;
 				InvokeQueued(this, [this] { performDrag(); });
@@ -579,60 +611,60 @@ void InnerWidget::updateSelected() {
 		//	}
 		//	return true;
 		//}); // TODO
-		if (!dragState.data.link) {
+		if (!dragState.link) {
 			HistoryStateRequest request;
 			if (_mouseAction == MouseAction::Selecting) {
 				request.flags |= Text::StateRequest::Flag::LookupSymbol;
 			} else {
 				selectingText = false;
 			}
-			dragState = _itemNearest->getState(itemPoint, request);
-			lnkhost = dragState.handler;
-			if (!dragState.data.link && itemPoint.x() >= st::historyPhotoLeft && itemPoint.x() < st::historyPhotoLeft + st::msgPhotoSize) {
-				//if (auto msg = item->toHistoryMessage()) {
-				//	if (msg->hasFromPhoto()) {
-				//		enumerateUserpics([&dragState, &lnkhost, &point](HistoryMessage *message, int userpicTop) -> bool {
-				//			// stop enumeration if the userpic is below our point
-				//			if (userpicTop > point.y()) {
-				//				return false;
-				//			}
+			dragState = item->getState(itemPoint, request);
+			lnkhost = item;
+			if (!dragState.link && itemPoint.x() >= st::historyPhotoLeft && itemPoint.x() < st::historyPhotoLeft + st::msgPhotoSize) {
+				if (auto msg = item->toHistoryMessage()) {
+					if (msg->hasFromPhoto()) {
+						//enumerateUserpics([&dragState, &lnkhost, &point](HistoryMessage *message, int userpicTop) -> bool {
+						//	// stop enumeration if the userpic is below our point
+						//	if (userpicTop > point.y()) {
+						//		return false;
+						//	}
 
-				//			// stop enumeration if we've found a userpic under the cursor
-				//			if (point.y() >= userpicTop && point.y() < userpicTop + st::msgPhotoSize) {
-				//				dragState.link = message->from()->openLink();
-				//				lnkhost = message;
-				//				return false;
-				//			}
-				//			return true;
-				//		});
-				//	}
-				//} // TODO
+						//	// stop enumeration if we've found a userpic under the cursor
+						//	if (point.y() >= userpicTop && point.y() < userpicTop + st::msgPhotoSize) {
+						//		dragState.link = message->from()->openLink();
+						//		lnkhost = message;
+						//		return false;
+						//	}
+						//	return true;
+						//}); // TODO
+					}
+				}
 			}
 		}
 	}
-	auto lnkChanged = ClickHandler::setActive(dragState.data.link, lnkhost);
-	if (lnkChanged || dragState.data.cursor != _mouseCursorState) {
+	auto lnkChanged = ClickHandler::setActive(dragState.link, lnkhost);
+	if (lnkChanged || dragState.cursor != _mouseCursorState) {
 		Ui::Tooltip::Hide();
 	}
-	if (dragState.data.link || dragState.data.cursor == HistoryInDateCursorState || dragState.data.cursor == HistoryInForwardedCursorState) {
+	if (dragState.link || dragState.cursor == HistoryInDateCursorState || dragState.cursor == HistoryInForwardedCursorState) {
 		Ui::Tooltip::Show(1000, this);
 	}
 
 	auto cursor = style::cur_default;
 	if (_mouseAction == MouseAction::None) {
-		_mouseCursorState = dragState.data.cursor;
-		if (dragState.data.link) {
+		_mouseCursorState = dragState.cursor;
+		if (dragState.link) {
 			cursor = style::cur_pointer;
 		} else if (_mouseCursorState == HistoryInTextCursorState) {
 			cursor = style::cur_text;
 		} else if (_mouseCursorState == HistoryInDateCursorState) {
 //			cursor = style::cur_cross;
 		}
-	} else if (_itemNearest) {
+	} else if (item) {
 		if (_mouseAction == MouseAction::Selecting) {
 			if (selectingText) {
-				auto second = dragState.data.symbol;
-				if (dragState.data.afterSymbol && _mouseSelectType == TextSelectType::Letters) {
+				auto second = dragState.symbol;
+				if (dragState.afterSymbol && _mouseSelectType == TextSelectType::Letters) {
 					++second;
 				}
 				auto selection = _mouseActionItem->adjustSelection({ qMin(second, _mouseTextSymbol), qMax(second, _mouseTextSymbol) }, _mouseSelectType);
@@ -656,9 +688,13 @@ void InnerWidget::updateSelected() {
 	}
 
 	// Voice message seek support.
-	if (_itemPressed) {
-		auto adjustedPoint = mapPointToItem(point, _itemPressed);
-		_itemPressed->updatePressed(adjustedPoint);
+	if (auto pressedItem = App::pressedLinkItem()) {
+		if (!pressedItem->detached()) {
+			if (pressedItem->history() == _history) {
+				auto adjustedPoint = mapPointToItem(point, pressedItem);
+				pressedItem->updatePressed(adjustedPoint);
+			}
+		}
 	}
 
 	//if (_mouseAction == MouseAction::Selecting) {
@@ -763,24 +799,31 @@ void InnerWidget::performDrag() {
 	//} // TODO
 }
 
-int InnerWidget::itemTop(gsl::not_null<Item*> item) const {
-	return _itemsTop + item->top();
+int InnerWidget::itemTop(gsl::not_null<const HistoryItem*> item) const {
+	return _itemsTop + item->y();
 }
 
-void InnerWidget::repaintItem(Item *item) {
+void InnerWidget::repaintItem(const HistoryItem *item) {
 	if (!item) {
 		return;
 	}
 	update(0, itemTop(item), width(), item->height());
 }
 
-QPoint InnerWidget::mapPointToItem(QPoint point, Item *item) const {
+QPoint InnerWidget::mapPointToItem(QPoint point, const HistoryItem *item) const {
 	if (!item) {
 		return QPoint();
 	}
 	return point - QPoint(0, itemTop(item));
 }
 
+void InnerWidget::handlePendingHistoryResize() {
+	if (_history->hasPendingResizedItems()) {
+		_history->resizeGetHeight(width());
+		updateSize();
+	}
+}
+
 InnerWidget::~InnerWidget() = default;
 
 } // namespace AdminLog
diff --git a/Telegram/SourceFiles/history/history_admin_log_inner.h b/Telegram/SourceFiles/history/history_admin_log_inner.h
index d93b76d1d..eb4fe4ed0 100644
--- a/Telegram/SourceFiles/history/history_admin_log_inner.h
+++ b/Telegram/SourceFiles/history/history_admin_log_inner.h
@@ -20,6 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 */
 #pragma once
 
+#include "history/history_admin_log_item.h"
 #include "ui/widgets/tooltip.h"
 #include "mtproto/sender.h"
 #include "base/timer.h"
@@ -35,7 +36,6 @@ class Controller;
 namespace AdminLog {
 
 class SectionMemento;
-class Item;
 
 class LocalIdManager {
 public:
@@ -118,9 +118,10 @@ private:
 	void mouseActionCancel();
 	void updateSelected();
 	void performDrag();
-	int itemTop(gsl::not_null<Item*> item) const;
-	void repaintItem(Item *item);
-	QPoint mapPointToItem(QPoint point, Item *item) const;
+	int itemTop(gsl::not_null<const HistoryItem*> item) const;
+	void repaintItem(const HistoryItem *item);
+	QPoint mapPointToItem(QPoint point, const HistoryItem *item) const;
+	void handlePendingHistoryResize();
 
 	void checkPreloadMore();
 	void updateVisibleTopItem();
@@ -142,8 +143,8 @@ private:
 	gsl::not_null<History*> _history;
 	base::lambda<void()> _cancelledCallback;
 	base::lambda<void(int top)> _scrollTo;
-	std::vector<std::unique_ptr<Item>> _items;
-	std::map<gsl::not_null<HistoryItem*>, gsl::not_null<Item*>, std::less<>> _itemsByHistoryItems;
+	std::vector<HistoryItemOwned> _items;
+	std::map<uint64, HistoryItem*> _itemsByIds;
 	int _itemsTop = 0;
 	int _itemsHeight = 0;
 
@@ -151,14 +152,14 @@ private:
 	int _minHeight = 0;
 	int _visibleTop = 0;
 	int _visibleBottom = 0;
-	Item *_visibleTopItem = nullptr;
+	HistoryItem *_visibleTopItem = nullptr;
 	int _visibleTopFromItem = 0;
 
 	bool _scrollDateShown = false;
 	Animation _scrollDateOpacity;
 	SingleQueuedInvokation _scrollDateCheck;
 	base::Timer _scrollDateHideTimer;
-	Item *_scrollDateLastItem = nullptr;
+	HistoryItem *_scrollDateLastItem = nullptr;
 	int _scrollDateLastItemTop = 0;
 	ClickHandlerPtr _scrollDateLink;
 
@@ -174,15 +175,12 @@ private:
 	TextSelectType _mouseSelectType = TextSelectType::Letters;
 	QPoint _dragStartPosition;
 	QPoint _mousePosition;
-	Item *_mouseActionItem = nullptr;
+	HistoryItem *_mouseActionItem = nullptr;
 	HistoryCursorState _mouseCursorState = HistoryDefaultCursorState;
 	uint16 _mouseTextSymbol = 0;
 	bool _pressWasInactive = false;
 
-	Item *_itemNearest = nullptr;
-	Item *_itemOver = nullptr;
-	Item *_itemPressed = nullptr;
-	Item *_selectedItem = nullptr;
+	HistoryItem *_selectedItem = nullptr;
 	TextSelection _selectedText;
 	bool _wasSelectedText = false; // was some text selected in current drag action
 	Qt::CursorShape _cursor = style::cur_default;
diff --git a/Telegram/SourceFiles/history/history_admin_log_item.cpp b/Telegram/SourceFiles/history/history_admin_log_item.cpp
index 01a7fec65..c02cb70a8 100644
--- a/Telegram/SourceFiles/history/history_admin_log_item.cpp
+++ b/Telegram/SourceFiles/history/history_admin_log_item.cpp
@@ -20,6 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 */
 #include "history/history_admin_log_item.h"
 
+#include "history/history_admin_log_inner.h"
 #include "lang/lang_keys.h"
 #include "messenger.h"
 
@@ -233,35 +234,39 @@ TextWithEntities GenerateParticipantChangeText(gsl::not_null<ChannelData*> chann
 
 } // namespace
 
-Item::Item(gsl::not_null<History*> history, LocalIdManager &idManager, const MTPDchannelAdminLogEvent &event)
-: _id(event.vid.v)
-, _date(::date(event.vdate))
-, _history(history)
-, _from(App::user(event.vuser_id.v)) {
+void GenerateItems(gsl::not_null<History*> history, LocalIdManager &idManager, const MTPDchannelAdminLogEvent &event, base::lambda<void(HistoryItemOwned item)> callback) {
+	Expects(history->peer->isChannel());
+
+	auto id = event.vid.v;
+	auto from = App::user(event.vuser_id.v);
+	auto channel = history->peer->asChannel();
 	auto &action = event.vaction;
 	auto date = event.vdate;
+	auto addPart = [&callback](gsl::not_null<HistoryItem*> item) {
+		return callback(HistoryItemOwned(item));
+	};
 
 	using ServiceFlag = MTPDmessageService::Flag;
 	using Flag = MTPDmessage::Flag;
-	auto fromName = App::peerName(_from);
-	auto fromLink = _from->openLink();
+	auto fromName = App::peerName(from);
+	auto fromLink = peerOpenClickHandler(from);
 	auto fromLinkText = textcmdLink(1, fromName);
 
-	auto addSimpleServiceMessage = [this, &idManager, date, fromLink](const QString &text, PhotoData *photo = nullptr) {
+	auto addSimpleServiceMessage = [&](const QString &text, PhotoData *photo = nullptr) {
 		auto message = HistoryService::PreparedText { text };
 		message.links.push_back(fromLink);
-		addPart(HistoryService::create(_history, idManager.next(), ::date(date), message, 0, peerToUser(_from->id), photo));
+		addPart(HistoryService::create(history, idManager.next(), ::date(date), message, 0, peerToUser(from->id), photo));
 	};
 
-	auto createChangeTitle = [this, addSimpleServiceMessage, fromLinkText](const MTPDchannelAdminLogEventActionChangeTitle &action) {
-		auto text = (channel()->isMegagroup() ? lng_action_changed_title : lng_admin_log_changed_title_channel)(lt_from, fromLinkText, lt_title, qs(action.vnew_value));
+	auto createChangeTitle = [&](const MTPDchannelAdminLogEventActionChangeTitle &action) {
+		auto text = (channel->isMegagroup() ? lng_action_changed_title : lng_admin_log_changed_title_channel)(lt_from, fromLinkText, lt_title, qs(action.vnew_value));
 		addSimpleServiceMessage(text);
 	};
 
-	auto createChangeAbout = [this, &idManager, date, addSimpleServiceMessage, fromLinkText](const MTPDchannelAdminLogEventActionChangeAbout &action) {
+	auto createChangeAbout = [&](const MTPDchannelAdminLogEventActionChangeAbout &action) {
 		auto newValue = qs(action.vnew_value);
 		auto oldValue = qs(action.vprev_value);
-		auto text = (channel()->isMegagroup()
+		auto text = (channel->isMegagroup()
 			? (newValue.isEmpty() ? lng_admin_log_removed_description_group : lng_admin_log_changed_description_group)
 			: (newValue.isEmpty() ? lng_admin_log_removed_description_channel : lng_admin_log_changed_description_channel)
 			)(lt_from, fromLinkText);
@@ -271,18 +276,18 @@ Item::Item(gsl::not_null<History*> history, LocalIdManager &idManager, const MTP
 		auto bodyReplyTo = 0;
 		auto bodyViaBotId = 0;
 		auto newDescription = PrepareText(newValue, QString());
-		auto body = HistoryMessage::create(_history, idManager.next(), bodyFlags, bodyReplyTo, bodyViaBotId, ::date(date), peerToUser(_from->id), newDescription);
+		auto body = HistoryMessage::create(history, idManager.next(), bodyFlags, bodyReplyTo, bodyViaBotId, ::date(date), peerToUser(from->id), newDescription);
 		if (!oldValue.isEmpty()) {
 			auto oldDescription = PrepareText(oldValue, QString());
-			body->addLogEntryOriginal(_id, lang(lng_admin_log_previous_description), oldDescription);
+			body->addLogEntryOriginal(id, lang(lng_admin_log_previous_description), oldDescription);
 		}
 		addPart(body);
 	};
 
-	auto createChangeUsername = [this, &idManager, date, addSimpleServiceMessage, fromLinkText](const MTPDchannelAdminLogEventActionChangeUsername &action) {
+	auto createChangeUsername = [&](const MTPDchannelAdminLogEventActionChangeUsername &action) {
 		auto newValue = qs(action.vnew_value);
 		auto oldValue = qs(action.vprev_value);
-		auto text = (channel()->isMegagroup()
+		auto text = (channel->isMegagroup()
 			? (newValue.isEmpty() ? lng_admin_log_removed_link_group : lng_admin_log_changed_link_group)
 			: (newValue.isEmpty() ? lng_admin_log_removed_link_channel : lng_admin_log_changed_link_channel)
 			)(lt_from, fromLinkText);
@@ -292,44 +297,44 @@ Item::Item(gsl::not_null<History*> history, LocalIdManager &idManager, const MTP
 		auto bodyReplyTo = 0;
 		auto bodyViaBotId = 0;
 		auto newLink = newValue.isEmpty() ? TextWithEntities() : PrepareText(Messenger::Instance().createInternalLinkFull(newValue), QString());
-		auto body = HistoryMessage::create(_history, idManager.next(), bodyFlags, bodyReplyTo, bodyViaBotId, ::date(date), peerToUser(_from->id), newLink);
+		auto body = HistoryMessage::create(history, idManager.next(), bodyFlags, bodyReplyTo, bodyViaBotId, ::date(date), peerToUser(from->id), newLink);
 		if (!oldValue.isEmpty()) {
 			auto oldLink = PrepareText(Messenger::Instance().createInternalLinkFull(oldValue), QString());
-			body->addLogEntryOriginal(_id, lang(lng_admin_log_previous_link), oldLink);
+			body->addLogEntryOriginal(id, lang(lng_admin_log_previous_link), oldLink);
 		}
 		addPart(body);
 	};
 
-	auto createChangePhoto = [this, date, addSimpleServiceMessage, fromLinkText](const MTPDchannelAdminLogEventActionChangePhoto &action) {
+	auto createChangePhoto = [&](const MTPDchannelAdminLogEventActionChangePhoto &action) {
 		t_assert(action.vnew_photo.type() == mtpc_chatPhoto);
-		auto photo = GenerateChatPhoto(channel()->bareId(), _id, date, action.vnew_photo.c_chatPhoto());
+		auto photo = GenerateChatPhoto(channel->bareId(), id, date, action.vnew_photo.c_chatPhoto());
 
-		auto text = (channel()->isMegagroup() ? lng_admin_log_changed_photo_group : lng_admin_log_changed_photo_channel)(lt_from, fromLinkText);
+		auto text = (channel->isMegagroup() ? lng_admin_log_changed_photo_group : lng_admin_log_changed_photo_channel)(lt_from, fromLinkText);
 		addSimpleServiceMessage(text, photo);
 	};
 
-	auto createToggleInvites = [this, addSimpleServiceMessage, fromLinkText](const MTPDchannelAdminLogEventActionToggleInvites &action) {
+	auto createToggleInvites = [&](const MTPDchannelAdminLogEventActionToggleInvites &action) {
 		auto enabled = (action.vnew_value.type() == mtpc_boolTrue);
 		auto text = (enabled ? lng_admin_log_invites_enabled : lng_admin_log_invites_disabled)(lt_from, fromLinkText);
 		addSimpleServiceMessage(text);
 	};
 
-	auto createToggleSignatures = [this, addSimpleServiceMessage, fromLinkText](const MTPDchannelAdminLogEventActionToggleSignatures &action) {
+	auto createToggleSignatures = [&](const MTPDchannelAdminLogEventActionToggleSignatures &action) {
 		auto enabled = (action.vnew_value.type() == mtpc_boolTrue);
 		auto text = (enabled ? lng_admin_log_signatures_enabled : lng_admin_log_signatures_disabled)(lt_from, fromLinkText);
 		addSimpleServiceMessage(text);
 	};
 
-	auto createUpdatePinned = [this, &idManager, date, addSimpleServiceMessage, fromLinkText](const MTPDchannelAdminLogEventActionUpdatePinned &action) {
+	auto createUpdatePinned = [&](const MTPDchannelAdminLogEventActionUpdatePinned &action) {
 		auto text = lng_admin_log_pinned_message(lt_from, fromLinkText);
 		addSimpleServiceMessage(text);
 
 		auto applyServiceAction = false;
 		auto detachExistingItem = false;
-		addPart(_history->createItem(PrepareLogMessage(action.vmessage, idManager.next(), date.v), applyServiceAction, detachExistingItem));
+		addPart(history->createItem(PrepareLogMessage(action.vmessage, idManager.next(), date.v), applyServiceAction, detachExistingItem));
 	};
 
-	auto createEditMessage = [this, &idManager, date, addSimpleServiceMessage, fromLinkText](const MTPDchannelAdminLogEventActionEditMessage &action) {
+	auto createEditMessage = [&](const MTPDchannelAdminLogEventActionEditMessage &action) {
 		auto newValue = ExtractEditedText(action.vnew_message);
 		auto canHaveCaption = MediaCanHaveCaption(action.vnew_message);
 		auto text = (canHaveCaption
@@ -341,54 +346,54 @@ Item::Item(gsl::not_null<History*> history, LocalIdManager &idManager, const MTP
 		auto oldValue = ExtractEditedText(action.vprev_message);
 		auto applyServiceAction = false;
 		auto detachExistingItem = false;
-		auto body = _history->createItem(PrepareLogMessage(action.vnew_message, idManager.next(), date.v), applyServiceAction, detachExistingItem);
+		auto body = history->createItem(PrepareLogMessage(action.vnew_message, idManager.next(), date.v), applyServiceAction, detachExistingItem);
 		if (!oldValue.text.isEmpty()) {
-			body->addLogEntryOriginal(_id, lang(canHaveCaption ? lng_admin_log_previous_caption : lng_admin_log_previous_message), oldValue);
+			body->addLogEntryOriginal(id, lang(canHaveCaption ? lng_admin_log_previous_caption : lng_admin_log_previous_message), oldValue);
 		}
 		addPart(body);
 	};
 
-	auto createDeleteMessage = [this, &idManager, date, addSimpleServiceMessage, fromLinkText](const MTPDchannelAdminLogEventActionDeleteMessage &action) {
+	auto createDeleteMessage = [&](const MTPDchannelAdminLogEventActionDeleteMessage &action) {
 		auto text = lng_admin_log_deleted_message(lt_from, fromLinkText);
 		addSimpleServiceMessage(text);
 
 		auto applyServiceAction = false;
 		auto detachExistingItem = false;
-		addPart(_history->createItem(PrepareLogMessage(action.vmessage, idManager.next(), date.v), applyServiceAction, detachExistingItem));
+		addPart(history->createItem(PrepareLogMessage(action.vmessage, idManager.next(), date.v), applyServiceAction, detachExistingItem));
 	};
 
-	auto createParticipantJoin = [this, &idManager, addSimpleServiceMessage, fromLinkText]() {
+	auto createParticipantJoin = [&]() {
 		auto text = lng_admin_log_participant_joined(lt_from, fromLinkText);
 		addSimpleServiceMessage(text);
 	};
 
-	auto createParticipantLeave = [this, &idManager, addSimpleServiceMessage, fromLinkText]() {
+	auto createParticipantLeave = [&]() {
 		auto text = lng_admin_log_participant_left(lt_from, fromLinkText);
 		addSimpleServiceMessage(text);
 	};
 
-	auto createParticipantInvite = [this, &idManager, date](const MTPDchannelAdminLogEventActionParticipantInvite &action) {
+	auto createParticipantInvite = [&](const MTPDchannelAdminLogEventActionParticipantInvite &action) {
 		auto bodyFlags = Flag::f_entities | Flag::f_from_id;
 		auto bodyReplyTo = 0;
 		auto bodyViaBotId = 0;
-		auto bodyText = GenerateParticipantChangeText(channel(), action.vparticipant);
-		addPart(HistoryMessage::create(_history, idManager.next(), bodyFlags, bodyReplyTo, bodyViaBotId, ::date(date), peerToUser(_from->id), bodyText));
+		auto bodyText = GenerateParticipantChangeText(channel, action.vparticipant);
+		addPart(HistoryMessage::create(history, idManager.next(), bodyFlags, bodyReplyTo, bodyViaBotId, ::date(date), peerToUser(from->id), bodyText));
 	};
 
-	auto createParticipantToggleBan = [this, &idManager, date](const MTPDchannelAdminLogEventActionParticipantToggleBan &action) {
+	auto createParticipantToggleBan = [&](const MTPDchannelAdminLogEventActionParticipantToggleBan &action) {
 		auto bodyFlags = Flag::f_entities | Flag::f_from_id;
 		auto bodyReplyTo = 0;
 		auto bodyViaBotId = 0;
-		auto bodyText = GenerateParticipantChangeText(channel(), action.vnew_participant, &action.vprev_participant);
-		addPart(HistoryMessage::create(_history, idManager.next(), bodyFlags, bodyReplyTo, bodyViaBotId, ::date(date), peerToUser(_from->id), bodyText));
+		auto bodyText = GenerateParticipantChangeText(channel, action.vnew_participant, &action.vprev_participant);
+		addPart(HistoryMessage::create(history, idManager.next(), bodyFlags, bodyReplyTo, bodyViaBotId, ::date(date), peerToUser(from->id), bodyText));
 	};
 
-	auto createParticipantToggleAdmin = [this, &idManager, date](const MTPDchannelAdminLogEventActionParticipantToggleAdmin &action) {
+	auto createParticipantToggleAdmin = [&](const MTPDchannelAdminLogEventActionParticipantToggleAdmin &action) {
 		auto bodyFlags = Flag::f_entities | Flag::f_from_id;
 		auto bodyReplyTo = 0;
 		auto bodyViaBotId = 0;
-		auto bodyText = GenerateParticipantChangeText(channel(), action.vnew_participant, &action.vprev_participant);
-		addPart(HistoryMessage::create(_history, idManager.next(), bodyFlags, bodyReplyTo, bodyViaBotId, ::date(date), peerToUser(_from->id), bodyText));
+		auto bodyText = GenerateParticipantChangeText(channel, action.vnew_participant, &action.vprev_participant);
+		addPart(HistoryMessage::create(history, idManager.next(), bodyFlags, bodyReplyTo, bodyViaBotId, ::date(date), peerToUser(from->id), bodyText));
 	};
 
 	switch (action.type()) {
@@ -410,78 +415,4 @@ Item::Item(gsl::not_null<History*> history, LocalIdManager &idManager, const MTP
 	}
 }
 
-void Item::addPart(HistoryItem *item) {
-	_parts.push_back(item);
-}
-
-int Item::resizeGetHeight(int newWidth) {
-	_height = 0;
-	for (auto part : _parts) {
-		_height += part->resizeGetHeight(newWidth);
-	}
-	return _height;
-}
-
-void Item::draw(Painter &p, QRect clip, TextSelection selection, TimeMs ms) {
-	auto top = 0;
-	for (auto part : _parts) {
-		auto height = part->height();
-		if (clip.top() < top + height && clip.top() + clip.height() > top) {
-			p.translate(0, top);
-			part->draw(p, clip.translated(0, -top), selection, ms);
-			p.translate(0, -top);
-		}
-		top += height;
-	}
-}
-
-bool Item::hasPoint(QPoint point) const {
-	auto top = 0;
-	for (auto part : _parts) {
-		auto height = part->height();
-		if (point.y() >= top && point.y() < top + height) {
-			return part->hasPoint(point - QPoint(0, top));
-		}
-		top += height;
-	}
-	return false;
-}
-
-Item::TextState Item::getState(QPoint point, HistoryStateRequest request) const {
-	auto top = 0;
-	for (auto part : _parts) {
-		auto height = part->height();
-		if (point.y() >= top && point.y() < top + height) {
-			auto result = TextState();
-			result.data = part->getState(point - QPoint(0, top), request);
-			result.handler = part;
-			return result;
-		}
-		top += height;
-	}
-	return Item::TextState();
-}
-
-TextSelection Item::adjustSelection(TextSelection selection, TextSelectType type) const {
-	return selection;
-}
-
-void Item::updatePressed(QPoint point) {
-}
-
-QString Item::getForwardedInfoText() const {
-	for (auto part : _parts) {
-		if (auto forwarded = part->Get<HistoryMessageForwarded>()) {
-			return forwarded->_text.originalText(AllTextSelection, ExpandLinksNone);
-		}
-	}
-	return QString();
-}
-
-Item::~Item() {
-	for (auto part : _parts) {
-		part->destroy();
-	}
-}
-
 } // namespace AdminLog
diff --git a/Telegram/SourceFiles/history/history_admin_log_item.h b/Telegram/SourceFiles/history/history_admin_log_item.h
index 58a069bfc..cd617884d 100644
--- a/Telegram/SourceFiles/history/history_admin_log_item.h
+++ b/Telegram/SourceFiles/history/history_admin_log_item.h
@@ -20,59 +20,44 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 */
 #pragma once
 
-#include "history/history_admin_log_inner.h"
-
 namespace AdminLog {
 
-class Item {
+class HistoryItemOwned;
+class LocalIdManager;
+
+void GenerateItems(gsl::not_null<History*> history, LocalIdManager &idManager, const MTPDchannelAdminLogEvent &event, base::lambda<void(HistoryItemOwned item)> callback);
+
+// Smart pointer wrapper for HistoryItem* that destroys the owned item.
+class HistoryItemOwned {
 public:
-	struct TextState {
-		HistoryTextState data;
-		ClickHandlerHost *handler = nullptr;
-	};
-
-	Item(gsl::not_null<History*> history, LocalIdManager &idManager, const MTPDchannelAdminLogEvent &event);
-
-	uint64 id() const {
-		return _id;
+	explicit HistoryItemOwned(gsl::not_null<HistoryItem*> data) : _data(data) {
 	}
-	QDateTime date() const {
-		return _date;
+	HistoryItemOwned(const HistoryItemOwned &other) = delete;
+	HistoryItemOwned &operator=(const HistoryItemOwned &other) = delete;
+	HistoryItemOwned(HistoryItemOwned &&other) : _data(base::take(other._data)) {
 	}
-	int top() const {
-		return _top;
+	HistoryItemOwned &operator=(HistoryItemOwned &&other) {
+		_data = base::take(other._data);
+		return *this;
 	}
-	void setTop(int top) {
-		_top = top;
-	}
-	int height() const {
-		return _height;
+	~HistoryItemOwned() {
+		if (_data) {
+			_data->destroy();
+		}
 	}
 
-	int resizeGetHeight(int newWidth);
-	void draw(Painter &p, QRect clip, TextSelection selection, TimeMs ms);
-	bool hasPoint(QPoint point) const;
-	TextState getState(QPoint point, HistoryStateRequest request) const;
-	TextSelection adjustSelection(TextSelection selection, TextSelectType type) const;
-	void updatePressed(QPoint point);
-
-	QString getForwardedInfoText() const;
-
-	~Item();
+	HistoryItem *get() const {
+		return _data;
+	}
+	HistoryItem *operator->() const {
+		return get();
+	}
+	operator HistoryItem*() const {
+		return get();
+	}
 
 private:
-	gsl::not_null<ChannelData*> channel() {
-		return _history->peer->asChannel();
-	}
-	void addPart(HistoryItem *item);
-
-	uint64 _id = 0;
-	QDateTime _date;
-	gsl::not_null<History*> _history;
-	gsl::not_null<UserData*> _from;
-	std::vector<HistoryItem*> _parts;
-	int _top = 0;
-	int _height = 0;
+	HistoryItem *_data = nullptr;
 
 };
 
diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp
index 311e7249f..030bfd199 100644
--- a/Telegram/SourceFiles/history/history_inner_widget.cpp
+++ b/Telegram/SourceFiles/history/history_inner_widget.cpp
@@ -780,7 +780,7 @@ void HistoryInner::touchScrollUpdated(const QPoint &screenPos) {
 	touchUpdateSpeed();
 }
 
-QPoint HistoryInner::mapMouseToItem(QPoint p, HistoryItem *item) {
+QPoint HistoryInner::mapPointToItem(QPoint p, HistoryItem *item) {
 	int32 msgy = itemTop(item);
 	if (msgy < 0) return QPoint(0, 0);
 
@@ -809,7 +809,7 @@ void HistoryInner::mouseActionStart(const QPoint &screenPos, Qt::MouseButton but
 
 	_mouseAction = MouseAction::None;
 	_mouseActionItem = App::mousedItem();
-	_dragStartPosition = mapMouseToItem(mapFromGlobal(screenPos), _mouseActionItem);
+	_dragStartPosition = mapPointToItem(mapFromGlobal(screenPos), _mouseActionItem);
 	_pressWasInactive = _controller->window()->wasInactivePress();
 	if (_pressWasInactive) _controller->window()->setInactivePress(false);
 
@@ -1037,7 +1037,7 @@ void HistoryInner::mouseActionFinish(const QPoint &screenPos, Qt::MouseButton bu
 		// if we are in selecting items mode perhaps we want to
 		// toggle selection instead of activating the pressed link
 		if (_mouseAction == MouseAction::PrepareDrag && !_pressWasInactive && !_selected.isEmpty() && _selected.cbegin().value() == FullSelection && button != Qt::RightButton) {
-			if (HistoryMedia *media = pressed->getMedia()) {
+			if (auto media = pressed->getMedia()) {
 				if (media->toggleSelectionByHandlerClick(activated)) {
 					activated.clear();
 				}
@@ -1174,7 +1174,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
 			uint16 selFrom = _selected.cbegin().value().from, selTo = _selected.cbegin().value().to;
 			hasSelected = (selTo > selFrom) ? 1 : 0;
 			if (App::mousedItem() && App::mousedItem() == App::hoveredItem()) {
-				QPoint mousePos(mapMouseToItem(mapFromGlobal(_mousePosition), App::mousedItem()));
+				auto mousePos = mapPointToItem(mapFromGlobal(_mousePosition), App::mousedItem());
 				HistoryStateRequest request;
 				request.flags |= Text::StateRequest::Flag::LookupSymbol;
 				auto dragState = App::mousedItem()->getState(mousePos, request);
@@ -2006,7 +2006,7 @@ void HistoryInner::onUpdateSelected() {
 		item = block->items[_curItem];
 
 		App::mousedItem(item);
-		m = mapMouseToItem(point, item);
+		m = mapPointToItem(point, item);
 		if (item->hasPoint(m)) {
 			if (App::hoveredItem() != item) {
 				repaintItem(App::hoveredItem());
@@ -2208,7 +2208,7 @@ void HistoryInner::onUpdateSelected() {
 	if (auto pressedItem = App::pressedLinkItem()) {
 		if (!pressedItem->detached()) {
 			if (pressedItem->history() == _history || pressedItem->history() == _migrated) {
-				auto adjustedPoint = mapMouseToItem(point, pressedItem);
+				auto adjustedPoint = mapPointToItem(point, pressedItem);
 				pressedItem->updatePressed(adjustedPoint);
 			}
 		}
diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h
index 1374d0ffb..de11669df 100644
--- a/Telegram/SourceFiles/history/history_inner_widget.h
+++ b/Telegram/SourceFiles/history/history_inner_widget.h
@@ -45,7 +45,7 @@ public:
 	TextWithEntities getSelectedText() const;
 
 	void touchScrollUpdated(const QPoint &screenPos);
-	QPoint mapMouseToItem(QPoint p, HistoryItem *item);
+	QPoint mapPointToItem(QPoint p, HistoryItem *item);
 
 	void recountHeight();
 	void updateSize();
diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp
index f1292a114..d33a4338c 100644
--- a/Telegram/SourceFiles/history/history_item.cpp
+++ b/Telegram/SourceFiles/history/history_item.cpp
@@ -724,34 +724,31 @@ void HistoryItem::detachFast() {
 }
 
 void HistoryItem::previousItemChanged() {
+	Expects(!isLogEntry());
 	recountDisplayDate();
 	recountAttachToPrevious();
 }
 
 // Called only if there is no more next item! Not always when it changes!
 void HistoryItem::nextItemChanged() {
+	Expects(!isLogEntry());
 	setAttachToNext(false);
 }
 
 void HistoryItem::recountAttachToPrevious() {
-	bool attach = false;
+	Expects(!isLogEntry());
+	auto attachToPrevious = false;
 	if (auto previous = previousItem()) {
 		if (!Has<HistoryMessageDate>() && !Has<HistoryMessageUnreadBar>()) {
-			attach = !isPost() && !previous->isPost()
+			attachToPrevious = !isPost() && !previous->isPost()
 				&& !serviceMsg() && !previous->serviceMsg()
 				&& !isEmpty() && !previous->isEmpty()
 				&& previous->from() == from()
 				&& (qAbs(previous->date.secsTo(date)) < kAttachMessageToPreviousSecondsDelta);
 		}
-		previous->setAttachToNext(attach);
-	}
-	if (attach && !(_flags & MTPDmessage_ClientFlag::f_attach_to_previous)) {
-		_flags |= MTPDmessage_ClientFlag::f_attach_to_previous;
-		setPendingInitDimensions();
-	} else if (!attach && (_flags & MTPDmessage_ClientFlag::f_attach_to_previous)) {
-		_flags &= ~MTPDmessage_ClientFlag::f_attach_to_previous;
-		setPendingInitDimensions();
+		previous->setAttachToNext(attachToPrevious);
 	}
+	setAttachToPrevious(attachToPrevious);
 }
 
 void HistoryItem::setAttachToNext(bool attachToNext) {
@@ -764,6 +761,16 @@ void HistoryItem::setAttachToNext(bool attachToNext) {
 	}
 }
 
+void HistoryItem::setAttachToPrevious(bool attachToPrevious) {
+	if (attachToPrevious && !(_flags & MTPDmessage_ClientFlag::f_attach_to_previous)) {
+		_flags |= MTPDmessage_ClientFlag::f_attach_to_previous;
+		setPendingInitDimensions();
+	} else if (!attachToPrevious && (_flags & MTPDmessage_ClientFlag::f_attach_to_previous)) {
+		_flags &= ~MTPDmessage_ClientFlag::f_attach_to_previous;
+		setPendingInitDimensions();
+	}
+}
+
 void HistoryItem::setId(MsgId newId) {
 	history()->changeMsgId(id, newId);
 	id = newId;
@@ -954,6 +961,8 @@ bool HistoryItem::unread() const {
 
 void HistoryItem::destroyUnreadBar() {
 	if (Has<HistoryMessageUnreadBar>()) {
+		t_assert(!isLogEntry());
+
 		RemoveComponents(HistoryMessageUnreadBar::Bit());
 		setPendingInitDimensions();
 		if (_history->unreadBar == this) {
@@ -965,6 +974,7 @@ void HistoryItem::destroyUnreadBar() {
 }
 
 void HistoryItem::setUnreadBarCount(int count) {
+	Expects(!isLogEntry());
 	if (count > 0) {
 		HistoryMessageUnreadBar *bar;
 		if (!Has<HistoryMessageUnreadBar>()) {
@@ -988,6 +998,7 @@ void HistoryItem::setUnreadBarCount(int count) {
 }
 
 void HistoryItem::setUnreadBarFreezed() {
+	Expects(!isLogEntry());
 	if (auto bar = Get<HistoryMessageUnreadBar>()) {
 		bar->_freezed = true;
 	}
@@ -1010,14 +1021,14 @@ void HistoryItem::clipCallback(Media::Clip::Notification notification) {
 	case NotificationReinit: {
 		auto stopped = false;
 		if (reader->autoPausedGif()) {
-			if (auto m = App::main()) {
-				if (!m->isItemVisible(this)) { // stop animation if it is not visible
-					media->stopInline();
-					if (auto document = media->getDocument()) { // forget data from memory
-						document->forget();
-					}
-					stopped = true;
+			auto amVisible = false;
+			AuthSession::Current().data().queryItemVisibility().notify({ this, &amVisible }, true);
+			if (!amVisible) { // stop animation if it is not visible
+				media->stopInline();
+				if (auto document = media->getDocument()) { // forget data from memory
+					document->forget();
 				}
+				stopped = true;
 			}
 		} else if (reader->mode() == Media::Clip::Reader::Mode::Video && reader->state() == Media::Clip::State::Finished) {
 			// Stop finished video message.
@@ -1065,7 +1076,8 @@ void HistoryItem::audioTrackUpdated() {
 }
 
 void HistoryItem::recountDisplayDate() {
-	bool displayingDate = ([this]() {
+	Expects(!isLogEntry());
+	setDisplayDate(([this]() {
 		if (isEmpty()) {
 			return false;
 		}
@@ -1074,13 +1086,15 @@ void HistoryItem::recountDisplayDate() {
 			return previous->isEmpty() || (previous->date.date() != date.date());
 		}
 		return true;
-	})();
+	})());
+}
 
-	if (displayingDate && !Has<HistoryMessageDate>()) {
+void HistoryItem::setDisplayDate(bool displayDate) {
+	if (displayDate && !Has<HistoryMessageDate>()) {
 		AddComponents(HistoryMessageDate::Bit());
 		Get<HistoryMessageDate>()->init(date);
 		setPendingInitDimensions();
-	} else if (!displayingDate && Has<HistoryMessageDate>()) {
+	} else if (!displayDate && Has<HistoryMessageDate>()) {
 		RemoveComponents(HistoryMessageDate::Bit());
 		setPendingInitDimensions();
 	}
diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h
index 21c9947e7..aff016c1f 100644
--- a/Telegram/SourceFiles/history/history_item.h
+++ b/Telegram/SourceFiles/history/history_item.h
@@ -542,6 +542,7 @@ public:
 		return !_block;
 	}
 	void attachToBlock(HistoryBlock *block, int index) {
+		Expects(!isLogEntry());
 		Expects(_block == nullptr);
 		Expects(_indexInBlock < 0);
 		Expects(block != nullptr);
@@ -820,7 +821,7 @@ public:
 	}
 	void setPendingResize() {
 		_flags |= MTPDmessage_ClientFlag::f_pending_resize;
-		if (!detached()) {
+		if (!detached() || isLogEntry()) {
 			_history->setHasPendingResizedItems();
 		}
 	}
@@ -879,6 +880,19 @@ public:
 	void clipCallback(Media::Clip::Notification notification);
 	void audioTrackUpdated();
 
+	void setLogEntryDisplayDate(bool displayDate) {
+		Expects(isLogEntry());
+		setDisplayDate(displayDate);
+	}
+	void setLogEntryAttachToPrevious(bool attachToPrevious) {
+		Expects(isLogEntry());
+		setAttachToNext(attachToPrevious);
+	}
+	void setLogEntryAttachToNext(bool attachToNext) {
+		Expects(isLogEntry());
+		setAttachToNext(attachToNext);
+	}
+
 	~HistoryItem();
 
 protected:
@@ -929,18 +943,27 @@ protected:
 		return nullptr;
 	}
 
-	// this should be called only from previousItemChanged()
+	// This should be called only from previousItemChanged()
 	// to add required bits to the Composer mask
-	// after that always use Has<HistoryMessageDate>()
+	// after that always use Has<HistoryMessageDate>().
 	void recountDisplayDate();
 
-	// this should be called only from previousItemChanged() or when
+	// This should be called only from previousItemChanged() or when
 	// HistoryMessageDate or HistoryMessageUnreadBar bit is changed in the Composer mask
-	// then the result should be cached in a client side flag MTPDmessage_ClientFlag::f_attach_to_previous
+	// then the result should be cached in a client side flag MTPDmessage_ClientFlag::f_attach_to_previous.
 	void recountAttachToPrevious();
 
-	// this should be called only recountAttachToPrevious() of the next item
-	// or when the next item is removed through nextItemChanged() call
+	// This should be called only from recountDisplayDate().
+	// Also this is called from setLogEntryDisplayDate() for channel log entries.
+	void setDisplayDate(bool displayDate);
+
+	// This should be called only from recountAttachToPrevious().
+	// Also this is called from setLogEntryAttachToPrevious() for channel log entries.
+	void setAttachToPrevious(bool attachToNext);
+
+	// This should be called only from recountAttachToPrevious() of the next item
+	// or when the next item is removed through nextItemChanged() call.
+	// Also this is called from setLogEntryAttachToNext() for channel log entries.
 	void setAttachToNext(bool attachToNext);
 
 	const HistoryMessageReplyMarkup *inlineReplyMarkup() const {
diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp
index cd98a5e66..0cc7c018c 100644
--- a/Telegram/SourceFiles/historywidget.cpp
+++ b/Telegram/SourceFiles/historywidget.cpp
@@ -681,6 +681,19 @@ HistoryWidget::HistoryWidget(QWidget *parent, gsl::not_null<Window::Controller*>
 		// So we force HistoryWidget::resizeEvent() here, without WA_UpdatesDisabled.
 		myEnsureResized(this);
 	});
+	subscribe(AuthSession::Current().data().pendingHistoryResize(), [this] { handlePendingHistoryUpdate(); });
+	subscribe(AuthSession::Current().data().queryItemVisibility(), [this](const AuthSessionData::ItemVisibilityQuery &query) {
+		if (_a_show.animating() || _history != query.item->history() || query.item->detached() || !isVisible()) {
+			return;
+		}
+		auto top = _list->itemTop(query.item);
+		if (top >= 0) {
+			auto scrollTop = _scroll->scrollTop();
+			if (top + query.item->height() > scrollTop && top < scrollTop + _scroll->height()) {
+				*query.isVisible = true;
+			}
+		}
+	});
 
 	orderWidgets();
 }
@@ -701,7 +714,7 @@ void HistoryWidget::scrollToCurrentVoiceMessage(FullMsgId fromId, FullMsgId toId
 
 	// If history has pending resize items, the scrollTopItem won't be updated.
 	// And the scrollTop will be reset back to scrollTopItem + scrollTopOffset.
-	notify_handlePendingHistoryUpdate();
+	handlePendingHistoryUpdate();
 
 	auto toTop = _list->itemTop(to);
 	if (toTop >= 0 && !isItemCompletelyHidden(from)) {
@@ -4782,17 +4795,6 @@ void HistoryWidget::grabFinish() {
 	_topShadow->show();
 }
 
-bool HistoryWidget::isItemVisible(HistoryItem *item) {
-	if (isHidden() || _a_show.animating() || !_list) {
-		return false;
-	}
-	int32 top = _list->itemTop(item), st = _scroll->scrollTop();
-	if (top < 0 || top + item->height() <= st || top >= st + _scroll->height()) {
-		return false;
-	}
-	return true;
-}
-
 void HistoryWidget::ui_repaintHistoryItem(gsl::not_null<const HistoryItem*> item) {
 	if (_peer && _list && (item->history() == _history || (_migrated && item->history() == _migrated))) {
 		auto ms = getms();
@@ -4825,7 +4827,7 @@ void HistoryWidget::notify_historyItemLayoutChanged(const HistoryItem *item) {
 	}
 }
 
-void HistoryWidget::notify_handlePendingHistoryUpdate() {
+void HistoryWidget::handlePendingHistoryUpdate() {
 	if (hasPendingResizedItems() || _updateHistoryGeometryRequired) {
 		if (_list) {
 			updateHistoryGeometry();
diff --git a/Telegram/SourceFiles/historywidget.h b/Telegram/SourceFiles/historywidget.h
index d5bdc8851..46cfa0db7 100644
--- a/Telegram/SourceFiles/historywidget.h
+++ b/Telegram/SourceFiles/historywidget.h
@@ -356,7 +356,6 @@ public:
 	bool notify_switchInlineBotButtonReceived(const QString &query, UserData *samePeerBot, MsgId samePeerReplyTo);
 	void notify_userIsBotChanged(UserData *user);
 	void notify_migrateUpdated(PeerData *peer);
-	void notify_handlePendingHistoryUpdate();
 
 	bool cmd_search();
 	bool cmd_next_chat();
@@ -484,6 +483,7 @@ private:
 		bool allFilesForCompress = true;
 	};
 
+	void handlePendingHistoryUpdate();
 	void fullPeerUpdated(PeerData *peer);
 	void topBarClick();
 	void toggleTabbedSelectorMode();
diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp
index 693adcfe6..07ad694bf 100644
--- a/Telegram/SourceFiles/mainwidget.cpp
+++ b/Telegram/SourceFiles/mainwidget.cpp
@@ -819,13 +819,6 @@ void MainWidget::onFilesOrForwardDrop(const PeerId &peerId, const QMimeData *dat
 	}
 }
 
-bool MainWidget::isItemVisible(HistoryItem *item) {
-	if (isHidden() || _a_show.animating()) {
-		return false;
-	}
-	return _history->isItemVisible(item);
-}
-
 void MainWidget::notify_botCommandsChanged(UserData *bot) {
 	_history->notify_botCommandsChanged(bot);
 }
@@ -901,10 +894,6 @@ void MainWidget::notify_historyMuteUpdated(History *history) {
 	_dialogs->notify_historyMuteUpdated(history);
 }
 
-void MainWidget::notify_handlePendingHistoryUpdate() {
-	_history->notify_handlePendingHistoryUpdate();
-}
-
 bool MainWidget::cmd_search() {
 	if (Ui::isLayerShown() || Ui::isMediaViewShown()) return false;
 	return _history->cmd_search();
diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h
index a2364f9af..e594fc499 100644
--- a/Telegram/SourceFiles/mainwidget.h
+++ b/Telegram/SourceFiles/mainwidget.h
@@ -378,8 +378,6 @@ public:
 
 	bool contentOverlapped(const QRect &globalRect);
 
-	bool isItemVisible(HistoryItem *item);
-
 	void documentLoadProgress(DocumentData *document);
 
 	void app_sendBotCallback(const HistoryMessageReplyMarkup::Button *button, const HistoryItem *msg, int row, int col);
@@ -398,7 +396,6 @@ public:
 	void notify_migrateUpdated(PeerData *peer);
 	void notify_historyItemLayoutChanged(const HistoryItem *item);
 	void notify_historyMuteUpdated(History *history);
-	void notify_handlePendingHistoryUpdate();
 
 	bool cmd_search();
 	bool cmd_next_chat();