Avoid chat position jumps during message loading
Sometimes, the chat list would jump without the user scrolling: - During intial loading of a room content, i.e. when it is expected that the list stays scrolled to bottom - During loading of messages after jumping to a linked message With this commit, the target event is repeatedly scrolled to upon list changes until the users scroll themselves, to avoid above scenarios. Change-Id: Iabbe76832e7e68686431b0baed9356c88eb50901
This commit is contained in:
parent
4e9ab0c6f9
commit
b808d8b464
@ -56,6 +56,11 @@ interface Timeline {
|
|||||||
*/
|
*/
|
||||||
fun restartWithEventId(eventId: String?)
|
fun restartWithEventId(eventId: String?)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event that should be displayed first, before the user scrolls.
|
||||||
|
*/
|
||||||
|
fun getInitialEventId(): String?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the timeline can be enriched by paginating.
|
* Check if the timeline can be enriched by paginating.
|
||||||
* @param direction the direction to check in
|
* @param direction the direction to check in
|
||||||
|
@ -221,6 +221,10 @@ internal class DefaultTimeline(
|
|||||||
postSnapshot()
|
postSnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getInitialEventId(): String? {
|
||||||
|
return initialEventId
|
||||||
|
}
|
||||||
|
|
||||||
override fun getTimelineEventAtIndex(index: Int): TimelineEvent? {
|
override fun getTimelineEventAtIndex(index: Int): TimelineEvent? {
|
||||||
return builtEvents.getOrNull(index)
|
return builtEvents.getOrNull(index)
|
||||||
}
|
}
|
||||||
|
@ -740,6 +740,8 @@ class RoomDetailFragment @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun navigateToEvent(action: RoomDetailViewEvents.NavigateToEvent) {
|
private fun navigateToEvent(action: RoomDetailViewEvents.NavigateToEvent) {
|
||||||
|
scrollOnNewMessageCallback.initialForceScroll = true
|
||||||
|
scrollOnNewMessageCallback.initialForceScrollEventId = action.eventId
|
||||||
val scrollPosition = timelineEventController.searchPositionOfEvent(action.eventId)
|
val scrollPosition = timelineEventController.searchPositionOfEvent(action.eventId)
|
||||||
if (scrollPosition == null) {
|
if (scrollPosition == null) {
|
||||||
scrollOnHighlightedEventCallback.scheduleScrollTo(action.eventId)
|
scrollOnHighlightedEventCallback.scheduleScrollTo(action.eventId)
|
||||||
@ -1067,6 +1069,8 @@ class RoomDetailFragment @Inject constructor(
|
|||||||
layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, true)
|
layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, true)
|
||||||
val stateRestorer = LayoutManagerStateRestorer(layoutManager).register()
|
val stateRestorer = LayoutManagerStateRestorer(layoutManager).register()
|
||||||
scrollOnNewMessageCallback = ScrollOnNewMessageCallback(layoutManager, timelineEventController)
|
scrollOnNewMessageCallback = ScrollOnNewMessageCallback(layoutManager, timelineEventController)
|
||||||
|
// Force scroll until the user has scrolled to address the bug where the list would jump during initial loading
|
||||||
|
scrollOnNewMessageCallback.initialForceScroll = true
|
||||||
scrollOnHighlightedEventCallback = ScrollOnHighlightedEventCallback(views.timelineRecyclerView, layoutManager, timelineEventController)
|
scrollOnHighlightedEventCallback = ScrollOnHighlightedEventCallback(views.timelineRecyclerView, layoutManager, timelineEventController)
|
||||||
views.timelineRecyclerView.layoutManager = layoutManager
|
views.timelineRecyclerView.layoutManager = layoutManager
|
||||||
views.timelineRecyclerView.itemAnimator = null
|
views.timelineRecyclerView.itemAnimator = null
|
||||||
@ -1080,6 +1084,15 @@ class RoomDetailFragment @Inject constructor(
|
|||||||
}
|
}
|
||||||
timelineEventController.addModelBuildListener(modelBuildListener)
|
timelineEventController.addModelBuildListener(modelBuildListener)
|
||||||
views.timelineRecyclerView.adapter = timelineEventController.adapter
|
views.timelineRecyclerView.adapter = timelineEventController.adapter
|
||||||
|
views.timelineRecyclerView.addOnScrollListener(object: RecyclerView.OnScrollListener() {
|
||||||
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
|
if (dy != 0) {
|
||||||
|
// User has scrolled, stop force scrolling
|
||||||
|
scrollOnNewMessageCallback.initialForceScroll = false
|
||||||
|
}
|
||||||
|
super.onScrolled(recyclerView, dx, dy)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
if (vectorPreferences.swipeToReplyIsEnabled()) {
|
if (vectorPreferences.swipeToReplyIsEnabled()) {
|
||||||
val quickReplyHandler = object : RoomMessageTouchHelperCallback.QuickReplayHandler {
|
val quickReplyHandler = object : RoomMessageTouchHelperCallback.QuickReplayHandler {
|
||||||
|
@ -28,8 +28,23 @@ class ScrollOnNewMessageCallback(private val layoutManager: LinearLayoutManager,
|
|||||||
|
|
||||||
private val newTimelineEventIds = CopyOnWriteArrayList<String>()
|
private val newTimelineEventIds = CopyOnWriteArrayList<String>()
|
||||||
private var forceScroll = false
|
private var forceScroll = false
|
||||||
|
var initialForceScroll = false
|
||||||
|
var initialForceScrollEventId: String? = null
|
||||||
|
get() = field ?: timelineEventController.timeline?.getInitialEventId()
|
||||||
|
|
||||||
fun addNewTimelineEventIds(eventIds: List<String>) {
|
fun addNewTimelineEventIds(eventIds: List<String>) {
|
||||||
|
// Disable initial force scroll
|
||||||
|
initialForceScroll = false
|
||||||
|
// Update force scroll id when sticking to the bottom - TODO try this if staying at bottom is not reliable as well
|
||||||
|
/*
|
||||||
|
if (eventIds.isNotEmpty()) {
|
||||||
|
initialForceScrollEventId.let {
|
||||||
|
if (it != null && it == timelineEventController.timeline?.getInitialEventId()) {
|
||||||
|
initialForceScrollEventId = eventIds[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
newTimelineEventIds.addAll(0, eventIds)
|
newTimelineEventIds.addAll(0, eventIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,6 +53,12 @@ class ScrollOnNewMessageCallback(private val layoutManager: LinearLayoutManager,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onInserted(position: Int, count: Int) {
|
override fun onInserted(position: Int, count: Int) {
|
||||||
|
if (initialForceScroll) {
|
||||||
|
timelineEventController.searchPositionOfEvent(initialForceScrollEventId)?.let {
|
||||||
|
layoutManager.scrollToPosition(it)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
if (position != 0) {
|
if (position != 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user