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

Commit cefca220 authored by Lucas Silva's avatar Lucas Silva
Browse files

Update communal hub to use responsive grid

Flag: com.android.systemui.communal_responsive_grid
Bug: 378171351
Test: atest SystemUITests
Change-Id: Id312bbfba7f7240aa30997dd463d583cc25a12d4
parent 81b1558a
Loading
Loading
Loading
Loading
+94 −25
Original line number Diff line number Diff line
@@ -66,6 +66,7 @@ import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyGridScope
import androidx.compose.foundation.lazy.grid.LazyGridState
import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
import androidx.compose.foundation.lazy.grid.itemsIndexed
@@ -169,6 +170,7 @@ import com.android.compose.modifiers.thenIf
import com.android.compose.ui.graphics.painter.rememberDrawablePainter
import com.android.internal.R.dimen.system_app_widget_background_radius
import com.android.systemui.Flags
import com.android.systemui.Flags.communalResponsiveGrid
import com.android.systemui.Flags.communalTimerFlickerFix
import com.android.systemui.Flags.communalWidgetResizing
import com.android.systemui.communal.domain.model.CommunalContentModel
@@ -194,7 +196,6 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToInt
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

@@ -693,7 +694,12 @@ private fun ResizableItemFrameWrapper(
            onResize = onResize,
            minHeightPx = minHeightPx,
            maxHeightPx = maxHeightPx,
            resizeMultiple = CommunalContentSize.HALF.span,
            resizeMultiple =
                if (communalResponsiveGrid()) {
                    1
                } else {
                    CommunalContentSize.FixedSize.HALF.span
                },
        ) {
            content(Modifier)
        }
@@ -701,14 +707,22 @@ private fun ResizableItemFrameWrapper(
}

@Composable
fun calculateWidgetSize(item: CommunalContentModel, isResizable: Boolean): WidgetSizeInfo {
fun calculateWidgetSize(
    cellHeight: Dp?,
    availableHeight: Dp?,
    item: CommunalContentModel,
    isResizable: Boolean,
): WidgetSizeInfo {
    val density = LocalDensity.current

    val minHeight = cellHeight ?: CommunalContentSize.FixedSize.HALF.dp()
    val maxHeight = availableHeight ?: CommunalContentSize.FixedSize.FULL.dp()

    return if (isResizable && item is CommunalContentModel.WidgetContent.Widget) {
        with(density) {
            val minHeightPx =
                (min(item.providerInfo.minResizeHeight, item.providerInfo.minHeight)
                    .coerceAtLeast(CommunalContentSize.HALF.dp().toPx().roundToInt()))
                    .coerceAtLeast(minHeight.roundToPx()))

            val maxHeightPx =
                (if (item.providerInfo.maxResizeHeight > 0) {
@@ -716,7 +730,7 @@ fun calculateWidgetSize(item: CommunalContentModel, isResizable: Boolean): Widge
                    } else {
                        Int.MAX_VALUE
                    })
                    .coerceIn(minHeightPx, CommunalContentSize.FULL.dp().toPx().roundToInt())
                    .coerceIn(minHeightPx, maxHeight.roundToPx())

            WidgetSizeInfo(minHeightPx, maxHeightPx)
        }
@@ -725,6 +739,37 @@ fun calculateWidgetSize(item: CommunalContentModel, isResizable: Boolean): Widge
    }
}

