diff options
| author | 2024-06-14 21:08:27 +0000 | |
|---|---|---|
| committer | 2024-06-14 21:08:27 +0000 | |
| commit | 19b905f211fade40d319192b74364bdcf5f926af (patch) | |
| tree | 77dc64f565daa67294d6745cc452a2bb43f90679 | |
| parent | 9ea86a757696c27ec9c396e236072719b630fd1b (diff) | |
| parent | 1c5659e691b022038f31961363e92ca55068d600 (diff) | |
Merge "Prefetch shareousel items" into main
2 files changed, 127 insertions, 1 deletions
diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselComposable.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselComposable.kt index c63055d2..0940baa0 100644 --- a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselComposable.kt +++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselComposable.kt @@ -16,6 +16,7 @@ package com.android.intentresolver.contentpreview.payloadtoggle.ui.composable import androidx.compose.animation.Crossfade +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -92,13 +93,18 @@ private fun Shareousel(viewModel: ShareouselViewModel, keySet: PreviewsModel) { } } +@OptIn(ExperimentalFoundationApi::class) @Composable private fun PreviewCarousel( previews: PreviewsModel, viewModel: ShareouselViewModel, ) { val centerIdx = previews.startIdx - val carouselState = rememberLazyListState(initialFirstVisibleItemIndex = centerIdx) + val carouselState = + rememberLazyListState( + initialFirstVisibleItemIndex = centerIdx, + prefetchStrategy = remember { ShareouselLazyListPrefetchStrategy() } + ) // TODO: start item needs to be centered, check out ScalingLazyColumn impl or see if // HorizontalPager works for our use-case LazyRow( diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselLazyListPrefetchStrategy.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselLazyListPrefetchStrategy.kt new file mode 100644 index 00000000..e47700f1 --- /dev/null +++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselLazyListPrefetchStrategy.kt @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.intentresolver.contentpreview.payloadtoggle.ui.composable + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.lazy.LazyListItemInfo +import androidx.compose.foundation.lazy.LazyListLayoutInfo +import androidx.compose.foundation.lazy.LazyListPrefetchScope +import androidx.compose.foundation.lazy.LazyListPrefetchStrategy +import androidx.compose.foundation.lazy.layout.LazyLayoutPrefetchState +import androidx.compose.foundation.lazy.layout.NestedPrefetchScope + +/** Prefetch strategy to fetch items ahead and behind the current scroll position. */ +@OptIn(ExperimentalFoundationApi::class) +class ShareouselLazyListPrefetchStrategy( + private val lookAhead: Int = 4, + private val lookBackward: Int = 1 +) : LazyListPrefetchStrategy { + // Map of index -> prefetch handle + private val prefetchHandles: MutableMap<Int, LazyLayoutPrefetchState.PrefetchHandle> = + mutableMapOf() + + private var prefetchRange = IntRange.EMPTY + + private enum class ScrollDirection { + UNKNOWN, // The user hasn't scrolled in either direction yet. + FORWARD, + BACKWARD, + } + + private var scrollDirection: ScrollDirection = ScrollDirection.UNKNOWN + + override fun LazyListPrefetchScope.onScroll(delta: Float, layoutInfo: LazyListLayoutInfo) { + if (layoutInfo.visibleItemsInfo.isNotEmpty()) { + scrollDirection = if (delta < 0) ScrollDirection.FORWARD else ScrollDirection.BACKWARD + updatePrefetchSet(layoutInfo.visibleItemsInfo) + } + + if (scrollDirection == ScrollDirection.FORWARD) { + val lastItem = layoutInfo.visibleItemsInfo.last() + val spacing = layoutInfo.mainAxisItemSpacing + val distanceToPrefetchItem = + lastItem.offset + lastItem.size + spacing - layoutInfo.viewportEndOffset + // if in the next frame we will get the same delta will we reach the item? + if (distanceToPrefetchItem < -delta) { + prefetchHandles.get(lastItem.index + 1)?.markAsUrgent() + } + } else { + val firstItem = layoutInfo.visibleItemsInfo.first() + val distanceToPrefetchItem = layoutInfo.viewportStartOffset - firstItem.offset + // if in the next frame we will get the same delta will we reach the item? + if (distanceToPrefetchItem < delta) { + prefetchHandles.get(firstItem.index - 1)?.markAsUrgent() + } + } + } + + override fun LazyListPrefetchScope.onVisibleItemsUpdated(layoutInfo: LazyListLayoutInfo) { + if (layoutInfo.visibleItemsInfo.isNotEmpty()) { + updatePrefetchSet(layoutInfo.visibleItemsInfo) + } + } + + override fun NestedPrefetchScope.onNestedPrefetch(firstVisibleItemIndex: Int) {} + + private fun getVisibleRange(visibleItems: List<LazyListItemInfo>) = + if (visibleItems.isEmpty()) IntRange.EMPTY + else IntRange(visibleItems.first().index, visibleItems.last().index) + + /** Update prefetchRange based upon the visible item range and scroll direction. */ + private fun updatePrefetchRange(visibleRange: IntRange) { + prefetchRange = + when (scrollDirection) { + // Prefetch in both directions + ScrollDirection.UNKNOWN -> + visibleRange.first - lookAhead / 2..visibleRange.last + lookAhead / 2 + ScrollDirection.FORWARD -> + visibleRange.first - lookBackward..visibleRange.last + lookAhead + ScrollDirection.BACKWARD -> + visibleRange.first - lookAhead..visibleRange.last + lookBackward + } + } + + private fun LazyListPrefetchScope.updatePrefetchSet(visibleItems: List<LazyListItemInfo>) { + val visibleRange = getVisibleRange(visibleItems) + updatePrefetchRange(visibleRange) + updatePrefetchOperations(visibleRange) + } + + private fun LazyListPrefetchScope.updatePrefetchOperations(visibleItemsRange: IntRange) { + // Remove any fetches outside of the prefetch range or inside the visible range + prefetchHandles + .filterKeys { it !in prefetchRange || it in visibleItemsRange } + .forEach { + it.value.cancel() + prefetchHandles.remove(it.key) + } + + // Ensure all non-visible items in the range are being prefetched + prefetchRange.forEach { + if (it !in visibleItemsRange && !prefetchHandles.containsKey(it)) { + prefetchHandles[it] = schedulePrefetch(it) + } + } + } +} |