Loading packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +160 −54 Original line number Diff line number Diff line Loading @@ -95,7 +95,6 @@ import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.FilledIconButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButtonColors Loading Loading @@ -202,6 +201,7 @@ import com.android.systemui.communal.ui.viewmodel.ResizeInfo import com.android.systemui.communal.ui.viewmodel.ResizeableItemFrameViewModel import com.android.systemui.communal.util.DensityUtils.Companion.adjustedDp import com.android.systemui.communal.util.ResizeUtils.resizeOngoingItems import com.android.systemui.communal.util.WindowSizeUtils import com.android.systemui.communal.widgets.SmartspaceAppWidgetHostView import com.android.systemui.communal.widgets.WidgetConfigurator import com.android.systemui.lifecycle.rememberViewModel Loading Loading @@ -765,24 +765,62 @@ fun calculateWidgetSize( } } /** * Calculates the padding needed to center widgets within the window. It dynamically adjusts for * orientation and window insets. */ @Composable private fun horizontalPaddingWithInsets(padding: Dp): Dp { private fun responsiveGridPaddingsWithInsets( horizontalPadding: Dp = 0.dp, verticalPadding: Dp = 0.dp, isEditMode: Boolean = false, ): PaddingValues { val orientation = LocalConfiguration.current.orientation val displayCutoutPaddings = WindowInsets.displayCutout.asPaddingValues() val horizontalDisplayCutoutPadding = remember(orientation, displayCutoutPaddings) { val layoutDirection = LocalLayoutDirection.current return remember( orientation, displayCutoutPaddings, isEditMode, horizontalPadding, verticalPadding, ) { if (isEditMode) { PaddingValues( horizontal = horizontalPadding + if (orientation == Configuration.ORIENTATION_LANDSCAPE) { // In edit mode activity, display cutout location changes with // orientation. maxOf( displayCutoutPaddings.calculateLeftPadding(layoutDirection), displayCutoutPaddings.calculateRightPadding(layoutDirection), ) } else { 0.dp }, vertical = verticalPadding, ) } else { PaddingValues( horizontal = horizontalPadding + if (orientation == Configuration.ORIENTATION_LANDSCAPE) { maxOf( // Top in portrait becomes startPadding (or endPadding) in landscape displayCutoutPaddings.calculateTopPadding(), // Bottom in portrait becomes endPadding (or startPadding) in landscape // Bottom in portrait becomes endPadding (or startPadding) in // landscape displayCutoutPaddings.calculateBottomPadding(), ) } else { 0.dp }, vertical = verticalPadding, ) } } return padding + horizontalDisplayCutoutPadding } @Composable Loading Loading @@ -866,6 +904,7 @@ private fun BoxScope.CommunalHubLazyGrid( var list = communalContent var dragDropState: GridDragDropState? = null var arrangementSpacing = Dimensions.ItemSpacing val windowSize = WindowSizeUtils.getWindowSizeCategory(LocalContext.current) if (viewModel.isEditMode && viewModel is CommunalEditModeViewModel) { list = contentListState.list // for drag & drop operations within the communal hub grid Loading Loading @@ -942,7 +981,10 @@ private fun BoxScope.CommunalHubLazyGrid( val size = SizeF(dpSize.width.value, dpSize.height.value) val selected = item.key == selectedKey.value val isResizable = if (item is CommunalContentModel.WidgetContent.Widget) { if ( item is CommunalContentModel.WidgetContent.Widget && windowSize != WindowSizeUtils.WindowSizeCategory.MOBILE_LANDSCAPE ) { item.providerInfo.resizeMode and AppWidgetProviderInfo.RESIZE_VERTICAL != 0 } else { false Loading @@ -955,6 +997,16 @@ private fun BoxScope.CommunalHubLazyGrid( ) { ResizeableItemFrameViewModel() } val itemAlpha = if (communalResponsiveGrid()) { val percentVisible by remember(gridState, index) { derivedStateOf { calculatePercentVisible(gridState, index) } } animateFloatAsState(percentVisible) } else { null } if (viewModel.isEditMode && dragDropState != null) { val isItemDragging = dragDropState.draggingItemKey == item.key val outlineAlpha by Loading Loading @@ -1005,7 +1057,13 @@ private fun BoxScope.CommunalHubLazyGrid( key = item.key, ) { isDragging -> CommunalContent( modifier = Modifier.requiredSize(dpSize), modifier = Modifier.requiredSize(dpSize).thenIf( item !is CommunalContentModel.WidgetPlaceholder && !isItemDragging ) { Modifier.graphicsLayer { alpha = itemAlpha?.value ?: 1f } }, model = item, viewModel = viewModel, size = size, Loading @@ -1020,17 +1078,6 @@ 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, Loading Loading @@ -1126,7 +1173,6 @@ private fun EmptyStateCta(contentPadding: PaddingValues, viewModel: BaseCommunal * 2) remove a widget from the grid and * 3) exit the edit mode. */ @OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable private fun Toolbar( removeEnabled: Boolean, Loading @@ -1145,16 +1191,10 @@ private fun Toolbar( targetValue = if (removeEnabled) 1f else 0.5f, label = "RemoveButtonAlphaAnimation", ) val toolbarPadding = toolbarPadding() Box( modifier = Modifier.fillMaxWidth() .padding( top = Dimensions.ToolbarPaddingTop, start = Dimensions.ToolbarPaddingHorizontal, end = Dimensions.ToolbarPaddingHorizontal, ) .onSizeChanged { setToolbarSize(it) } Modifier.fillMaxWidth().padding(toolbarPadding).onSizeChanged { setToolbarSize(it) } ) { val addWidgetText = stringResource(R.string.hub_mode_add_widget_button_text) Loading Loading @@ -1350,7 +1390,7 @@ fun HighlightedItem(modifier: Modifier = Modifier, alpha: Float = 1.0f) { // resize grid items to account for the border. modifier.drawBehind { // 8dp of padding between the widget and the highlight on every side. val padding = 8.adjustedDp.toPx() val padding = Dimensions.WidgetOutlinePadding.toPx() drawRoundRect( brush, alpha = alpha, Loading Loading @@ -1891,13 +1931,11 @@ private fun nonScalableTextSize(sizeInDp: Dp) = with(LocalDensity.current) { siz private fun gridContentPadding(isEditMode: Boolean, toolbarSize: IntSize?): PaddingValues { if (!isEditMode || toolbarSize == null) { return if (communalResponsiveGrid()) { val horizontalPaddings: Dp = if (isCompactWindow()) { horizontalPaddingWithInsets(Dimensions.ItemSpacingCompact) responsiveGridPaddingsWithInsets(Dimensions.ItemSpacingCompact) } else { Dimensions.ItemSpacing PaddingValues(horizontal = Dimensions.ItemSpacing) } PaddingValues(start = horizontalPaddings, end = horizontalPaddings) } else { PaddingValues( start = Dimensions.ItemSpacing, Loading @@ -1910,16 +1948,31 @@ 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) { Dimensions.ToolbarPaddingTop + toolbarSize.height.toDp() } val toolbarHeight = with(density) { toolbarSize.height.toDp() } return if (communalResponsiveGrid()) { PaddingValues( start = Dimensions.ToolbarPaddingHorizontal, end = Dimensions.ToolbarPaddingHorizontal, top = hubDimensions.GridTopSpacing, // In edit mode, grid spans full screen so min top padding of the grid should include space // taken by toolbar. val toolbarPadding = toolbarPadding() // Add extra padding to render the widget outline which draws behind the selected widget. val topPadding = toolbarPadding.calculateTopPadding() + toolbarHeight + toolbarPadding.calculateBottomPadding() + Dimensions.WidgetOutlinePadding responsiveGridPaddingsWithInsets( horizontalPadding = if (isCompactWindow()) { Dimensions.ItemSpacingCompact + Dimensions.WidgetOutlinePadding } else { Dimensions.ItemSpacing }, verticalPadding = topPadding, isEditMode = true, ) } else { val toolbarHeightWithTopPadding = toolbarHeight + Dimensions.ToolbarPaddingTop val verticalPadding = ((screenHeight - toolbarHeight - hubDimensions.GridHeight + ((screenHeight - toolbarHeightWithTopPadding - hubDimensions.GridHeight + hubDimensions.GridTopSpacing) / 2) .coerceAtLeast(Dimensions.Spacing) PaddingValues( Loading Loading @@ -1949,6 +2002,33 @@ private fun CommunalContentSize.FixedSize.dp(): Dp { } } @Composable private fun toolbarPadding(): PaddingValues { if (!communalResponsiveGrid()) { return PaddingValues( top = Dimensions.ToolbarPaddingTop, start = Dimensions.ToolbarPaddingHorizontal, end = Dimensions.ToolbarPaddingHorizontal, ) } val displayCutoutPaddings = WindowInsets.displayCutout.asPaddingValues() val horizontalPadding = hubDimensions.toolbarHorizontalPadding val bottomPadding = hubDimensions.toolbarBottomPadding return remember(displayCutoutPaddings, horizontalPadding, bottomPadding) { // Depending on camera location, there can be no cutout paddings, set a min value val topPadding = displayCutoutPaddings.calculateTopPadding().coerceAtLeast(Dimensions.ToolbarPaddingTop) PaddingValues( start = horizontalPadding, top = topPadding, end = horizontalPadding, bottom = bottomPadding, ) } } private fun firstIndexAtOffset(gridState: LazyGridState, offset: Offset): Int? = gridState.layoutInfo.visibleItemsInfo.firstItemAtOffset(offset)?.index Loading Loading @@ -1978,6 +2058,32 @@ class Dimensions(val context: Context, val config: Configuration) { val GridHeight: Dp get() = CardHeightFull + GridTopSpacing /** Responsive grid toolbar bottom padding. */ val toolbarBottomPadding: Dp get() { val windowSizeCategory = WindowSizeUtils.getWindowSizeCategory(context) return if (windowSizeCategory == WindowSizeUtils.WindowSizeCategory.MOBILE_LANDSCAPE) { 6.adjustedDp } else { 0.adjustedDp } } /** Responsive grid toolbar horizontal padding. */ val toolbarHorizontalPadding: Dp get() { val windowSizeCategory = WindowSizeUtils.getWindowSizeCategory(context) return if (windowSizeCategory == WindowSizeUtils.WindowSizeCategory.TABLET) { if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) { 18.adjustedDp } else { 12.adjustedDp } } else { 9.adjustedDp } } companion object { val CardHeightFull get() = 530.adjustedDp Loading @@ -1997,15 +2103,15 @@ class Dimensions(val context: Context, val config: Configuration) { val CardWidth get() = 360.adjustedDp val CardOutlineWidth get() = 3.adjustedDp val WidgetOutlinePadding get() = 8.adjustedDp val Spacing get() = ItemSpacing / 2 // The sizing/padding of the toolbar in glanceable hub edit mode val ToolbarPaddingTop get() = 27.adjustedDp get() = if (communalResponsiveGrid()) 12.adjustedDp else 27.adjustedDp val ToolbarPaddingHorizontal get() = ItemSpacing Loading Loading
packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +160 −54 Original line number Diff line number Diff line Loading @@ -95,7 +95,6 @@ import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.FilledIconButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButtonColors Loading Loading @@ -202,6 +201,7 @@ import com.android.systemui.communal.ui.viewmodel.ResizeInfo import com.android.systemui.communal.ui.viewmodel.ResizeableItemFrameViewModel import com.android.systemui.communal.util.DensityUtils.Companion.adjustedDp import com.android.systemui.communal.util.ResizeUtils.resizeOngoingItems import com.android.systemui.communal.util.WindowSizeUtils import com.android.systemui.communal.widgets.SmartspaceAppWidgetHostView import com.android.systemui.communal.widgets.WidgetConfigurator import com.android.systemui.lifecycle.rememberViewModel Loading Loading @@ -765,24 +765,62 @@ fun calculateWidgetSize( } } /** * Calculates the padding needed to center widgets within the window. It dynamically adjusts for * orientation and window insets. */ @Composable private fun horizontalPaddingWithInsets(padding: Dp): Dp { private fun responsiveGridPaddingsWithInsets( horizontalPadding: Dp = 0.dp, verticalPadding: Dp = 0.dp, isEditMode: Boolean = false, ): PaddingValues { val orientation = LocalConfiguration.current.orientation val displayCutoutPaddings = WindowInsets.displayCutout.asPaddingValues() val horizontalDisplayCutoutPadding = remember(orientation, displayCutoutPaddings) { val layoutDirection = LocalLayoutDirection.current return remember( orientation, displayCutoutPaddings, isEditMode, horizontalPadding, verticalPadding, ) { if (isEditMode) { PaddingValues( horizontal = horizontalPadding + if (orientation == Configuration.ORIENTATION_LANDSCAPE) { // In edit mode activity, display cutout location changes with // orientation. maxOf( displayCutoutPaddings.calculateLeftPadding(layoutDirection), displayCutoutPaddings.calculateRightPadding(layoutDirection), ) } else { 0.dp }, vertical = verticalPadding, ) } else { PaddingValues( horizontal = horizontalPadding + if (orientation == Configuration.ORIENTATION_LANDSCAPE) { maxOf( // Top in portrait becomes startPadding (or endPadding) in landscape displayCutoutPaddings.calculateTopPadding(), // Bottom in portrait becomes endPadding (or startPadding) in landscape // Bottom in portrait becomes endPadding (or startPadding) in // landscape displayCutoutPaddings.calculateBottomPadding(), ) } else { 0.dp }, vertical = verticalPadding, ) } } return padding + horizontalDisplayCutoutPadding } @Composable Loading Loading @@ -866,6 +904,7 @@ private fun BoxScope.CommunalHubLazyGrid( var list = communalContent var dragDropState: GridDragDropState? = null var arrangementSpacing = Dimensions.ItemSpacing val windowSize = WindowSizeUtils.getWindowSizeCategory(LocalContext.current) if (viewModel.isEditMode && viewModel is CommunalEditModeViewModel) { list = contentListState.list // for drag & drop operations within the communal hub grid Loading Loading @@ -942,7 +981,10 @@ private fun BoxScope.CommunalHubLazyGrid( val size = SizeF(dpSize.width.value, dpSize.height.value) val selected = item.key == selectedKey.value val isResizable = if (item is CommunalContentModel.WidgetContent.Widget) { if ( item is CommunalContentModel.WidgetContent.Widget && windowSize != WindowSizeUtils.WindowSizeCategory.MOBILE_LANDSCAPE ) { item.providerInfo.resizeMode and AppWidgetProviderInfo.RESIZE_VERTICAL != 0 } else { false Loading @@ -955,6 +997,16 @@ private fun BoxScope.CommunalHubLazyGrid( ) { ResizeableItemFrameViewModel() } val itemAlpha = if (communalResponsiveGrid()) { val percentVisible by remember(gridState, index) { derivedStateOf { calculatePercentVisible(gridState, index) } } animateFloatAsState(percentVisible) } else { null } if (viewModel.isEditMode && dragDropState != null) { val isItemDragging = dragDropState.draggingItemKey == item.key val outlineAlpha by Loading Loading @@ -1005,7 +1057,13 @@ private fun BoxScope.CommunalHubLazyGrid( key = item.key, ) { isDragging -> CommunalContent( modifier = Modifier.requiredSize(dpSize), modifier = Modifier.requiredSize(dpSize).thenIf( item !is CommunalContentModel.WidgetPlaceholder && !isItemDragging ) { Modifier.graphicsLayer { alpha = itemAlpha?.value ?: 1f } }, model = item, viewModel = viewModel, size = size, Loading @@ -1020,17 +1078,6 @@ 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, Loading Loading @@ -1126,7 +1173,6 @@ private fun EmptyStateCta(contentPadding: PaddingValues, viewModel: BaseCommunal * 2) remove a widget from the grid and * 3) exit the edit mode. */ @OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable private fun Toolbar( removeEnabled: Boolean, Loading @@ -1145,16 +1191,10 @@ private fun Toolbar( targetValue = if (removeEnabled) 1f else 0.5f, label = "RemoveButtonAlphaAnimation", ) val toolbarPadding = toolbarPadding() Box( modifier = Modifier.fillMaxWidth() .padding( top = Dimensions.ToolbarPaddingTop, start = Dimensions.ToolbarPaddingHorizontal, end = Dimensions.ToolbarPaddingHorizontal, ) .onSizeChanged { setToolbarSize(it) } Modifier.fillMaxWidth().padding(toolbarPadding).onSizeChanged { setToolbarSize(it) } ) { val addWidgetText = stringResource(R.string.hub_mode_add_widget_button_text) Loading Loading @@ -1350,7 +1390,7 @@ fun HighlightedItem(modifier: Modifier = Modifier, alpha: Float = 1.0f) { // resize grid items to account for the border. modifier.drawBehind { // 8dp of padding between the widget and the highlight on every side. val padding = 8.adjustedDp.toPx() val padding = Dimensions.WidgetOutlinePadding.toPx() drawRoundRect( brush, alpha = alpha, Loading Loading @@ -1891,13 +1931,11 @@ private fun nonScalableTextSize(sizeInDp: Dp) = with(LocalDensity.current) { siz private fun gridContentPadding(isEditMode: Boolean, toolbarSize: IntSize?): PaddingValues { if (!isEditMode || toolbarSize == null) { return if (communalResponsiveGrid()) { val horizontalPaddings: Dp = if (isCompactWindow()) { horizontalPaddingWithInsets(Dimensions.ItemSpacingCompact) responsiveGridPaddingsWithInsets(Dimensions.ItemSpacingCompact) } else { Dimensions.ItemSpacing PaddingValues(horizontal = Dimensions.ItemSpacing) } PaddingValues(start = horizontalPaddings, end = horizontalPaddings) } else { PaddingValues( start = Dimensions.ItemSpacing, Loading @@ -1910,16 +1948,31 @@ 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) { Dimensions.ToolbarPaddingTop + toolbarSize.height.toDp() } val toolbarHeight = with(density) { toolbarSize.height.toDp() } return if (communalResponsiveGrid()) { PaddingValues( start = Dimensions.ToolbarPaddingHorizontal, end = Dimensions.ToolbarPaddingHorizontal, top = hubDimensions.GridTopSpacing, // In edit mode, grid spans full screen so min top padding of the grid should include space // taken by toolbar. val toolbarPadding = toolbarPadding() // Add extra padding to render the widget outline which draws behind the selected widget. val topPadding = toolbarPadding.calculateTopPadding() + toolbarHeight + toolbarPadding.calculateBottomPadding() + Dimensions.WidgetOutlinePadding responsiveGridPaddingsWithInsets( horizontalPadding = if (isCompactWindow()) { Dimensions.ItemSpacingCompact + Dimensions.WidgetOutlinePadding } else { Dimensions.ItemSpacing }, verticalPadding = topPadding, isEditMode = true, ) } else { val toolbarHeightWithTopPadding = toolbarHeight + Dimensions.ToolbarPaddingTop val verticalPadding = ((screenHeight - toolbarHeight - hubDimensions.GridHeight + ((screenHeight - toolbarHeightWithTopPadding - hubDimensions.GridHeight + hubDimensions.GridTopSpacing) / 2) .coerceAtLeast(Dimensions.Spacing) PaddingValues( Loading Loading @@ -1949,6 +2002,33 @@ private fun CommunalContentSize.FixedSize.dp(): Dp { } } @Composable private fun toolbarPadding(): PaddingValues { if (!communalResponsiveGrid()) { return PaddingValues( top = Dimensions.ToolbarPaddingTop, start = Dimensions.ToolbarPaddingHorizontal, end = Dimensions.ToolbarPaddingHorizontal, ) } val displayCutoutPaddings = WindowInsets.displayCutout.asPaddingValues() val horizontalPadding = hubDimensions.toolbarHorizontalPadding val bottomPadding = hubDimensions.toolbarBottomPadding return remember(displayCutoutPaddings, horizontalPadding, bottomPadding) { // Depending on camera location, there can be no cutout paddings, set a min value val topPadding = displayCutoutPaddings.calculateTopPadding().coerceAtLeast(Dimensions.ToolbarPaddingTop) PaddingValues( start = horizontalPadding, top = topPadding, end = horizontalPadding, bottom = bottomPadding, ) } } private fun firstIndexAtOffset(gridState: LazyGridState, offset: Offset): Int? = gridState.layoutInfo.visibleItemsInfo.firstItemAtOffset(offset)?.index Loading Loading @@ -1978,6 +2058,32 @@ class Dimensions(val context: Context, val config: Configuration) { val GridHeight: Dp get() = CardHeightFull + GridTopSpacing /** Responsive grid toolbar bottom padding. */ val toolbarBottomPadding: Dp get() { val windowSizeCategory = WindowSizeUtils.getWindowSizeCategory(context) return if (windowSizeCategory == WindowSizeUtils.WindowSizeCategory.MOBILE_LANDSCAPE) { 6.adjustedDp } else { 0.adjustedDp } } /** Responsive grid toolbar horizontal padding. */ val toolbarHorizontalPadding: Dp get() { val windowSizeCategory = WindowSizeUtils.getWindowSizeCategory(context) return if (windowSizeCategory == WindowSizeUtils.WindowSizeCategory.TABLET) { if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) { 18.adjustedDp } else { 12.adjustedDp } } else { 9.adjustedDp } } companion object { val CardHeightFull get() = 530.adjustedDp Loading @@ -1997,15 +2103,15 @@ class Dimensions(val context: Context, val config: Configuration) { val CardWidth get() = 360.adjustedDp val CardOutlineWidth get() = 3.adjustedDp val WidgetOutlinePadding get() = 8.adjustedDp val Spacing get() = ItemSpacing / 2 // The sizing/padding of the toolbar in glanceable hub edit mode val ToolbarPaddingTop get() = 27.adjustedDp get() = if (communalResponsiveGrid()) 12.adjustedDp else 27.adjustedDp val ToolbarPaddingHorizontal get() = ItemSpacing Loading