Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 00b125e4 authored by Darrell Shi's avatar Darrell Shi
Browse files

Update glanceable hub layout specs on compact screens

This change enables flexible widget aspect ratio, allowing widgets to
maximize the real estate on compact screens. It also changes how the
grid content paddings are calculated. For compact screens, we now
calculate the top and bottom paddings needed for edit mode, and
distribute them evenly on each side for non edit mode. This ensures that
widget size remains consistent between the two modes, and only the
positions may shift vertically.

Test: visually; see recordings in bug
Bug: 419357983
Bug: 419361764
Flag: com.android.systemui.communal_responsive_grid
Flag: com.android.systemui.hub_edit_mode_transition
Change-Id: I51b803dbbf37a90e78f2bdbe748c44a8805789a8
parent 3e2e9de6
Loading
Loading
Loading
Loading
+1 −8
Original line number Diff line number Diff line
@@ -16,7 +16,6 @@

package com.android.systemui.communal.ui.compose

import android.content.res.Configuration
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
@@ -40,7 +39,6 @@ import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.IntRect
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.toSize
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.ContentScope
@@ -175,14 +173,9 @@ constructor(
                        } else {
                            constraints.maxHeight - lockIconBounds.top
                        }
                    // Bias the widgets up by a small offset for visual balance in landscape
                    // orientation
                    val verticalOffset =
                        (if (orientation == Configuration.ORIENTATION_LANDSCAPE) (-3).dp else 0.dp)
                            .roundToPx()
                    // Use even top and bottom margin for grid to be centered in maxHeight (window)
                    communalGridMaxHeight = constraints.maxHeight - communalGridVerticalMargin * 2
                    communalGridPositionY = communalGridVerticalMargin + verticalOffset
                    communalGridPositionY = communalGridVerticalMargin
                } else {
                    communalGridMaxHeight = lockIconBounds?.top ?: constraints.maxHeight
                    communalGridPositionY = 0
+72 −15
Original line number Diff line number Diff line
@@ -166,6 +166,7 @@ import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntRect
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.coerceAtLeast
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.round
import androidx.compose.ui.unit.sp
@@ -258,7 +259,10 @@ fun CommunalHub(
            initialValue = hubEditModeTransition() || !viewModel.isEditMode
        )

    val minContentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize)
    val minContentPadding =
        if (hubEditModeTransition())
            gridContentPadding(viewModel.isEditMode, Dimensions.ToolbarHeight)
        else gridContentPadding(viewModel.isEditMode, toolbarSize)
    ObserveScrollEffect(gridState, viewModel)

    val context = LocalContext.current