@Composable
private fun HorizontalGridWrapper(
    contentPadding: PaddingValues,
    gridState: LazyGridState,
    modifier: Modifier = Modifier,
    content: LazyGridScope.(sizeInfo: SizeInfo?) -> Unit,
) {
    if (communalResponsiveGrid()) {
        ResponsiveLazyHorizontalGrid(
            cellAspectRatio = 1.5f,
            modifier = modifier,
            state = gridState,
            minContentPadding = contentPadding,
            minHorizontalArrangement = Dimensions.ItemSpacing,
            minVerticalArrangement = Dimensions.ItemSpacing,
            content = content,
        )
    } else {
        LazyHorizontalGrid(
            modifier = modifier,
            state = gridState,
            rows = GridCells.Fixed(CommunalContentSize.FixedSize.FULL.span),
            contentPadding = contentPadding,
            horizontalArrangement = Arrangement.spacedBy(Dimensions.ItemSpacing),
            verticalArrangement = Arrangement.spacedBy(Dimensions.ItemSpacing),
        ) {
            content(null)
        }
    }
}

@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun BoxScope.CommunalHubLazyGrid(
@@ -778,28 +823,32 @@ private fun BoxScope.CommunalHubLazyGrid(
        // Since the grid has its own listener for in-grid drag events, we use a separate element
        // for android drag events.
        Box(Modifier.fillMaxSize().dragAndDropTarget(dragAndDropTargetState)) {}
    } else if (communalResponsiveGrid()) {
        gridModifier = gridModifier.fillMaxSize()
    } else {
        gridModifier = gridModifier.height(hubDimensions.GridHeight)
    }

    val itemArrangement = Arrangement.spacedBy(Dimensions.ItemSpacing)
    LazyHorizontalGrid(
    HorizontalGridWrapper(
        modifier = gridModifier,
        state = gridState,
        rows = GridCells.Fixed(CommunalContentSize.FULL.span),
        gridState = gridState,
        contentPadding = contentPadding,
        horizontalArrangement = itemArrangement,
        verticalArrangement = itemArrangement,
    ) {
    ) { sizeInfo ->
        itemsIndexed(
            items = list,
            key = { _, item -> item.key },
            contentType = { _, item -> item.key },
            span = { _, item -> GridItemSpan(item.size.span) },
            span = { _, item -> GridItemSpan(item.getSpanOrMax(sizeInfo?.gridSize?.height)) },
        ) { index, item ->
            val size = SizeF(Dimensions.CardWidth.value, item.size.dp().value)
            val currentItemSpan = item.getSpanOrMax(sizeInfo?.gridSize?.height)
            val dpSize =
                if (sizeInfo != null) {
                    DpSize(sizeInfo.cellSize.width, sizeInfo.calculateHeight(currentItemSpan))
                } else {
                    DpSize(Dimensions.CardWidth, (item.size as CommunalContentSize.FixedSize).dp())
                }
            val size = SizeF(dpSize.width.value, dpSize.height.value)
            val selected = item.key == selectedKey.value
            val dpSize = DpSize(size.width.dp, size.height.dp)
            val isResizable =
                if (item is CommunalContentModel.WidgetContent.Widget) {
                    item.providerInfo.resizeMode and AppWidgetProviderInfo.RESIZE_VERTICAL != 0
@@ -809,7 +858,7 @@ private fun BoxScope.CommunalHubLazyGrid(

            val resizeableItemFrameViewModel =
                rememberViewModel(
                    key = item.size.span,
                    key = currentItemSpan,
                    traceName = "ResizeableItemFrame.viewModel.$index",
                ) {
                    ResizeableItemFrameViewModel()
@@ -822,13 +871,23 @@ private fun BoxScope.CommunalHubLazyGrid(
                        animationSpec = spring(stiffness = Spring.StiffnessMediumLow),
                        label = "Widget resizing outline alpha",
                    )
                val widgetSizeInfo = calculateWidgetSize(item, isResizable)

                val widgetSizeInfo =
                    calculateWidgetSize(
                        cellHeight = sizeInfo?.cellSize?.height,
                        availableHeight = sizeInfo?.availableHeight,
                        item = item,
                        isResizable = isResizable,
                    )
                ResizableItemFrameWrapper(
                    key = item.key,
                    currentSpan = GridItemSpan(item.size.span),
                    currentSpan = GridItemSpan(currentItemSpan),
                    gridState = gridState,
                    gridContentPadding = contentPadding,
                    verticalArrangement = itemArrangement,
                    verticalArrangement =
                        Arrangement.spacedBy(
                            sizeInfo?.verticalArrangement ?: Dimensions.ItemSpacing
                        ),
                    enabled = selected,
                    alpha = { outlineAlpha },
                    modifier =
@@ -1686,11 +1745,11 @@ private fun beforeContentPadding(paddingValues: PaddingValues): ContentPaddingIn
    }
}

private fun CommunalContentSize.dp(): Dp {
private fun CommunalContentSize.FixedSize.dp(): Dp {
    return when (this) {
        CommunalContentSize.FULL -> Dimensions.CardHeightFull
        CommunalContentSize.HALF -> Dimensions.CardHeightHalf
        CommunalContentSize.THIRD -> Dimensions.CardHeightThird
        CommunalContentSize.FixedSize.FULL -> Dimensions.CardHeightFull
        CommunalContentSize.FixedSize.HALF -> Dimensions.CardHeightHalf
        CommunalContentSize.FixedSize.THIRD -> Dimensions.CardHeightThird
    }
}

@@ -1709,7 +1768,10 @@ class Dimensions(val context: Context, val config: Configuration) {
    val GridTopSpacing: Dp
        get() {
            val result =
                if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
                if (
                    communalResponsiveGrid() ||
                        config.orientation == Configuration.ORIENTATION_LANDSCAPE
                ) {
                    114.dp
                } else {
                    val windowMetrics =
@@ -1729,7 +1791,7 @@ class Dimensions(val context: Context, val config: Configuration) {
            get() = 530.adjustedDp

        val ItemSpacing
            get() = 50.adjustedDp
            get() = if (communalResponsiveGrid()) 32.adjustedDp else 50.adjustedDp

        val CardHeightHalf
            get() = (CardHeightFull - ItemSpacing) / 2
@@ -1771,6 +1833,13 @@ class Dimensions(val context: Context, val config: Configuration) {

data class WidgetSizeInfo(val minHeightPx: Int, val maxHeightPx: Int)

private fun CommunalContentModel.getSpanOrMax(maxSpan: Int?) =
    if (maxSpan != null) {
        size.span.coerceAtMost(maxSpan)
    } else {
        size.span
    }

private object Colors {
    val DisabledColorFilter by lazy { disabledColorMatrix() }

+9 −5
Original line number Diff line number Diff line
@@ -147,9 +147,9 @@ fun ResponsiveLazyHorizontalGrid(
                SizeInfo(
                    cellSize = finalSize,
                    contentPadding = finalContentPadding,
                    horizontalArrangement = minHorizontalArrangement,
                    verticalArrangement = minVerticalArrangement,
                    maxHeight = maxHeight,
                    gridSize = gridSize,
                )
            )
        }
@@ -176,16 +176,15 @@ private fun calculateClosestSize(maxWidth: Dp, maxHeight: Dp, aspectRatio: Float
 * Provides size info of the responsive grid, since the size is dynamic.
 *
 * @property cellSize The size of each cell in the grid.
 * @property contentPadding The final content padding of the grid.
 * @property horizontalArrangement The space between columns in the grid.
 * @property verticalArrangement The space between rows in the grid.
 * @property gridSize The size of the grid, in cell units.
 * @property availableHeight The maximum height an item in the grid may occupy.
 */
data class SizeInfo(
    val cellSize: DpSize,
    val contentPadding: PaddingValues,
    val horizontalArrangement: Dp,
    val verticalArrangement: Dp,
    val gridSize: IntSize,
    private val contentPadding: PaddingValues,
    private val maxHeight: Dp,
) {
    val availableHeight: Dp
@@ -193,6 +192,11 @@ data class SizeInfo(
            maxHeight -
                contentPadding.calculateBottomPadding() -
                contentPadding.calculateTopPadding()

    /** Calculates the height in dp of a certain number of rows. */
    fun calculateHeight(numRows: Int): Dp {
        return numRows * cellSize.height + (numRows - 1) * verticalArrangement
    }
}

@Composable
+191 −50

File changed.

Preview size limit exceeded, changes collapsed.

+13 −6
Original line number Diff line number Diff line
@@ -18,12 +18,14 @@ package com.android.systemui.communal.view.viewmodel

import android.content.ComponentName
import android.content.pm.UserInfo
import android.platform.test.annotations.DisableFlags
import android.platform.test.flag.junit.FlagsParameterization
import android.provider.Settings
import android.widget.RemoteViews
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.Flags.FLAG_COMMUNAL_RESPONSIVE_GRID
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.model.CommunalSmartspaceTimer
import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
@@ -248,7 +250,9 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
                .isInstanceOf(CommunalContentModel.CtaTileInViewMode::class.java)
        }

    /** TODO(b/378171351): Handle ongoing content in responsive grid. */
    @Test
    @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID)
    fun ongoingContent_umoAndOneTimer_sizedAppropriately() =
        testScope.runTest {
            // Widgets available.
@@ -280,11 +284,13 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
            assertThat(timer).isInstanceOf(CommunalContentModel.Smartspace::class.java)
            assertThat(umo).isInstanceOf(CommunalContentModel.Umo::class.java)

            assertThat(timer?.size).isEqualTo(CommunalContentSize.HALF)
            assertThat(umo?.size).isEqualTo(CommunalContentSize.HALF)
            assertThat(timer?.size).isEqualTo(CommunalContentSize.FixedSize.HALF)
            assertThat(umo?.size).isEqualTo(CommunalContentSize.FixedSize.HALF)
        }

    /** TODO(b/378171351): Handle ongoing content in responsive grid. */
    @Test
    @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID)
    fun ongoingContent_umoAndTwoTimers_sizedAppropriately() =
        testScope.runTest {
            // Widgets available.
@@ -324,9 +330,9 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
            assertThat(umo).isInstanceOf(CommunalContentModel.Umo::class.java)

            // One full-sized timer and a half-sized timer and half-sized UMO.
            assertThat(timer1?.size).isEqualTo(CommunalContentSize.HALF)
            assertThat(timer2?.size).isEqualTo(CommunalContentSize.HALF)
            assertThat(umo?.size).isEqualTo(CommunalContentSize.FULL)
            assertThat(timer1?.size).isEqualTo(CommunalContentSize.FixedSize.HALF)
            assertThat(timer2?.size).isEqualTo(CommunalContentSize.FixedSize.HALF)
            assertThat(umo?.size).isEqualTo(CommunalContentSize.FixedSize.FULL)
        }

    @Test
@@ -891,7 +897,8 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
        @JvmStatic
        @Parameters(name = "{0}")
        fun getParams(): List<FlagsParameterization> {
            return FlagsParameterization.allCombinationsOf().andSceneContainer()
            return FlagsParameterization.allCombinationsOf(FLAG_COMMUNAL_RESPONSIVE_GRID)
                .andSceneContainer()
        }
    }
}
+3 −3
Original line number Diff line number Diff line
@@ -34,9 +34,9 @@ import com.android.systemui.communal.data.repository.CommunalWidgetRepository
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.domain.model.CommunalContentModel.WidgetContent
import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.shared.model.CommunalContentSize.FULL
import com.android.systemui.communal.shared.model.CommunalContentSize.HALF
import com.android.systemui.communal.shared.model.CommunalContentSize.THIRD
import com.android.systemui.communal.shared.model.CommunalContentSize.FixedSize.FULL
import com.android.systemui.communal.shared.model.CommunalContentSize.FixedSize.HALF
import com.android.systemui.communal.shared.model.CommunalContentSize.FixedSize.THIRD
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.shared.model.EditModeState
Loading