From 972b5078f670588fdf638d2c913bbf1a34f40631 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Wed, 24 May 2017 16:22:27 +0300
Subject: [PATCH] Improve animated scroll to message.

Use it in replies navigation and in jumping to message history end.
---
 Telegram/SourceFiles/historywidget.cpp | 110 +++++++++++++++++++------
 Telegram/SourceFiles/historywidget.h   |   2 +
 2 files changed, 86 insertions(+), 26 deletions(-)

diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp
index a7861431f..407d91ce1 100644
--- a/Telegram/SourceFiles/historywidget.cpp
+++ b/Telegram/SourceFiles/historywidget.cpp
@@ -668,20 +668,82 @@ void HistoryWidget::scrollToCurrentVoiceMessage(FullMsgId fromId, FullMsgId toId
 		auto scrollBottom = scrollTop + _scroll->height();
 		auto toBottom = toTop + to->height();
 		if ((toTop < scrollTop && toBottom < scrollBottom) || (toTop > scrollTop && toBottom > scrollBottom)) {
-			auto scrollTo = snap(itemTopForHighlight(to), 0, _scroll->scrollTopMax());
-			_scrollToMediaMessageAnimation.finish();
-			_scrollToMediaMessageAnimation.start([this, toId] {
-				auto toTop = _list->itemTop(App::histItemById(toId));
-				if (toTop < 0) {
-					_scrollToMediaMessageAnimation.finish();
-				} else {
-					synteticScrollToY(qRound(_scrollToMediaMessageAnimation.current()) + toTop);
-				}
-			}, scrollTop - toTop, scrollTo - toTop, 200, anim::sineInOut);
+			animatedScrollToItem(to->id);
 		}
 	}
 }
 
+void HistoryWidget::animatedScrollToItem(MsgId msgId) {
+	Expects(_history != nullptr);
+
+	auto animatedScrollAttachedToItem = [this](HistoryItem *item, int scrollTo) {
+		auto itemTop = _list->itemTop(item);
+		if (itemTop < 0) {
+			return false;
+		}
+
+		auto maxAnimatedDelta = _scroll->height();
+		auto transition = anim::sineInOut;
+		auto scrollTop = _scroll->scrollTop();
+		if (scrollTo > scrollTop + maxAnimatedDelta) {
+			scrollTop = scrollTo - maxAnimatedDelta;
+			synteticScrollToY(scrollTop);
+			transition = anim::easeOutCubic;
+		} else if (scrollTo + maxAnimatedDelta < scrollTop) {
+			scrollTop = scrollTo + maxAnimatedDelta;
+			synteticScrollToY(scrollTop);
+			transition = anim::easeOutCubic;
+		}
+		_scrollToMediaMessageAnimation.finish();
+		_scrollToMediaMessageAnimation.start([this, itemId = item->fullId()] {
+			auto itemTop = _list->itemTop(App::histItemById(itemId));
+			if (itemTop < 0) {
+				_scrollToMediaMessageAnimation.finish();
+			} else {
+				synteticScrollToY(qRound(_scrollToMediaMessageAnimation.current()) + itemTop);
+			}
+		}, scrollTop - itemTop, scrollTo - itemTop, st::slideDuration, anim::sineInOut);
+		return true;
+	};
+
+	if (msgId == ShowAtUnreadMsgId) {
+		// Special case "scroll to bottom".
+		auto scrollTo = _scroll->scrollTopMax();
+
+		// Attach our scroll animation to some item.
+		auto item = _history->isEmpty() ? nullptr : _history->blocks.back()->items.back();
+		if (animatedScrollAttachedToItem(item, scrollTo)) {
+			return;
+		}
+
+		// If something went wrong we just scroll without animation.
+		synteticScrollToY(scrollTo);
+		return;
+	}
+
+	auto to = App::histItemById(_channel, msgId);
+	auto scrollTo = snap(itemTopForHighlight(to), 0, _scroll->scrollTopMax());
+	animatedScrollAttachedToItem(to, scrollTo);
+}
+
+void HistoryWidget::highlightMessage(HistoryItem *context) {
+	Expects(_list != nullptr);
+
+	_animActiveStart = getms();
+	_animActiveTimer.start(AnimationTimerDelta);
+	_activeAnimMsgId = _showAtMsgId;
+	if (context
+		&& context->history() == _history
+		&& context->isGroupMigrate()
+		&& _migrated
+		&& !_migrated->isEmpty()
+		&& _migrated->loadedAtBottom()
+		&& _migrated->blocks.back()->items.back()->isGroupMigrate()
+		&& _list->historyTop() != _list->historyDrawTop()) {
+		_activeAnimMsgId = -_migrated->blocks.back()->items.back()->id;
+	}
+}
+
 int HistoryWidget::itemTopForHighlight(HistoryItem *item) const {
 	auto itemTop = _list->itemTop(item);
 	auto heightLeft = (_scroll->height() - item->height());
@@ -1707,6 +1769,9 @@ void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool re
 			bool canShowNow = _history->isReadyFor(showAtMsgId);
 			if (!canShowNow) {
 				delayedShowAt(showAtMsgId);
+
+				App::main()->dlgUpdated(wasHistory ? wasHistory->peer : nullptr, wasMsgId);
+				emit historyShown(_history, _showAtMsgId);
 			} else {
 				_history->forgetScrollState();
 				if (_migrated) {
@@ -1722,13 +1787,14 @@ void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool re
 					}
 				}
 
-				_showAtMsgId = showAtMsgId;
-				_histInited = false;
-
-				historyLoaded();
+				setMsgId(showAtMsgId);
+				if (_histInited) {
+					animatedScrollToItem(_showAtMsgId);
+					highlightMessage(App::histItemById(_channel, _showAtMsgId));
+				} else {
+					historyLoaded();
+				}
 			}
