Loading packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +56 −1 Original line number Diff line number Diff line Loading @@ -151,6 +151,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntRect import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.LayoutDirection Loading Loading @@ -947,12 +948,28 @@ private fun BoxScope.CommunalHubLazyGrid( } } } else { val itemAlpha = if (communalResponsiveGrid()) { val percentVisible by remember(gridState, index) { derivedStateOf { calculatePercentVisible(gridState, index) } } animateFloatAsState(percentVisible) } else { null } CommunalContent( model = item, viewModel = viewModel, size = size, selected = false, modifier = Modifier.requiredSize(dpSize).animateItem(), modifier = Modifier.requiredSize(dpSize).animateItem().thenIf( communalResponsiveGrid() ) { Modifier.graphicsLayer { alpha = itemAlpha?.value ?: 1f } }, index = index, contentListState = contentListState, interactionHandler = interactionHandler, Loading Loading @@ -1856,6 +1873,44 @@ private fun CommunalContentModel.getSpanOrMax(maxSpan: Int?) = size.span } private fun IntRect.percentOverlap(other: IntRect): Float { val intersection = intersect(other) if (intersection.width < 0 || intersection.height < 0) { return 0f } val overlapArea = intersection.width * intersection.height val area = width * height return overlapArea.toFloat() / area.toFloat() } private fun calculatePercentVisible(state: LazyGridState, index: Int): Float { val viewportSize = state.layoutInfo.viewportSize val visibleRect = IntRect( offset = IntOffset( state.layoutInfo.viewportStartOffset + state.layoutInfo.beforeContentPadding, 0, ), size = IntSize( width = viewportSize.width - state.layoutInfo.beforeContentPadding - state.layoutInfo.afterContentPadding, height = viewportSize.height, ), ) val itemInfo = state.layoutInfo.visibleItemsInfo.find { it.index == index } return if (itemInfo != null) { val boundingBox = IntRect(itemInfo.offset, itemInfo.size) boundingBox.percentOverlap(visibleRect) } else { 0f } } private object Colors { val DisabledColorFilter by lazy { disabledColorMatrix() } Loading packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt +42 −10 Original line number Diff line number Diff line Loading @@ -36,7 +36,9 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.toComposeRect import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.Dp Loading @@ -45,6 +47,7 @@ import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.coerceAtMost import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.times import androidx.window.layout.WindowMetricsCalculator /** * Renders a responsive [LazyHorizontalGrid] with dynamic columns and rows. Each cell will maintain Loading @@ -71,7 +74,7 @@ fun ResponsiveLazyHorizontalGrid( "$minHorizontalArrangement and $minVerticalArrangement, respectively." } BoxWithConstraints(modifier) { val gridSize = rememberGridSize(maxWidth = maxWidth, maxHeight = maxHeight) val gridSize = rememberGridSize() val layoutDirection = LocalLayoutDirection.current val density = LocalDensity.current Loading Loading @@ -128,25 +131,43 @@ fun ResponsiveLazyHorizontalGrid( val extraWidth = maxWidth - usedWidth val extraHeight = maxHeight - usedHeight val finalStartPadding = minStartPadding + extraWidth / 2 // If there is a single column or single row, distribute extra space evenly across the grid. // Otherwise, distribute it along the content padding to center the content. val distributeHorizontalSpaceAlongGutters = gridSize.height == 1 || gridSize.width == 1 val evenlyDistributedWidth = if (distributeHorizontalSpaceAlongGutters) { extraWidth / (gridSize.width + 1) } else { extraWidth / 2 } val finalStartPadding = minStartPadding + evenlyDistributedWidth val finalEndPadding = minEndPadding + evenlyDistributedWidth val finalTopPadding = minTopPadding + extraHeight / 2 val finalContentPadding = PaddingValues( start = finalStartPadding, end = minEndPadding + extraWidth / 2, end = finalEndPadding, top = finalTopPadding, bottom = minBottomPadding + extraHeight / 2, ) with(density) { setContentOffset(Offset(finalStartPadding.toPx(), finalTopPadding.toPx())) } val horizontalArrangement = if (distributeHorizontalSpaceAlongGutters) { minHorizontalArrangement + evenlyDistributedWidth } else { minHorizontalArrangement } LazyHorizontalGrid( rows = GridCells.Fixed(gridSize.height), modifier = Modifier.fillMaxSize(), state = state, contentPadding = finalContentPadding, horizontalArrangement = Arrangement.spacedBy(minHorizontalArrangement), horizontalArrangement = Arrangement.spacedBy(horizontalArrangement), verticalArrangement = Arrangement.spacedBy(minVerticalArrangement), flingBehavior = flingBehavior, userScrollEnabled = userScrollEnabled, Loading Loading @@ -210,27 +231,38 @@ data class SizeInfo( } @Composable private fun rememberGridSize(maxWidth: Dp, maxHeight: Dp): IntSize { private fun rememberGridSize(): IntSize { val configuration = LocalConfiguration.current val orientation = configuration.orientation val screenSize = calculateWindowSize() return remember(orientation, maxWidth, maxHeight) { return remember(orientation, screenSize) { if (orientation == Configuration.ORIENTATION_PORTRAIT) { IntSize( width = calculateNumCellsWidth(maxWidth), height = calculateNumCellsHeight(maxHeight), width = calculateNumCellsWidth(screenSize.width), height = calculateNumCellsHeight(screenSize.height), ) } else { // In landscape we invert the rows/columns to ensure we match the same area as portrait. // This keeps the number of elements in the grid consistent when changing orientation. IntSize( width = calculateNumCellsHeight(maxWidth), height = calculateNumCellsWidth(maxHeight), width = calculateNumCellsHeight(screenSize.width), height = calculateNumCellsWidth(screenSize.height), ) } } } @Composable fun calculateWindowSize(): DpSize { // Observe view configuration changes and recalculate the size class on each change. LocalConfiguration.current val density = LocalDensity.current val context = LocalContext.current val metrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context) return with(density) { metrics.bounds.toComposeRect().size.toDpSize() } } private fun calculateNumCellsWidth(width: Dp) = // See https://developer.android.com/develop/ui/views/layout/use-window-size-classes when { Loading Loading
packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +56 −1 Original line number Diff line number Diff line Loading @@ -151,6 +151,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntRect import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.LayoutDirection Loading Loading @@ -947,12 +948,28 @@ private fun BoxScope.CommunalHubLazyGrid( } } } else { val itemAlpha = if (communalResponsiveGrid()) { val percentVisible by remember(gridState, index) { derivedStateOf { calculatePercentVisible(gridState, index) } } animateFloatAsState(percentVisible) } else { null } CommunalContent( model = item, viewModel = viewModel, size = size, selected = false, modifier = Modifier.requiredSize(dpSize).animateItem(), modifier = Modifier.requiredSize(dpSize).animateItem().thenIf( communalResponsiveGrid() ) { Modifier.graphicsLayer { alpha = itemAlpha?.value ?: 1f } }, index = index, contentListState = contentListState, interactionHandler = interactionHandler, Loading Loading @@ -1856,6 +1873,44 @@ private fun CommunalContentModel.getSpanOrMax(maxSpan: Int?) = size.span } private fun IntRect.percentOverlap(other: IntRect): Float { val intersection = intersect(other) if (intersection.width < 0 || intersection.height < 0) { return 0f } val overlapArea = intersection.width * intersection.height val area = width * height return overlapArea.toFloat() / area.toFloat() } private fun calculatePercentVisible(state: LazyGridState, index: Int): Float { val viewportSize = state.layoutInfo.viewportSize val visibleRect = IntRect( offset = IntOffset( state.layoutInfo.viewportStartOffset + state.layoutInfo.beforeContentPadding, 0, ), size = IntSize( width = viewportSize.width - state.layoutInfo.beforeContentPadding - state.layoutInfo.afterContentPadding, height = viewportSize.height, ), ) val itemInfo = state.layoutInfo.visibleItemsInfo.find { it.index == index } return if (itemInfo != null) { val boundingBox = IntRect(itemInfo.offset, itemInfo.size) boundingBox.percentOverlap(visibleRect) } else { 0f } } private object Colors { val DisabledColorFilter by lazy { disabledColorMatrix() } Loading
packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt +42 −10 Original line number Diff line number Diff line Loading @@ -36,7 +36,9 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.toComposeRect import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.Dp Loading @@ -45,6 +47,7 @@ import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.coerceAtMost import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.times import androidx.window.layout.WindowMetricsCalculator /** * Renders a responsive [LazyHorizontalGrid] with dynamic columns and rows. Each cell will maintain Loading @@ -71,7 +74,7 @@ fun ResponsiveLazyHorizontalGrid( "$minHorizontalArrangement and $minVerticalArrangement, respectively." } BoxWithConstraints(modifier) { val gridSize = rememberGridSize(maxWidth = maxWidth, maxHeight = maxHeight) val gridSize = rememberGridSize() val layoutDirection = LocalLayoutDirection.current val density = LocalDensity.current Loading Loading @@ -128,25 +131,43 @@ fun ResponsiveLazyHorizontalGrid( val extraWidth = maxWidth - usedWidth val extraHeight = maxHeight - usedHeight val finalStartPadding = minStartPadding + extraWidth / 2 // If there is a single column or single row, distribute extra space evenly across the grid. // Otherwise, distribute it along the content padding to center the content. val distributeHorizontalSpaceAlongGutters = gridSize.height == 1 || gridSize.width == 1 val evenlyDistributedWidth = if (distributeHorizontalSpaceAlongGutters) { extraWidth / (gridSize.width + 1) } else { extraWidth / 2 } val finalStartPadding = minStartPadding + evenlyDistributedWidth val finalEndPadding = minEndPadding + evenlyDistributedWidth val finalTopPadding = minTopPadding + extraHeight / 2 val finalContentPadding = PaddingValues( start = finalStartPadding, end = minEndPadding + extraWidth / 2, end = finalEndPadding, top = finalTopPadding, bottom = minBottomPadding + extraHeight / 2, ) with(density) { setContentOffset(Offset(finalStartPadding.toPx(), finalTopPadding.toPx())) } val horizontalArrangement = if (distributeHorizontalSpaceAlongGutters) { minHorizontalArrangement + evenlyDistributedWidth } else { minHorizontalArrangement } LazyHorizontalGrid( rows = GridCells.Fixed(gridSize.height), modifier = Modifier.fillMaxSize(), state = state, contentPadding = finalContentPadding, horizontalArrangement = Arrangement.spacedBy(minHorizontalArrangement), horizontalArrangement = Arrangement.spacedBy(horizontalArrangement), verticalArrangement = Arrangement.spacedBy(minVerticalArrangement), flingBehavior = flingBehavior, userScrollEnabled = userScrollEnabled, Loading Loading @@ -210,27 +231,38 @@ data class SizeInfo( } @Composable private fun rememberGridSize(maxWidth: Dp, maxHeight: Dp): IntSize { private fun rememberGridSize(): IntSize { val configuration = LocalConfiguration.current val orientation = configuration.orientation val screenSize = calculateWindowSize() return remember(orientation, maxWidth, maxHeight) { return remember(orientation, screenSize) { if (orientation == Configuration.ORIENTATION_PORTRAIT) { IntSize( width = calculateNumCellsWidth(maxWidth), height = calculateNumCellsHeight(maxHeight), width = calculateNumCellsWidth(screenSize.width), height = calculateNumCellsHeight(screenSize.height), ) } else { // In landscape we invert the rows/columns to ensure we match the same area as portrait. // This keeps the number of elements in the grid consistent when changing orientation. IntSize( width = calculateNumCellsHeight(maxWidth), height = calculateNumCellsWidth(maxHeight), width = calculateNumCellsHeight(screenSize.width), height = calculateNumCellsWidth(screenSize.height), ) } } } @Composable fun calculateWindowSize(): DpSize { // Observe view configuration changes and recalculate the size class on each change. LocalConfiguration.current val density = LocalDensity.current val context = LocalContext.current val metrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context) return with(density) { metrics.bounds.toComposeRect().size.toDpSize() } } private fun calculateNumCellsWidth(width: Dp) = // See https://developer.android.com/develop/ui/views/layout/use-window-size-classes when { Loading