@@ -36,6 +36,7 @@ import androidx.compose.runtime.remember
36
36
import androidx.compose.runtime.rememberCoroutineScope
37
37
import androidx.compose.runtime.rememberUpdatedState
38
38
import androidx.compose.runtime.setValue
39
+ import androidx.compose.runtime.snapshotFlow
39
40
import androidx.compose.ui.Alignment
40
41
import androidx.compose.ui.Modifier
41
42
import androidx.compose.ui.draw.rotate
@@ -72,6 +73,10 @@ import io.element.android.libraries.matrix.api.core.EventId
72
73
import io.element.android.libraries.matrix.api.core.UserId
73
74
import io.element.android.libraries.matrix.api.timeline.Timeline
74
75
import io.element.android.libraries.ui.strings.CommonStrings
76
+ import kotlinx.coroutines.ExperimentalCoroutinesApi
77
+ import kotlinx.coroutines.flow.collectLatest
78
+ import kotlinx.coroutines.flow.combine
79
+ import kotlinx.coroutines.flow.distinctUntilChanged
75
80
import kotlinx.coroutines.launch
76
81
import timber.log.Timber
77
82
@@ -177,17 +182,8 @@ fun TimelineView(
177
182
onClearFocusRequestState = ::clearFocusRequestState
178
183
)
179
184
180
- val isCloseToStartOfLoadedTimeline by remember {
181
- derivedStateOf {
182
- lazyListState.firstVisibleItemIndex + lazyListState.layoutInfo.visibleItemsInfo.size >= lazyListState.layoutInfo.totalItemsCount - 10
183
- }
184
- }
185
- LaunchedEffect (isCloseToStartOfLoadedTimeline) {
186
- // Only back paginate when we're close to the start of the loaded timeline items and the user is actively scrolling
187
- if (lazyListState.isScrollInProgress && isCloseToStartOfLoadedTimeline) {
188
- Timber .d(" Prefetching pagination with ${lazyListState.layoutInfo.totalItemsCount} items" )
189
- state.eventSink(TimelineEvents .LoadMore (Timeline .PaginationDirection .BACKWARDS ))
190
- }
185
+ TimelinePrefetchingHelper (lazyListState = lazyListState) {
186
+ state.eventSink(TimelineEvents .LoadMore (Timeline .PaginationDirection .BACKWARDS ))
191
187
}
192
188
193
189
TimelineScrollHelper (
@@ -218,6 +214,41 @@ private fun MessageShieldDialog(state: TimelineState) {
218
214
)
219
215
}
220
216
217
+ @OptIn(ExperimentalCoroutinesApi ::class )
218
+ @Composable
219
+ private fun TimelinePrefetchingHelper (
220
+ lazyListState : LazyListState ,
221
+ prefetch : () -> Unit ,
222
+ ) {
223
+ // We're using snapshot flows for these because using `LaunchedEffect` with `derivedState` doesn't seem to be responsive enough
224
+ val firstVisibleItemIndexFlow = snapshotFlow {
225
+ lazyListState.firstVisibleItemIndex
226
+ }
227
+ val layoutInfoFlow = snapshotFlow {
228
+ lazyListState.layoutInfo
229
+ }
230
+ val isScrollingFlow = snapshotFlow {
231
+ lazyListState.isScrollInProgress
232
+ }
233
+
234
+ LaunchedEffect (Unit ) {
235
+ val isCloseToStartOfLoadedTimelineFlow = combine(layoutInfoFlow, firstVisibleItemIndexFlow) { layoutInfo, firstVisibleItemIndex ->
236
+ firstVisibleItemIndex + layoutInfo.visibleItemsInfo.size >= layoutInfo.totalItemsCount - 40
237
+ }
238
+
239
+ combine(isCloseToStartOfLoadedTimelineFlow, isScrollingFlow) { needsPrefetch, isScrolling ->
240
+ needsPrefetch && isScrolling
241
+ }
242
+ .distinctUntilChanged()
243
+ .collectLatest { needsPrefetch ->
244
+ if (needsPrefetch) {
245
+ Timber .d(" Prefetching pagination with ${lazyListState.layoutInfo.totalItemsCount} items" )
246
+ prefetch()
247
+ }
248
+ }
249
+ }
250
+ }
251
+
221
252
@Composable
222
253
private fun BoxScope.TimelineScrollHelper (
223
254
hasAnyEvent : Boolean ,
0 commit comments