@@ -842,7 +846,8 @@ private fun HorizontalGridWrapper(
        val flingBehavior =
            rememberSnapFlingBehavior(lazyGridState = gridState, snapPosition = SnapPosition.Start)
        ResponsiveLazyHorizontalGrid(
            cellAspectRatio = 1.5f,
            // Use flexible aspect ratio on compact screens to maximize the real estate
            cellAspectRatio = if (hubEditModeTransition() && isCompactWindow()) 0f else 1.5f,
            modifier = modifier,
            state = gridState,
            flingBehavior = flingBehavior,
@@ -906,7 +911,9 @@ private fun BoxScope.CommunalHubLazyGrid(
    var gridItemSize: SizeInfo? by remember { mutableStateOf(null) }
    var list = communalContent
    var dragDropState: GridDragDropState? = null
    var arrangementSpacing = Dimensions.ItemSpacing
    val arrangementSpacing =
        if (communalResponsiveGrid() && isCompactWindow()) Dimensions.ItemSpacingCompact
        else Dimensions.ItemSpacing
    val windowSize = WindowSizeUtils.getWindowSizeCategory(LocalContext.current)
    if (viewModel.isEditMode && viewModel is CommunalEditModeViewModel) {
        list = contentListState.list
@@ -942,9 +949,6 @@ private fun BoxScope.CommunalHubLazyGrid(
        Box(Modifier.fillMaxSize().dragAndDropTarget(dragAndDropTargetState)) {}
    } else if (communalResponsiveGrid()) {
        gridModifier = gridModifier.fillMaxSize()
        if (isCompactWindow()) {
            arrangementSpacing = Dimensions.ItemSpacingCompact
        }
    } else {
        gridModifier = gridModifier.height(hubDimensions.GridHeight)
    }
@@ -1197,7 +1201,9 @@ private fun Toolbar(
    val toolbarPadding = toolbarPadding()
    Box(
        modifier =
            Modifier.fillMaxWidth().padding(toolbarPadding).onSizeChanged { setToolbarSize(it) }
            Modifier.fillMaxWidth().padding(toolbarPadding).thenIf(!hubEditModeTransition()) {
                Modifier.onSizeChanged { setToolbarSize(it) }
            }
    ) {
        val addWidgetText = stringResource(R.string.hub_mode_add_widget_button_text)

@@ -1230,7 +1236,8 @@ private fun Toolbar(
                colors = filledButtonColors(),
                contentPadding = Dimensions.ButtonPadding,
                modifier =
                    Modifier.graphicsLayer { alpha = removeButtonAlpha }
                    Modifier.toolbarHeight()
                        .graphicsLayer { alpha = removeButtonAlpha }
                        .onGloballyPositioned {
                            // It's possible for this callback to fire after remove has been
                            // disabled. Check enabled state before setting.
@@ -1298,6 +1305,7 @@ private fun ToolbarButton(
            onClick = onClick,
            colors = filledButtonColors(),
            contentPadding = Dimensions.ButtonPadding,
            modifier = Modifier.toolbarHeight(),
        ) {
            Row(
                horizontalArrangement =
@@ -1320,6 +1328,7 @@ private fun ToolbarButton(
            colors = ButtonDefaults.outlinedButtonColors(contentColor = colors.primary),
            border = BorderStroke(width = 2.0.dp, color = colors.primary),
            contentPadding = Dimensions.ButtonPadding,
            modifier = Modifier.toolbarHeight(),
        ) {
            Row(
                horizontalArrangement =
@@ -1946,8 +1955,40 @@ private fun nonScalableTextSize(sizeInDp: Dp) = with(LocalDensity.current) { siz
 * outside the grid over the toolbar, without part of it getting clipped by the container.
 */
@Composable
private fun gridContentPadding(isEditMode: Boolean, toolbarSize: IntSize?): PaddingValues {
    if (!isEditMode || toolbarSize == null) {
private fun gridContentPadding(isEditMode: Boolean, toolbarHeight: Dp): PaddingValues {
    if (communalResponsiveGrid() && hubEditModeTransition()) {
        val itemSpacing =
            if (isCompactWindow()) Dimensions.ItemSpacingCompact else Dimensions.ItemSpacing
        val editModeTopPadding =
            toolbarPadding().calculateTopPadding() + toolbarHeight + itemSpacing
        // For compact windows, allow the bottom spacing to be minimum so that all items shift
        // down. For large window, use top padding for both vertical directions to ensure items
        // are centered vertically.
        val editModeBottomPadding = if (isCompactWindow()) itemSpacing else editModeTopPadding

        val finalTopPadding: Dp
        val finalBottomPadding: Dp
        if (isEditMode) {
            finalTopPadding = editModeTopPadding
            finalBottomPadding = editModeBottomPadding
        } else {
            // When in non edit mode, distribute the paddings needed for edit mode evenly on top
            // and bottom. This allows the paddings to shift, keeping the widget size consistent
            // between the two modes.
            finalTopPadding = (editModeTopPadding + editModeBottomPadding) / 2
            finalBottomPadding = finalTopPadding
        }

        return PaddingValues(
            start = itemSpacing,
            top = finalTopPadding,
            end = itemSpacing,
            bottom = finalBottomPadding,
        )
    }

    val isToolbarAbsent = !isEditMode || toolbarHeight == 0.dp
    if (isToolbarAbsent) {
        return if (communalResponsiveGrid()) {
            if (isCompactWindow()) {
                responsiveGridPaddingsWithInsets(Dimensions.ItemSpacingCompact)
@@ -1966,7 +2007,6 @@ private fun gridContentPadding(isEditMode: Boolean, toolbarSize: IntSize?): Padd
    val density = LocalDensity.current
    val windowMetrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context)
    val screenHeight = with(density) { windowMetrics.bounds.height().toDp() }
    val toolbarHeight = with(density) { toolbarSize.height.toDp() }
    return if (communalResponsiveGrid()) {
        // In edit mode, grid spans full screen so min top padding of the grid should include space
        // taken by toolbar.
@@ -2002,6 +2042,14 @@ private fun gridContentPadding(isEditMode: Boolean, toolbarSize: IntSize?): Padd
    }
}

@Composable
private fun gridContentPadding(isEditMode: Boolean, toolbarSize: IntSize?): PaddingValues {
    return gridContentPadding(
        isEditMode,
        toolbarHeight = with(LocalDensity.current) { toolbarSize?.height?.toDp() } ?: 0.dp,
    )
}

/** Compact size in landscape or portrait */
@Composable
fun isCompactWindow(): Boolean {
@@ -2012,6 +2060,10 @@ fun isCompactWindow(): Boolean {
    }
}

private fun Modifier.toolbarHeight(): Modifier {
    return this.thenIf(hubEditModeTransition()) { Modifier.height(Dimensions.ToolbarHeight) }
}

private fun CommunalContentSize.FixedSize.dp(): Dp {
    return when (this) {
        CommunalContentSize.FixedSize.FULL -> Dimensions.CardHeightFull
@@ -2078,6 +2130,8 @@ class Dimensions(val context: Context, val config: Configuration) {
    /** Responsive grid toolbar bottom padding. */
    val toolbarBottomPadding: Dp
        get() {
            if (hubEditModeTransition()) return 0.dp

            val windowSizeCategory = WindowSizeUtils.getWindowSizeCategory(context)
            return if (windowSizeCategory == WindowSizeUtils.WindowSizeCategory.MOBILE_LANDSCAPE) {
                6.adjustedDp
@@ -2106,7 +2160,7 @@ class Dimensions(val context: Context, val config: Configuration) {
            get() = 530.adjustedDp

        val ItemSpacingCompact
            get() = 12.adjustedDp
            get() = if (hubEditModeTransition()) 16.adjustedDp else 12.adjustedDp

        val ItemSpacing
            get() = if (communalResponsiveGrid()) 32.adjustedDp else 50.adjustedDp
@@ -2126,6 +2180,9 @@ class Dimensions(val context: Context, val config: Configuration) {
        val Spacing
            get() = ItemSpacing / 2

        val ToolbarHeight
            get() = 40.dp

        // The sizing/padding of the toolbar in glanceable hub edit mode
        val ToolbarPaddingTop
            get() = if (communalResponsiveGrid()) 12.adjustedDp else 27.adjustedDp
@@ -2133,15 +2190,15 @@ class Dimensions(val context: Context, val config: Configuration) {
        val ToolbarPaddingHorizontal
            get() = ItemSpacing

        val ToolbarButtonPaddingHorizontal
        private val ToolbarButtonPaddingHorizontal
            get() = 24.adjustedDp

        val ToolbarButtonPaddingVertical
        private val ToolbarButtonPaddingVertical
            get() = 16.adjustedDp

        val ButtonPadding =
            PaddingValues(
                vertical = ToolbarButtonPaddingVertical,
                vertical = if (hubEditModeTransition()) 0.dp else ToolbarButtonPaddingVertical,
                horizontal = ToolbarButtonPaddingHorizontal,
            )
        val IconSize = 40.adjustedDp
+7 −2
Original line number Diff line number Diff line
@@ -55,6 +55,9 @@ import com.android.systemui.communal.util.WindowSizeUtils.COMPACT_WIDTH
 * Renders a responsive [LazyHorizontalGrid] with dynamic columns and rows. Each cell will maintain
 * the specified aspect ratio, but is otherwise resizeable in order to best fill the available
 * space.
 *
 * @param cellAspectRatio The aspect ratio (width / height) for each cell. Use 0f for flexible
 *   aspect ratio, allowing cells to fill all available space.
 */
@Composable
fun ResponsiveLazyHorizontalGrid(
@@ -70,7 +73,6 @@ fun ResponsiveLazyHorizontalGrid(
    overscrollEffect: OverscrollEffect? = rememberOverscrollEffect(),
    content: LazyGridScope.(sizeInfo: SizeInfo) -> Unit,
) {
    check(cellAspectRatio > 0f) { "Aspect ratio must be greater than 0, but was $cellAspectRatio" }
    check(minHorizontalArrangement.value >= 0f && minVerticalArrangement.value >= 0f) {
        "Horizontal and vertical arrangements must be non-negative, but were " +
            "$minHorizontalArrangement and $minVerticalArrangement, respectively."
@@ -195,7 +197,10 @@ private fun calculateUsedSpace(cellSize: Dp, numCells: Int, padding: Dp, cellSpa
    cellSize * numCells + padding + (numCells - 1) * cellSpacing

private fun calculateClosestSize(maxWidth: Dp, maxHeight: Dp, aspectRatio: Float): DpSize {
    return if (maxWidth / maxHeight > aspectRatio) {
    return if (aspectRatio <= 0f) {
        // Flexible aspect ratio. Allow cell to fill max width and height.
        DpSize(maxWidth, maxHeight)
    } else if (maxWidth / maxHeight > aspectRatio) {
        // Target is too wide, shrink width
        DpSize(maxHeight * aspectRatio, maxHeight)
    } else {