diff options
| author | 2024-06-15 21:22:58 +0000 | |
|---|---|---|
| committer | 2024-06-15 21:22:58 +0000 | |
| commit | 91615a2daee535144ae42fbe76356574bb9f6276 (patch) | |
| tree | fa795a7da53691a21e9659d924de79a627ba8594 /java/src | |
| parent | ebaed5c1bccae61032a6249453b91d28fd0d32a1 (diff) | |
| parent | 468ae8749759dfdb58175e5ea431ca36a588876e (diff) | |
Snap for 11975806 from 468ae8749759dfdb58175e5ea431ca36a588876e to 24Q3-release
Change-Id: Id8df002a3225ffb562364ac360a3a4a083a7c953
Diffstat (limited to 'java/src')
2 files changed, 129 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..c40ed266 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,12 +16,14 @@  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  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 @@ -92,18 +94,24 @@ 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(          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)) 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) +            } +        } +    } +}  |