Center initial item in Sharousel
As long as there are enough items in the row to center the initial item,
it will be centered, otherwise it will be as close to center as the
scroll bounds allow.
Test: manual testing
BUG: 341925364
Flag: android.service.chooser.chooser_payload_toggling
Change-Id: Ifb96071ba70c3ba42f53d584ddfc31d4fc62cedf
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 9ac36a8..e40d98b 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
@@ -45,6 +45,7 @@
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -71,6 +72,7 @@
import com.android.intentresolver.contentpreview.payloadtoggle.ui.viewmodel.ShareouselPreviewViewModel
import com.android.intentresolver.contentpreview.payloadtoggle.ui.viewmodel.ShareouselViewModel
import kotlin.math.abs
+import kotlin.math.min
import kotlinx.coroutines.launch
@Composable
@@ -104,44 +106,48 @@
previews: PreviewsModel,
viewModel: ShareouselViewModel,
) {
- val centerIdx = previews.startIdx
- val carouselState =
- rememberLazyListState(
- initialFirstVisibleItemIndex = centerIdx,
- prefetchStrategy = remember { ShareouselLazyListPrefetchStrategy() }
- )
var maxAspectRatio by remember { mutableStateOf(0f) }
var viewportHeight by remember { mutableStateOf(0) }
-
- val horizontalPadding = 16.dp
+ var viewportCenter by remember { mutableStateOf(0) }
+ var horizontalPadding by remember { mutableStateOf(0.dp) }
Box(
modifier =
Modifier.fillMaxWidth()
.height(dimensionResource(R.dimen.chooser_preview_image_height_tall))
.layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
- val aspectRatio =
+ val (minItemWidth, maxAR) =
if (placeable.height <= 0) {
- 0f
+ 0f to 0f
} else {
- val maxItemWidth =
- maxOf(0, placeable.width - 2 * horizontalPadding.roundToPx())
- (maxItemWidth.toFloat() / placeable.height).coerceIn(
- 0f,
- MAX_ASPECT_RATIO
- )
+ val minItemWidth = (MIN_ASPECT_RATIO * placeable.height)
+ val maxItemWidth = maxOf(0, placeable.width - 32.dp.roundToPx())
+ val maxAR =
+ (maxItemWidth.toFloat() / placeable.height).coerceIn(
+ 0f,
+ MAX_ASPECT_RATIO
+ )
+ minItemWidth to maxAR
}
- maxAspectRatio = aspectRatio
+ viewportCenter = placeable.width / 2
+ maxAspectRatio = maxAR
viewportHeight = placeable.height
+ horizontalPadding = ((placeable.width - minItemWidth) / 2).toDp()
layout(placeable.width, placeable.height) { placeable.place(0, 0) }
},
) {
- if (maxAspectRatio <= 0) {
+ if (maxAspectRatio <= 0 && previews.previewModels.isNotEmpty()) {
// Do not compose the list until we know the viewport size
return@Box
}
- // TODO: start item needs to be centered, check out ScalingLazyColumn impl or see if
- // HorizontalPager works for our use-case
+
+ var firstSelectedIndex by remember { mutableStateOf(null as Int?) }
+
+ val carouselState =
+ rememberLazyListState(
+ prefetchStrategy = remember { ShareouselLazyListPrefetchStrategy() },
+ )
+
LazyRow(
state = carouselState,
horizontalArrangement = Arrangement.spacedBy(4.dp),
@@ -149,26 +155,38 @@
modifier = Modifier.fillMaxSize().systemGestureExclusion(),
) {
itemsIndexed(previews.previewModels, key = { _, model -> model.uri }) { index, model ->
+ val visibleItem by remember {
+ derivedStateOf {
+ carouselState.layoutInfo.visibleItemsInfo.firstOrNull { it.index == index }
+ }
+ }
+
// Index if this is the element in the center of the viewing area, otherwise null
val previewIndex by remember {
derivedStateOf {
- carouselState.layoutInfo.visibleItemsInfo
- .firstOrNull { it.index == index }
- ?.let {
- val viewportCenter = carouselState.layoutInfo.viewportEndOffset / 2
- val halfPreviewWidth = it.size / 2
- val previewCenter = it.offset + halfPreviewWidth
- val previewDistanceToViewportCenter =
- abs(previewCenter - viewportCenter)
- if (previewDistanceToViewportCenter <= halfPreviewWidth) {
- index
- } else {
- null
- }
+ visibleItem?.let {
+ val halfPreviewWidth = it.size / 2
+ val previewCenter = it.offset + halfPreviewWidth
+ val previewDistanceToViewportCenter =
+ abs(previewCenter - viewportCenter)
+ if (previewDistanceToViewportCenter <= halfPreviewWidth) {
+ index
+ } else {
+ null
}
+ }
}
}
+ val previewModel =
+ viewModel.preview(model, viewportHeight, previewIndex, rememberCoroutineScope())
+ val selected by
+ previewModel.isSelected.collectAsStateWithLifecycle(initialValue = false)
+
+ if (selected) {
+ firstSelectedIndex = min(index, firstSelectedIndex ?: Int.MAX_VALUE)
+ }
+
ShareouselCard(
viewModel.preview(
model,
@@ -180,6 +198,22 @@
)
}
}
+
+ firstSelectedIndex?.let { index ->
+ LaunchedEffect(Unit) {
+ val visibleItem =
+ carouselState.layoutInfo.visibleItemsInfo.firstOrNull { it.index == index }
+ val center =
+ with(carouselState.layoutInfo) {
+ ((viewportEndOffset - viewportStartOffset) / 2) + viewportStartOffset
+ }
+
+ carouselState.scrollToItem(
+ index = index,
+ scrollOffset = visibleItem?.size?.div(2)?.minus(center) ?: 0,
+ )
+ }
+ }
}
}