-			App::main()->dlgUpdated(wasHistory ? wasHistory->peer : nullptr, wasMsgId);
-			emit historyShown(_history, _showAtMsgId);
 
 			_topBar->update();
 			update();
@@ -2452,7 +2518,6 @@ void HistoryWidget::messagesReceived(PeerData *peer, const MTPmessages_Messages
 		setMsgId(_delayedShowAtMsgId);
 
 		_histInited = false;
-
 		historyLoaded();
 	}
 }
@@ -4841,9 +4906,7 @@ void HistoryWidget::updateListSize(bool initial, bool loadedDown, const ScrollCh
 			return updateListSize(initial, false, change);
 		} else {
 			toY = itemTopForHighlight(item);
-			_animActiveStart = getms();
-			_animActiveTimer.start(AnimationTimerDelta);
-			_activeAnimMsgId = _showAtMsgId;
+			highlightMessage(item);
 		}
 	} else if (initial && _showAtMsgId > 0) {
 		auto item = App::histItemById(_channel, _showAtMsgId);
@@ -4854,12 +4917,7 @@ void HistoryWidget::updateListSize(bool initial, bool loadedDown, const ScrollCh
 			return updateListSize(initial, false, change);
 		} else {
 			toY = itemTopForHighlight(item);
-			_animActiveStart = getms();
-			_animActiveTimer.start(AnimationTimerDelta);
-			_activeAnimMsgId = _showAtMsgId;
-			if (item->isGroupMigrate() && _migrated && !_migrated->isEmpty() && _migrated->loadedAtBottom() && _migrated->blocks.back()->items.back()->isGroupMigrate() && _list->historyTop() != _list->historyDrawTop()) {
-				_activeAnimMsgId = -_migrated->blocks.back()->items.back()->id;
-			}
+			highlightMessage(item);
 		}
 	} else if (initial && (_history->unreadBar || (_migrated && _migrated->unreadBar))) {
 		toY = unreadBarTop();
diff --git a/Telegram/SourceFiles/historywidget.h b/Telegram/SourceFiles/historywidget.h
index 6d2dce044..9c0c8e1a2 100644
--- a/Telegram/SourceFiles/historywidget.h
+++ b/Telegram/SourceFiles/historywidget.h
@@ -698,6 +698,8 @@ private:
 		setFieldText(TextWithTags(), events, undoHistoryAction);
 	}
 
+	void animatedScrollToItem(MsgId msgId);
+	void highlightMessage(HistoryItem *context);
 	void updateDragAreas();
 
 	// when scroll position or scroll area size changed this method