@@ -76,11 +76,16 @@ import io.element.android.libraries.testtags.TestTags
76
76
import io.element.android.libraries.testtags.testTag
77
77
import io.element.android.libraries.ui.strings.CommonStrings
78
78
import kotlinx.coroutines.ExperimentalCoroutinesApi
79
+ import kotlinx.coroutines.FlowPreview
80
+ import kotlinx.coroutines.delay
79
81
import kotlinx.coroutines.flow.collectLatest
80
82
import kotlinx.coroutines.flow.combine
83
+ import kotlinx.coroutines.flow.conflate
81
84
import kotlinx.coroutines.flow.distinctUntilChanged
85
+ import kotlinx.coroutines.flow.transform
82
86
import kotlinx.coroutines.launch
83
87
import timber.log.Timber
88
+ import kotlin.time.Duration.Companion.milliseconds
84
89
85
90
@Composable
86
91
fun TimelineView (
@@ -139,6 +144,10 @@ fun TimelineView(
139
144
)
140
145
}
141
146
147
+ fun prefetchMoreItems () {
148
+ state.eventSink(TimelineEvents .LoadMore (Timeline .PaginationDirection .BACKWARDS ))
149
+ }
150
+
142
151
// Animate alpha when timeline is first displayed, to avoid flashes or glitching when viewing rooms
143
152
AnimatedVisibility (visible = true , enter = fadeIn()) {
144
153
Box (modifier) {
@@ -185,9 +194,10 @@ fun TimelineView(
185
194
onClearFocusRequestState = ::clearFocusRequestState
186
195
)
187
196
188
- TimelinePrefetchingHelper (lazyListState = lazyListState) {
189
- state.eventSink(TimelineEvents .LoadMore (Timeline .PaginationDirection .BACKWARDS ))
190
- }
197
+ TimelinePrefetchingHelper (
198
+ lazyListState = lazyListState,
199
+ prefetch = ::prefetchMoreItems
200
+ )
191
201
192
202
TimelineScrollHelper (
193
203
hasAnyEvent = state.hasAnyEvent,
@@ -217,33 +227,33 @@ private fun MessageShieldDialog(state: TimelineState) {
217
227
)
218
228
}
219
229
220
- @OptIn(ExperimentalCoroutinesApi ::class )
230
+ @OptIn(ExperimentalCoroutinesApi ::class , FlowPreview :: class )
221
231
@Composable
222
232
private fun TimelinePrefetchingHelper (
223
233
lazyListState : LazyListState ,
224
234
prefetch : () -> Unit ,
225
235
) {
226
236
val latestPrefetch by rememberUpdatedState(prefetch)
227
237
228
- // We're using snapshot flows for these because using `LaunchedEffect` with `derivedState` doesn't seem to be responsive enough
229
- val firstVisibleItemIndexFlow = snapshotFlow {
230
- lazyListState.firstVisibleItemIndex
231
- }
232
- val layoutInfoFlow = snapshotFlow {
233
- lazyListState.layoutInfo
234
- }
235
- val isScrollingFlow = snapshotFlow {
236
- lazyListState.isScrollInProgress
237
- }
238
+ LaunchedEffect (Unit ) {
239
+ // We're using snapshot flows for these because using `LaunchedEffect` with `derivedState` doesn't seem to be responsive enough
240
+ val firstVisibleItemIndexFlow = snapshotFlow { lazyListState.firstVisibleItemIndex }
241
+ val layoutInfoFlow = snapshotFlow { lazyListState.layoutInfo }
242
+ val isScrollingFlow = snapshotFlow { lazyListState.isScrollInProgress }
243
+ // This value changes too frequently, so we debounce it to avoid unnecessary prefetching. It's the equivalent of a conditional 'throttleLatest'
244
+ .conflate()
245
+ .transform { isScrolling ->
246
+ emit(isScrolling)
247
+ if (isScrolling) delay(100 .milliseconds)
248
+ }
238
249
239
- LaunchedEffect (latestPrefetch) {
240
250
val isCloseToStartOfLoadedTimelineFlow = combine(layoutInfoFlow, firstVisibleItemIndexFlow) { layoutInfo, firstVisibleItemIndex ->
241
251
firstVisibleItemIndex + layoutInfo.visibleItemsInfo.size >= layoutInfo.totalItemsCount - 40
242
252
}
243
253
244
254
combine(
245
- isCloseToStartOfLoadedTimelineFlow,
246
- isScrollingFlow,
255
+ isCloseToStartOfLoadedTimelineFlow.distinctUntilChanged() ,
256
+ isScrollingFlow.distinctUntilChanged() ,
247
257
) { needsPrefetch, isScrolling ->
248
258
needsPrefetch && isScrolling
249
259
}
0 commit comments