Loading packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt +120 −6 Original line number Diff line number Diff line Loading @@ -16,34 +16,58 @@ package com.android.systemui.statusbar.phone import android.app.Dialog import android.content.Context import android.content.res.Configuration import android.os.Bundle import androidx.annotation.GravityInt import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.AnchoredDraggableDefaults import androidx.compose.foundation.gestures.AnchoredDraggableState import androidx.compose.foundation.gestures.DraggableAnchors import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.anchoredDraggable import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsDraggedAsState import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.isSpecified import com.android.compose.theme.PlatformTheme import com.android.systemui.keyboard.shortcut.ui.composable.hasCompactWindowSize import com.android.systemui.res.R import kotlin.math.roundToInt /** * Create a [SystemUIDialog] with the given [content]. Loading Loading @@ -97,6 +121,9 @@ fun SystemUIDialogFactory.createBottomSheet( theme: Int = R.style.Theme_SystemUI_BottomSheet, dismissOnDeviceLock: Boolean = SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK, content: @Composable (SystemUIDialog) -> Unit, isDraggable: Boolean = true, // TODO(b/337205027): remove maxWidth parameter when aligned to M3 spec maxWidth: Dp = Dp.Unspecified, ): ComponentSystemUIDialog { return create( context = context, Loading @@ -104,9 +131,49 @@ fun SystemUIDialogFactory.createBottomSheet( dismissOnDeviceLock = dismissOnDeviceLock, delegate = EdgeToEdgeDialogDelegate(), content = { dialog -> val dragState = if (isDraggable) remember { AnchoredDraggableState(initialValue = DragAnchors.Start) } else null val interactionSource = if (isDraggable) remember { MutableInteractionSource() } else null if (dragState != null) { val isDragged by interactionSource!!.collectIsDraggedAsState() LaunchedEffect(dragState.currentValue, isDragged) { if (!isDragged && dragState.currentValue == DragAnchors.End) dialog.dismiss() } } Box( modifier = Modifier.bottomSheetClickable { dialog.dismiss() }, contentAlignment = Alignment.BottomCenter modifier = Modifier.bottomSheetClickable { dialog.dismiss() } .then( if (isDraggable) Modifier.anchoredDraggable( state = dragState!!, interactionSource = interactionSource, orientation = Orientation.Vertical, flingBehavior = AnchoredDraggableDefaults.flingBehavior( state = dragState ), ) .offset { IntOffset(x = 0, y = dragState.requireOffset().roundToInt()) } .onSizeChanged { layoutSize -> val dragEndPoint = layoutSize.height - dialog.height dragState.updateAnchors( DraggableAnchors { DragAnchors.entries.forEach { anchor -> anchor at dragEndPoint * anchor.fraction } } ) } .padding(top = draggableTopPadding()) else Modifier // No-Op ), contentAlignment = Alignment.BottomCenter, ) { val radius = dimensionResource(R.dimen.bottom_sheet_corner_radius) Surface( Loading @@ -114,8 +181,11 @@ fun SystemUIDialogFactory.createBottomSheet( Modifier.bottomSheetPaddings() // consume input so it doesn't get to the parent Composable .bottomSheetClickable {} // TODO(b/337205027) change width .widthIn(max = 800.dp), .widthIn( max = if (maxWidth.isSpecified) maxWidth else DraggableBottomSheet.MaxWidth ), shape = RoundedCornerShape(topStart = radius, topEnd = radius), color = MaterialTheme.colorScheme.surfaceContainer, ) { Loading @@ -127,14 +197,29 @@ fun SystemUIDialogFactory.createBottomSheet( } ) ) { if (isDraggable) { Column( Modifier.wrapContentWidth(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally, ) { DragHandle(dialog) content(dialog) } } else { content(dialog) } } } } }, ) } private enum class DragAnchors(val fraction: Float) { Start(0f), End(1f), } private fun SystemUIDialogFactory.create( context: Context, theme: Int, Loading Loading @@ -177,7 +262,7 @@ private fun Modifier.bottomSheetPaddings(): Modifier { padding( start = insets.getLeft(this, LocalLayoutDirection.current).toDp() + horizontalPadding, top = insets.getTop(this).toDp(), end = insets.getRight(this, LocalLayoutDirection.current).toDp() + horizontalPadding end = insets.getRight(this, LocalLayoutDirection.current).toDp() + horizontalPadding, ) } } Loading @@ -191,3 +276,32 @@ private fun Modifier.bottomSheetPaddings(): Modifier { @Composable private fun Modifier.bottomSheetClickable(onClick: () -> Unit) = pointerInput(onClick) { detectTapGestures { onClick() } } @Composable private fun DragHandle(dialog: Dialog) { // TODO(b/373340318): Rename drag handle string resource. val dragHandleContentDescription = stringResource(id = R.string.shortcut_helper_content_description_drag_handle) Surface( modifier = Modifier.padding(top = 16.dp, bottom = 6.dp) .semantics { contentDescription = dragHandleContentDescription } .clickable { dialog.dismiss() }, color = MaterialTheme.colorScheme.outlineVariant, shape = MaterialTheme.shapes.extraLarge, ) { Box(Modifier.size(width = 32.dp, height = 4.dp)) } } @Composable private fun draggableTopPadding(): Dp { return if (hasCompactWindowSize()) DraggableBottomSheet.DefaultTopPadding else DraggableBottomSheet.LargeScreenTopPadding } private object DraggableBottomSheet { val DefaultTopPadding = 64.dp val LargeScreenTopPadding = 72.dp val MaxWidth = 640.dp } packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt +14 −34 Original line number Diff line number Diff line Loading @@ -31,7 +31,6 @@ import androidx.compose.ui.semantics.paneTitle import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.theme.PlatformTheme import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.res.R import com.android.systemui.volume.panel.ui.layout.ComponentsLayout Loading @@ -43,38 +42,28 @@ private const val VolumePanelTestTag = "VolumePanel" private val padding = 24.dp @Composable fun VolumePanelRoot( viewModel: VolumePanelViewModel, modifier: Modifier = Modifier, ) { fun VolumePanelRoot(viewModel: VolumePanelViewModel, modifier: Modifier = Modifier) { val accessibilityTitle = stringResource(R.string.accessibility_volume_settings) val state: VolumePanelState by viewModel.volumePanelState.collectAsStateWithLifecycle() val components by viewModel.componentsLayout.collectAsStateWithLifecycle() with(VolumePanelComposeScope(state)) { components?.let { componentsState -> PlatformTheme { Components( componentsState, modifier .sysuiResTag(VolumePanelTestTag) .semantics { paneTitle = accessibilityTitle } .padding( start = padding, top = padding, end = padding, bottom = 20.dp, ) .padding(start = padding, top = padding, end = padding, bottom = 20.dp), ) } } } } @Composable private fun VolumePanelComposeScope.Components( layout: ComponentsLayout, modifier: Modifier = Modifier modifier: Modifier = Modifier, ) { val arrangement: Arrangement.Vertical = if (isLargeScreen) { Loading @@ -82,14 +71,11 @@ private fun VolumePanelComposeScope.Components( } else { if (isPortrait) Arrangement.spacedBy(padding) else Arrangement.spacedBy(4.dp) } Column( modifier = modifier, verticalArrangement = arrangement, ) { Column(modifier = modifier, verticalArrangement = arrangement) { if (isPortrait || isLargeScreen) { VerticalVolumePanelContent( modifier = Modifier.weight(weight = 1f, fill = false), layout = layout layout = layout, ) } else { HorizontalVolumePanelContent( Loading @@ -97,23 +83,17 @@ private fun VolumePanelComposeScope.Components( layout = layout, ) } BottomBar( modifier = Modifier, layout = layout, ) BottomBar(modifier = Modifier, layout = layout) } } @Composable private fun VolumePanelComposeScope.BottomBar( layout: ComponentsLayout, modifier: Modifier = Modifier modifier: Modifier = Modifier, ) { if (layout.bottomBarComponent.isVisible) { Box( modifier = modifier.fillMaxWidth(), contentAlignment = Alignment.Center, ) { Box(modifier = modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { with(layout.bottomBarComponent.component as ComposeVolumePanelUiComponent) { Content(Modifier) } Loading packages/SystemUI/src/com/android/systemui/volume/ui/navigation/VolumeNavigator.kt +5 −1 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.provider.Settings import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.unit.dp import com.android.internal.logging.UiEventLogger import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application Loading Loading @@ -89,7 +90,7 @@ constructor( VolumePanelRoute.SETTINGS_VOLUME_PANEL -> activityStarter.startActivity( /* intent= */ Intent(Settings.Panel.ACTION_VOLUME), /* dismissShade= */ true /* dismissShade= */ true, ) VolumePanelRoute.SYSTEM_UI_VOLUME_PANEL -> volumePanelFactory.create(aboveStatusBar = true, view = null) Loading Loading @@ -122,6 +123,9 @@ constructor( remember(coroutineScope) { viewModelFactory.create(coroutineScope) } ) }, isDraggable = false, // TODO(b/337205027) change maxWidth maxWidth = 800.dp, ) } } Loading
packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt +120 −6 Original line number Diff line number Diff line Loading @@ -16,34 +16,58 @@ package com.android.systemui.statusbar.phone import android.app.Dialog import android.content.Context import android.content.res.Configuration import android.os.Bundle import androidx.annotation.GravityInt import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.AnchoredDraggableDefaults import androidx.compose.foundation.gestures.AnchoredDraggableState import androidx.compose.foundation.gestures.DraggableAnchors import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.anchoredDraggable import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsDraggedAsState import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.isSpecified import com.android.compose.theme.PlatformTheme import com.android.systemui.keyboard.shortcut.ui.composable.hasCompactWindowSize import com.android.systemui.res.R import kotlin.math.roundToInt /** * Create a [SystemUIDialog] with the given [content]. Loading Loading @@ -97,6 +121,9 @@ fun SystemUIDialogFactory.createBottomSheet( theme: Int = R.style.Theme_SystemUI_BottomSheet, dismissOnDeviceLock: Boolean = SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK, content: @Composable (SystemUIDialog) -> Unit, isDraggable: Boolean = true, // TODO(b/337205027): remove maxWidth parameter when aligned to M3 spec maxWidth: Dp = Dp.Unspecified, ): ComponentSystemUIDialog { return create( context = context, Loading @@ -104,9 +131,49 @@ fun SystemUIDialogFactory.createBottomSheet( dismissOnDeviceLock = dismissOnDeviceLock, delegate = EdgeToEdgeDialogDelegate(), content = { dialog -> val dragState = if (isDraggable) remember { AnchoredDraggableState(initialValue = DragAnchors.Start) } else null val interactionSource = if (isDraggable) remember { MutableInteractionSource() } else null if (dragState != null) { val isDragged by interactionSource!!.collectIsDraggedAsState() LaunchedEffect(dragState.currentValue, isDragged) { if (!isDragged && dragState.currentValue == DragAnchors.End) dialog.dismiss() } } Box( modifier = Modifier.bottomSheetClickable { dialog.dismiss() }, contentAlignment = Alignment.BottomCenter modifier = Modifier.bottomSheetClickable { dialog.dismiss() } .then( if (isDraggable) Modifier.anchoredDraggable( state = dragState!!, interactionSource = interactionSource, orientation = Orientation.Vertical, flingBehavior = AnchoredDraggableDefaults.flingBehavior( state = dragState ), ) .offset { IntOffset(x = 0, y = dragState.requireOffset().roundToInt()) } .onSizeChanged { layoutSize -> val dragEndPoint = layoutSize.height - dialog.height dragState.updateAnchors( DraggableAnchors { DragAnchors.entries.forEach { anchor -> anchor at dragEndPoint * anchor.fraction } } ) } .padding(top = draggableTopPadding()) else Modifier // No-Op ), contentAlignment = Alignment.BottomCenter, ) { val radius = dimensionResource(R.dimen.bottom_sheet_corner_radius) Surface( Loading @@ -114,8 +181,11 @@ fun SystemUIDialogFactory.createBottomSheet( Modifier.bottomSheetPaddings() // consume input so it doesn't get to the parent Composable .bottomSheetClickable {} // TODO(b/337205027) change width .widthIn(max = 800.dp), .widthIn( max = if (maxWidth.isSpecified) maxWidth else DraggableBottomSheet.MaxWidth ), shape = RoundedCornerShape(topStart = radius, topEnd = radius), color = MaterialTheme.colorScheme.surfaceContainer, ) { Loading @@ -127,14 +197,29 @@ fun SystemUIDialogFactory.createBottomSheet( } ) ) { if (isDraggable) { Column( Modifier.wrapContentWidth(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally, ) { DragHandle(dialog) content(dialog) } } else { content(dialog) } } } } }, ) } private enum class DragAnchors(val fraction: Float) { Start(0f), End(1f), } private fun SystemUIDialogFactory.create( context: Context, theme: Int, Loading Loading @@ -177,7 +262,7 @@ private fun Modifier.bottomSheetPaddings(): Modifier { padding( start = insets.getLeft(this, LocalLayoutDirection.current).toDp() + horizontalPadding, top = insets.getTop(this).toDp(), end = insets.getRight(this, LocalLayoutDirection.current).toDp() + horizontalPadding end = insets.getRight(this, LocalLayoutDirection.current).toDp() + horizontalPadding, ) } } Loading @@ -191,3 +276,32 @@ private fun Modifier.bottomSheetPaddings(): Modifier { @Composable private fun Modifier.bottomSheetClickable(onClick: () -> Unit) = pointerInput(onClick) { detectTapGestures { onClick() } } @Composable private fun DragHandle(dialog: Dialog) { // TODO(b/373340318): Rename drag handle string resource. val dragHandleContentDescription = stringResource(id = R.string.shortcut_helper_content_description_drag_handle) Surface( modifier = Modifier.padding(top = 16.dp, bottom = 6.dp) .semantics { contentDescription = dragHandleContentDescription } .clickable { dialog.dismiss() }, color = MaterialTheme.colorScheme.outlineVariant, shape = MaterialTheme.shapes.extraLarge, ) { Box(Modifier.size(width = 32.dp, height = 4.dp)) } } @Composable private fun draggableTopPadding(): Dp { return if (hasCompactWindowSize()) DraggableBottomSheet.DefaultTopPadding else DraggableBottomSheet.LargeScreenTopPadding } private object DraggableBottomSheet { val DefaultTopPadding = 64.dp val LargeScreenTopPadding = 72.dp val MaxWidth = 640.dp }
packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt +14 −34 Original line number Diff line number Diff line Loading @@ -31,7 +31,6 @@ import androidx.compose.ui.semantics.paneTitle import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.theme.PlatformTheme import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.res.R import com.android.systemui.volume.panel.ui.layout.ComponentsLayout Loading @@ -43,38 +42,28 @@ private const val VolumePanelTestTag = "VolumePanel" private val padding = 24.dp @Composable fun VolumePanelRoot( viewModel: VolumePanelViewModel, modifier: Modifier = Modifier, ) { fun VolumePanelRoot(viewModel: VolumePanelViewModel, modifier: Modifier = Modifier) { val accessibilityTitle = stringResource(R.string.accessibility_volume_settings) val state: VolumePanelState by viewModel.volumePanelState.collectAsStateWithLifecycle() val components by viewModel.componentsLayout.collectAsStateWithLifecycle() with(VolumePanelComposeScope(state)) { components?.let { componentsState -> PlatformTheme { Components( componentsState, modifier .sysuiResTag(VolumePanelTestTag) .semantics { paneTitle = accessibilityTitle } .padding( start = padding, top = padding, end = padding, bottom = 20.dp, ) .padding(start = padding, top = padding, end = padding, bottom = 20.dp), ) } } } } @Composable private fun VolumePanelComposeScope.Components( layout: ComponentsLayout, modifier: Modifier = Modifier modifier: Modifier = Modifier, ) { val arrangement: Arrangement.Vertical = if (isLargeScreen) { Loading @@ -82,14 +71,11 @@ private fun VolumePanelComposeScope.Components( } else { if (isPortrait) Arrangement.spacedBy(padding) else Arrangement.spacedBy(4.dp) } Column( modifier = modifier, verticalArrangement = arrangement, ) { Column(modifier = modifier, verticalArrangement = arrangement) { if (isPortrait || isLargeScreen) { VerticalVolumePanelContent( modifier = Modifier.weight(weight = 1f, fill = false), layout = layout layout = layout, ) } else { HorizontalVolumePanelContent( Loading @@ -97,23 +83,17 @@ private fun VolumePanelComposeScope.Components( layout = layout, ) } BottomBar( modifier = Modifier, layout = layout, ) BottomBar(modifier = Modifier, layout = layout) } } @Composable private fun VolumePanelComposeScope.BottomBar( layout: ComponentsLayout, modifier: Modifier = Modifier modifier: Modifier = Modifier, ) { if (layout.bottomBarComponent.isVisible) { Box( modifier = modifier.fillMaxWidth(), contentAlignment = Alignment.Center, ) { Box(modifier = modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { with(layout.bottomBarComponent.component as ComposeVolumePanelUiComponent) { Content(Modifier) } Loading
packages/SystemUI/src/com/android/systemui/volume/ui/navigation/VolumeNavigator.kt +5 −1 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.provider.Settings import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.unit.dp import com.android.internal.logging.UiEventLogger import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application Loading Loading @@ -89,7 +90,7 @@ constructor( VolumePanelRoute.SETTINGS_VOLUME_PANEL -> activityStarter.startActivity( /* intent= */ Intent(Settings.Panel.ACTION_VOLUME), /* dismissShade= */ true /* dismissShade= */ true, ) VolumePanelRoute.SYSTEM_UI_VOLUME_PANEL -> volumePanelFactory.create(aboveStatusBar = true, view = null) Loading Loading @@ -122,6 +123,9 @@ constructor( remember(coroutineScope) { viewModelFactory.create(coroutineScope) } ) }, isDraggable = false, // TODO(b/337205027) change maxWidth maxWidth = 800.dp, ) } }