Fix loading initial items of non-live timelines (#6598)
This was done automatically by the SDK in the past by returning a `Reset` timeline update, but this behaviour changed and now we have to do it.
This commit is contained in:
parent
a3b973a57d
commit
5ed8b3893e
3 changed files with 48 additions and 4 deletions
|
|
@ -84,6 +84,7 @@ import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.conflate
|
import kotlinx.coroutines.flow.conflate
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.transform
|
import kotlinx.coroutines.flow.transform
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
@ -262,11 +263,16 @@ private fun TimelinePrefetchingHelper(
|
||||||
firstVisibleItemIndex + layoutInfo.visibleItemsInfo.size >= layoutInfo.totalItemsCount - 40
|
firstVisibleItemIndex + layoutInfo.visibleItemsInfo.size >= layoutInfo.totalItemsCount - 40
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we have no timeline items, we need to back paginate to load some messages. This usually happens on all timelines except for live ones.
|
||||||
|
// This automatic pagination was previously done by the SDK, and we received a `Reset` update, but now we need to do it ourselves.
|
||||||
|
val isEmptyTimelineFlow = layoutInfoFlow.map { it.totalItemsCount == 0 }
|
||||||
|
|
||||||
combine(
|
combine(
|
||||||
isCloseToStartOfLoadedTimelineFlow.distinctUntilChanged(),
|
isCloseToStartOfLoadedTimelineFlow.distinctUntilChanged(),
|
||||||
isScrollingFlow.distinctUntilChanged(),
|
isScrollingFlow.distinctUntilChanged(),
|
||||||
) { needsPrefetch, isScrolling ->
|
isEmptyTimelineFlow,
|
||||||
needsPrefetch && isScrolling
|
) { needsPrefetch, isScrolling, isEmptyAndNeedsBackPagination ->
|
||||||
|
isEmptyAndNeedsBackPagination || needsPrefetch && isScrolling
|
||||||
}
|
}
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.collectLatest { needsPrefetch ->
|
.collectLatest { needsPrefetch ->
|
||||||
|
|
|
||||||
|
|
@ -522,6 +522,9 @@ class MessagesViewTest {
|
||||||
rule.setMessagesView(
|
rule.setMessagesView(
|
||||||
state = stateWithActionListState,
|
state = stateWithActionListState,
|
||||||
)
|
)
|
||||||
|
// Clear initial 'LoadMore' event emitted when setting the state
|
||||||
|
eventsRecorder.clear()
|
||||||
|
|
||||||
val verifiedUserSendFailure = rule.activity.getString(CommonStrings.screen_timeline_item_menu_send_failure_changed_identity, "Alice")
|
val verifiedUserSendFailure = rule.activity.getString(CommonStrings.screen_timeline_item_menu_send_failure_changed_identity, "Alice")
|
||||||
rule.onNodeWithText(verifiedUserSendFailure).performClick()
|
rule.onNodeWithText(verifiedUserSendFailure).performClick()
|
||||||
// Give time for the close animation to complete
|
// Give time for the close animation to complete
|
||||||
|
|
@ -585,6 +588,9 @@ class MessagesViewTest {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.setMessagesView(state = state)
|
rule.setMessagesView(state = state)
|
||||||
|
// Clear initial 'LoadMore' event emitted when setting the state
|
||||||
|
eventsRecorder.clear()
|
||||||
|
|
||||||
rule.onNodeWithText("This is a pinned message").performClick()
|
rule.onNodeWithText("This is a pinned message").performClick()
|
||||||
eventsRecorder.assertSingle(TimelineEvent.FocusOnEvent(AN_EVENT_ID, debounce = FOCUS_ON_PINNED_EVENT_DEBOUNCE_DURATION_IN_MILLIS.milliseconds))
|
eventsRecorder.assertSingle(TimelineEvent.FocusOnEvent(AN_EVENT_ID, debounce = FOCUS_ON_PINNED_EVENT_DEBOUNCE_DURATION_IN_MILLIS.milliseconds))
|
||||||
}
|
}
|
||||||
|
|
@ -601,6 +607,9 @@ class MessagesViewTest {
|
||||||
timelineState = aTimelineState(eventSink = eventsRecorder)
|
timelineState = aTimelineState(eventSink = eventsRecorder)
|
||||||
)
|
)
|
||||||
rule.setMessagesView(state = state)
|
rule.setMessagesView(state = state)
|
||||||
|
// Clear initial 'LoadMore' event emitted when setting the state
|
||||||
|
eventsRecorder.clear()
|
||||||
|
|
||||||
val text = rule.activity.getString(R.string.screen_room_timeline_tombstoned_room_action)
|
val text = rule.activity.getString(R.string.screen_room_timeline_tombstoned_room_action)
|
||||||
// The bottomsheet subcompose seems to make the node to appear twice
|
// The bottomsheet subcompose seems to make the node to appear twice
|
||||||
rule.onAllNodesWithText(text).onFirst().performClick()
|
rule.onAllNodesWithText(text).onFirst().performClick()
|
||||||
|
|
|
||||||
|
|
@ -67,24 +67,31 @@ class TimelineViewTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `reaching the end of the timeline does not send a LoadMore event`() {
|
fun `reaching the end of the timeline does not send a LoadMore event`() {
|
||||||
val eventsRecorder = EventsRecorder<TimelineEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
||||||
rule.setTimelineView(
|
rule.setTimelineView(
|
||||||
state = aTimelineState(
|
state = aTimelineState(
|
||||||
|
timelineItems = persistentListOf(aTimelineItemEvent(content = aTimelineItemImageContent())),
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
eventsRecorder.assertSingle(TimelineEvent.OnScrollFinished(firstIndex = 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `scroll to bottom on live timeline does not emit the Event`() {
|
fun `scroll to bottom on live timeline does not emit the Event`() {
|
||||||
val eventsRecorder = EventsRecorder<TimelineEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
||||||
rule.setTimelineView(
|
rule.setTimelineView(
|
||||||
state = aTimelineState(
|
state = aTimelineState(
|
||||||
|
timelineItems = persistentListOf(aTimelineItemEvent(content = aTimelineItemImageContent())),
|
||||||
isLive = true,
|
isLive = true,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
forceJumpToBottomVisibility = true,
|
forceJumpToBottomVisibility = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
eventsRecorder.assertSingle(TimelineEvent.OnScrollFinished(firstIndex = 0))
|
||||||
|
eventsRecorder.clear()
|
||||||
|
|
||||||
val contentDescription = rule.activity.getString(CommonStrings.a11y_jump_to_bottom)
|
val contentDescription = rule.activity.getString(CommonStrings.a11y_jump_to_bottom)
|
||||||
rule.onNodeWithContentDescription(contentDescription).performClick()
|
rule.onNodeWithContentDescription(contentDescription).performClick()
|
||||||
}
|
}
|
||||||
|
|
@ -94,15 +101,33 @@ class TimelineViewTest {
|
||||||
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
||||||
rule.setTimelineView(
|
rule.setTimelineView(
|
||||||
state = aTimelineState(
|
state = aTimelineState(
|
||||||
|
timelineItems = persistentListOf(aTimelineItemEvent(content = aTimelineItemImageContent())),
|
||||||
isLive = false,
|
isLive = false,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
eventsRecorder.assertSingle(TimelineEvent.OnScrollFinished(firstIndex = 0))
|
||||||
|
eventsRecorder.clear()
|
||||||
|
|
||||||
val contentDescription = rule.activity.getString(CommonStrings.a11y_jump_to_bottom)
|
val contentDescription = rule.activity.getString(CommonStrings.a11y_jump_to_bottom)
|
||||||
rule.onNodeWithContentDescription(contentDescription).performClick()
|
rule.onNodeWithContentDescription(contentDescription).performClick()
|
||||||
eventsRecorder.assertSingle(TimelineEvent.JumpToLive)
|
eventsRecorder.assertSingle(TimelineEvent.JumpToLive)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `an empty timeline triggers a prefetch`() {
|
||||||
|
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
||||||
|
rule.setTimelineView(
|
||||||
|
state = aTimelineState(
|
||||||
|
timelineItems = persistentListOf(),
|
||||||
|
eventSink = eventsRecorder,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
eventsRecorder.assertSingle(TimelineEvent.LoadMore(Timeline.PaginationDirection.BACKWARDS))
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `show shield dialog`() {
|
fun `show shield dialog`() {
|
||||||
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
||||||
|
|
@ -133,11 +158,15 @@ class TimelineViewTest {
|
||||||
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
||||||
rule.setTimelineView(
|
rule.setTimelineView(
|
||||||
state = aTimelineState(
|
state = aTimelineState(
|
||||||
|
timelineItems = persistentListOf(aTimelineItemEvent(content = aTimelineItemImageContent())),
|
||||||
isLive = false,
|
isLive = false,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
messageShield = aCriticalShield(),
|
messageShield = aCriticalShield(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
eventsRecorder.assertSingle(TimelineEvent.OnScrollFinished(firstIndex = 0))
|
||||||
|
eventsRecorder.clear()
|
||||||
|
|
||||||
rule.clickOn(CommonStrings.action_ok)
|
rule.clickOn(CommonStrings.action_ok)
|
||||||
eventsRecorder.assertSingle(TimelineEvent.HideShieldDialog)
|
eventsRecorder.assertSingle(TimelineEvent.HideShieldDialog)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue