From 1c5659e691b022038f31961363e92ca55068d600 Mon Sep 17 00:00:00 2001 From: Matt Casey Date: Wed, 12 Jun 2024 17:22:56 +0000 Subject: Prefetch shareousel items Based upon the DefaultLazyListPrefetchStrategy impl, but prefetching more things. Bug: 344961968 Test: Manual test with ShareTest and slow load times Flag: android.service.chooser.chooser_payload_toggling Change-Id: I28ef69a360ce6e02f9ff95e4aab98365b380de0d --- .../ui/composable/ShareouselComposable.kt | 8 +- .../ShareouselLazyListPrefetchStrategy.kt | 120 +++++++++++++++++++++ 2 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselLazyListPrefetchStrategy.kt (limited to 'java/src') 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 = + 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) = + 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) { + 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) + } + } + } +} -- cgit v1.2.3-59-g8ed1b From 72ea64aa101e763d69b2426a802dae3743ea3fb9 Mon Sep 17 00:00:00 2001 From: Govinda Wasserman Date: Fri, 14 Jun 2024 15:15:09 -0400 Subject: Creates 16 dp offset for first and last items. This lines up item with text above and ensures the user can tell if there are items to the left of it. Test: manual testing BUG: 341925364 Flag: android.service.chooser.chooser_payload_toggling Change-Id: I62d2b67843b373a894d88df5e2c7fc4ea9d510e0 --- .../contentpreview/payloadtoggle/ui/composable/ShareouselComposable.kt | 2 ++ 1 file changed, 2 insertions(+) (limited to 'java/src') 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..6af02523 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 @@ -22,6 +22,7 @@ import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxHeight @@ -104,6 +105,7 @@ private fun PreviewCarousel( LazyRow( state = carouselState, horizontalArrangement = Arrangement.spacedBy(4.dp), + contentPadding = PaddingValues(start = 16.dp, end = 16.dp), modifier = Modifier.fillMaxWidth() .height(dimensionResource(R.dimen.chooser_preview_image_height_tall)) -- cgit v1.2.3-59-g8ed1b