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

Commit b8a2ee16 authored by Darrell Shi's avatar Darrell Shi
Browse files

Reimplement CommunalHub with LazyHorizontalGrid

This change strips out dependency on the CommunalLayoutLib, and
reimplements the UI layer with LazyHorizontalGrid. Now the Compose UI
layer simply renders cards at the given size in the given order. It's up
to upstream layers to decide the sizes and ordering.

Test: verified that CommunalHub renders all three sizes correctly, and
there is no visual delta
Test: veriified that as cards are added / removed, existing cards
maintain state
Bug: 307942173
Fix: 307942173
Flag: ACONFIG com.android.systemui.communal_hub DEVELOPMENT

Change-Id: Ie807b1f1c2e776156ce2c402f372a5bf55064655
parent e619e972
Loading
Loading
Loading
Loading
+0 −1
Original line number Original line Diff line number Diff line
@@ -60,7 +60,6 @@ systemui_compose_java_defaults {
            // except for SystemUI-core.
            // except for SystemUI-core.
            // Copied from compose/features/Android.bp.
            // Copied from compose/features/Android.bp.
            static_libs: [
            static_libs: [
                "CommunalLayoutLib",
                "PlatformComposeCore",
                "PlatformComposeCore",
                "PlatformComposeSceneTransitionLayout",
                "PlatformComposeSceneTransitionLayout",


+86 −61
Original line number Original line Diff line number Diff line
@@ -4,8 +4,14 @@ import android.appwidget.AppWidgetHostView
import android.os.Bundle
import android.os.Bundle
import android.util.SizeF
import android.util.SizeF
import androidx.compose.foundation.background
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
import androidx.compose.material3.Card
import androidx.compose.material3.Card
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.collectAsState
@@ -13,16 +19,12 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.res.integerResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.viewinterop.AndroidView
import com.android.systemui.communal.layout.ui.compose.CommunalGridLayout
import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard
import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutConfig
import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.ui.model.CommunalContentUiModel
import com.android.systemui.communal.ui.model.CommunalContentUiModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.res.R


@Composable
@Composable
fun CommunalHub(
fun CommunalHub(
@@ -34,68 +36,91 @@ fun CommunalHub(
    Box(
    Box(
        modifier = modifier.fillMaxSize().background(Color.White),
        modifier = modifier.fillMaxSize().background(Color.White),
    ) {
    ) {
        CommunalGridLayout(
        LazyHorizontalGrid(
            modifier = Modifier.align(Alignment.CenterStart),
            modifier = modifier.height(Dimensions.GridHeight).align(Alignment.CenterStart),
            layoutConfig =
            rows = GridCells.Fixed(CommunalContentSize.FULL.span),
                CommunalGridLayoutConfig(
            horizontalArrangement = Arrangement.spacedBy(Dimensions.Spacing),
                    gridColumnSize = dimensionResource(R.dimen.communal_grid_column_size),
            verticalArrangement = Arrangement.spacedBy(Dimensions.Spacing),
                    gridGutter = dimensionResource(R.dimen.communal_grid_gutter_size),
        ) {
                    gridHeight = dimensionResource(R.dimen.communal_grid_height),
            if (showTutorial) {
                    gridColumnsPerCard = integerResource(R.integer.communal_grid_columns_per_card),
                items(
                ),
                    count = tutorialContentSizes.size,
            communalCards = if (showTutorial) tutorialContent else widgetContent.map(::contentCard),
                    // TODO(b/308148193): a more scalable solution for unique ids.
                    key = { index -> "tutorial_$index" },
                    span = { index -> GridItemSpan(tutorialContentSizes[index].span) },
                ) { index ->
                    TutorialCard(
                        modifier =
                            Modifier.size(Dimensions.CardWidth, tutorialContentSizes[index].dp()),
                    )
                    )
                }
                }
}
            } else {

                items(
private val tutorialContent =
                    count = widgetContent.size,
    listOf(
                    key = { index -> widgetContent[index].id },
        tutorialCard(CommunalGridLayoutCard.Size.FULL),
                    span = { index -> GridItemSpan(widgetContent[index].size.span) },
        tutorialCard(CommunalGridLayoutCard.Size.THIRD),
                ) { index ->
        tutorialCard(CommunalGridLayoutCard.Size.THIRD),
                    val widget = widgetContent[index]
        tutorialCard(CommunalGridLayoutCard.Size.THIRD),
                    ContentCard(
        tutorialCard(CommunalGridLayoutCard.Size.HALF),
                        modifier = Modifier.size(Dimensions.CardWidth, widget.size.dp()),
        tutorialCard(CommunalGridLayoutCard.Size.HALF),
                        model = widget,
        tutorialCard(CommunalGridLayoutCard.Size.HALF),
        tutorialCard(CommunalGridLayoutCard.Size.HALF),
                    )
                    )

                }
private fun tutorialCard(size: CommunalGridLayoutCard.Size): CommunalGridLayoutCard {
            }
    return object : CommunalGridLayoutCard() {
        override val supportedSizes = listOf(size)

        @Composable
        override fun Content(modifier: Modifier, size: SizeF) {
            Card(modifier = modifier, content = {})
        }
        }
    }
    }
}
}


private fun contentCard(model: CommunalContentUiModel): CommunalGridLayoutCard {
// A placeholder for tutorial content.
    return object : CommunalGridLayoutCard() {
@Composable
        override val supportedSizes = listOf(convertToCardSize(model.size))
private fun TutorialCard(modifier: Modifier = Modifier) {
        override val priority = model.priority
    Card(modifier = modifier, content = {})
}


@Composable
@Composable
        override fun Content(modifier: Modifier, size: SizeF) {
private fun ContentCard(
    model: CommunalContentUiModel,
    modifier: Modifier = Modifier,
) {
    AndroidView(
    AndroidView(
        modifier = modifier,
        modifier = modifier,
        factory = {
        factory = {
            model.view.apply {
            model.view.apply {
                if (this is AppWidgetHostView) {
                if (this is AppWidgetHostView) {
                            updateAppWidgetSize(Bundle(), listOf(size))
                    val size = SizeF(Dimensions.CardWidth.value, model.size.dp().value)
                    updateAppWidgetSize(Bundle.EMPTY, listOf(size))
                }
                }
            }
            }
        },
        },
    )
    )
}
}

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


private fun convertToCardSize(size: CommunalContentSize): CommunalGridLayoutCard.Size {
// Sizes for the tutorial placeholders.
    return when (size) {
private val tutorialContentSizes =
        CommunalContentSize.FULL -> CommunalGridLayoutCard.Size.FULL
    listOf(
        CommunalContentSize.HALF -> CommunalGridLayoutCard.Size.HALF
        CommunalContentSize.FULL,
        CommunalContentSize.THIRD -> CommunalGridLayoutCard.Size.THIRD
        CommunalContentSize.THIRD,
    }
        CommunalContentSize.THIRD,
        CommunalContentSize.THIRD,
        CommunalContentSize.HALF,
        CommunalContentSize.HALF,
        CommunalContentSize.HALF,
        CommunalContentSize.HALF,
    )

private object Dimensions {
    val CardWidth = 464.dp
    val CardHeightFull = 630.dp
    val CardHeightHalf = 307.dp
    val CardHeightThird = 199.dp
    val GridHeight = CardHeightFull
    val Spacing = 16.dp
}
}
+10 −5
Original line number Original line Diff line number Diff line
@@ -16,14 +16,19 @@


package com.android.systemui.communal.shared.model
package com.android.systemui.communal.shared.model


/** Supported sizes for communal content in the layout grid. */
/**
enum class CommunalContentSize {
 * Supported sizes for communal content in the layout grid.
 *
 * @param span The span of the content in a column. For example, if FULL is 6, then 3 represents
 *   HALF, 2 represents THIRD, and 1 represents SIXTH.
 */
enum class CommunalContentSize(val span: Int) {
    /** Content takes the full height of the column. */
    /** Content takes the full height of the column. */
    FULL,
    FULL(6),


    /** Content takes half of the height of the column. */
    /** Content takes half of the height of the column. */
    HALF,
    HALF(3),


    /** Content takes a third of the height of the column. */
    /** Content takes a third of the height of the column. */
    THIRD,
    THIRD(2),
}
}
+2 −2
Original line number Original line Diff line number Diff line
@@ -9,7 +9,7 @@ import com.android.systemui.communal.shared.model.CommunalContentSize
 * This model stays in the UI layer.
 * This model stays in the UI layer.
 */
 */
data class CommunalContentUiModel(
data class CommunalContentUiModel(
    val id: String,
    val view: View,
    val view: View,
    val size: CommunalContentSize,
    val size: CommunalContentSize = CommunalContentSize.HALF,
    val priority: Int,
)
)
+7 −8
Original line number Original line Diff line number Diff line
@@ -20,7 +20,6 @@ import android.appwidget.AppWidgetHost
import android.content.Context
import android.content.Context
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor
import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor
import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.ui.model.CommunalContentUiModel
import com.android.systemui.communal.ui.model.CommunalContentUiModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Application
@@ -42,16 +41,16 @@ constructor(


    /** List of widgets to be displayed in the communal hub. */
    /** List of widgets to be displayed in the communal hub. */
    val widgetContent: Flow<List<CommunalContentUiModel>> =
    val widgetContent: Flow<List<CommunalContentUiModel>> =
        communalInteractor.widgetContent.map {
        communalInteractor.widgetContent.map { widgets ->
            it.map {
            widgets.map Widget@{ widget ->
                // TODO(b/306406256): As adding and removing widgets functionalities are
                // TODO(b/306406256): As adding and removing widgets functionalities are
                // supported, cache the host views so they're not recreated each time.
                // supported, cache the host views so they're not recreated each time.
                val hostView = appWidgetHost.createView(context, it.appWidgetId, it.providerInfo)
                val hostView =
                return@map CommunalContentUiModel(
                    appWidgetHost.createView(context, widget.appWidgetId, widget.providerInfo)
                return@Widget CommunalContentUiModel(
                    // TODO(b/308148193): a more scalable solution for unique ids.
                    id = "widget_${widget.appWidgetId}",
                    view = hostView,
                    view = hostView,
                    priority = it.priority,
                    // All widgets have HALF size.
                    size = CommunalContentSize.HALF,
                )
                )
            }
            }
        }
        }