summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt26
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt66
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt48
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt51
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt213
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt47
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt20
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelTest.kt82
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt18
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt113
-rw-r--r--packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt50
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt10
21 files changed, 484 insertions, 366 deletions
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt
index 931134795a53..26e7524f4fa8 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt
@@ -29,7 +29,6 @@ import com.android.compose.animation.scene.ContentScope
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
-import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.composable.blueprint.rememberBurnIn
@@ -44,10 +43,7 @@ import com.android.systemui.scene.ui.composable.Overlay
import com.android.systemui.shade.ui.composable.OverlayShade
import com.android.systemui.shade.ui.composable.OverlayShadeHeader
import com.android.systemui.shade.ui.composable.SingleShadeMeasurePolicy
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
-import com.android.systemui.statusbar.phone.ui.StatusBarIconController
-import com.android.systemui.statusbar.phone.ui.TintedIconManager
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -58,11 +54,6 @@ class NotificationsShadeOverlay
constructor(
private val actionsViewModelFactory: NotificationsShadeOverlayActionsViewModel.Factory,
private val contentViewModelFactory: NotificationsShadeOverlayContentViewModel.Factory,
- private val tintedIconManagerFactory: TintedIconManager.Factory,
- private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
- private val statusBarIconController: StatusBarIconController,
- private val notificationIconContainerStatusBarViewBinder:
- NotificationIconContainerStatusBarViewBinder,
private val shadeSession: SaveableSession,
private val stackScrollView: Lazy<NotificationScrollView>,
private val clockSection: DefaultClockSection,
@@ -94,18 +85,16 @@ constructor(
}
OverlayShade(
- isShadeLayoutWide = viewModel.isShadeLayoutWide,
panelAlignment = Alignment.TopStart,
modifier = modifier,
onScrimClicked = viewModel::onScrimClicked,
header = {
+ val headerViewModel =
+ rememberViewModel("NotificationsShadeOverlayHeader") {
+ viewModel.shadeHeaderViewModelFactory.create()
+ }
OverlayShadeHeader(
- viewModelFactory = viewModel.shadeHeaderViewModelFactory,
- createTintedIconManager = tintedIconManagerFactory::create,
- createBatteryMeterViewController = batteryMeterViewControllerFactory::create,
- statusBarIconController = statusBarIconController,
- notificationIconContainerStatusBarViewBinder =
- notificationIconContainerStatusBarViewBinder,
+ viewModel = headerViewModel,
modifier =
Modifier.element(NotificationsShade.Elements.StatusBar)
.layoutId(SingleShadeMeasurePolicy.LayoutId.ShadeHeader),
@@ -114,7 +103,7 @@ constructor(
) {
Box {
Column {
- if (viewModel.showHeader) {
+ if (viewModel.showClock) {
val burnIn = rememberBurnIn(clockInteractor)
with(clockSection) {
@@ -140,8 +129,7 @@ constructor(
modifier = Modifier.fillMaxWidth(),
)
}
- // Communicates the bottom position of the drawable area within the shade to
- // NSSL.
+ // Communicates the bottom position of the drawable area within the shade to NSSL.
NotificationStackCutoffGuideline(
stackScrollView = stackScrollView.get(),
viewModel = placeholderViewModel,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 4bfbb3a908fa..62a8cc5a7fe3 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -16,7 +16,6 @@
package com.android.systemui.qs.ui.composable
-import android.view.ViewGroup
import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateDpAsState
@@ -76,7 +75,6 @@ import com.android.compose.animation.scene.animateSceneFloatAsState
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.modifiers.thenIf
import com.android.compose.windowsizeclass.LocalWindowSizeClass
-import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout
import com.android.systemui.compose.modifiers.sysuiResTag
@@ -100,15 +98,14 @@ import com.android.systemui.res.R
import com.android.systemui.scene.session.ui.composable.SaveableSession
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.Scene
+import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel
import com.android.systemui.shade.ui.composable.CollapsedShadeHeader
import com.android.systemui.shade.ui.composable.ExpandedShadeHeader
import com.android.systemui.shade.ui.composable.Shade
import com.android.systemui.shade.ui.composable.ShadeHeader
+import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
-import com.android.systemui.statusbar.phone.StatusBarLocation
-import com.android.systemui.statusbar.phone.ui.StatusBarIconController
-import com.android.systemui.statusbar.phone.ui.TintedIconManager
import dagger.Lazy
import javax.inject.Inject
import javax.inject.Named
@@ -125,9 +122,6 @@ constructor(
private val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory,
private val actionsViewModelFactory: QuickSettingsUserActionsViewModel.Factory,
private val contentViewModelFactory: QuickSettingsSceneContentViewModel.Factory,
- private val tintedIconManagerFactory: TintedIconManager.Factory,
- private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
- private val statusBarIconController: StatusBarIconController,
private val mediaCarouselController: MediaCarouselController,
@Named(MediaModule.QS_PANEL) private val mediaHost: MediaHost,
) : ExclusiveActivatable(), Scene {
@@ -145,16 +139,26 @@ constructor(
@Composable
override fun ContentScope.Content(modifier: Modifier) {
+ val viewModel =
+ rememberViewModel("QuickSettingsScene-viewModel") { contentViewModelFactory.create() }
+ val headerViewModel =
+ rememberViewModel("QuickSettingsScene-headerViewModel") {
+ viewModel.shadeHeaderViewModelFactory.create()
+ }
+ val brightnessMirrorViewModel =
+ rememberViewModel("QuickSettingsScene-brightnessMirrorViewModel") {
+ viewModel.brightnessMirrorViewModelFactory.create()
+ }
+ val notificationsPlaceholderViewModel =
+ rememberViewModel("QuickSettingsScene-notifPlaceholderViewModel") {
+ notificationsPlaceholderViewModelFactory.create()
+ }
QuickSettingsScene(
notificationStackScrollView = notificationStackScrollView.get(),
- viewModelFactory = contentViewModelFactory,
- notificationsPlaceholderViewModel =
- rememberViewModel("QuickSettingsScene-notifPlaceholderViewModel") {
- notificationsPlaceholderViewModelFactory.create()
- },
- createTintedIconManager = tintedIconManagerFactory::create,
- createBatteryMeterViewController = batteryMeterViewControllerFactory::create,
- statusBarIconController = statusBarIconController,
+ viewModel = viewModel,
+ headerViewModel = headerViewModel,
+ brightnessMirrorViewModel = brightnessMirrorViewModel,
+ notificationsPlaceholderViewModel = notificationsPlaceholderViewModel,
mediaCarouselController = mediaCarouselController,
mediaHost = mediaHost,
modifier = modifier,
@@ -166,23 +170,16 @@ constructor(
@Composable
private fun ContentScope.QuickSettingsScene(
notificationStackScrollView: NotificationScrollView,
- viewModelFactory: QuickSettingsSceneContentViewModel.Factory,
+ viewModel: QuickSettingsSceneContentViewModel,
+ headerViewModel: ShadeHeaderViewModel,
+ brightnessMirrorViewModel: BrightnessMirrorViewModel,
notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
- createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
- createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
- statusBarIconController: StatusBarIconController,
mediaCarouselController: MediaCarouselController,
mediaHost: MediaHost,
modifier: Modifier = Modifier,
shadeSession: SaveableSession,
) {
val cutoutLocation = LocalDisplayCutout.current.location
-
- val viewModel = rememberViewModel("QuickSettingsScene-viewModel") { viewModelFactory.create() }
- val brightnessMirrorViewModel =
- rememberViewModel("QuickSettingsScene-brightnessMirrorViewModel") {
- viewModel.brightnessMirrorViewModelFactory.create()
- }
val brightnessMirrorShowing by brightnessMirrorViewModel.isShowing.collectAsStateWithLifecycle()
val contentAlpha by
animateFloatAsState(
@@ -222,8 +219,7 @@ private fun ContentScope.QuickSettingsScene(
.graphicsLayer { alpha = contentAlpha }
.thenIf(shouldPunchHoleBehindScrim) {
// Render the scene to an offscreen buffer so that BlendMode.DstOut only clears
- // this
- // scene (and not the one under it) during a scene transition.
+ // this scene (and not the one under it) during a scene transition.
Modifier.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
}
.thenIf(cutoutLocation != CutoutLocation.CENTER) { Modifier.displayCutoutPadding() }
@@ -348,21 +344,11 @@ private fun ContentScope.QuickSettingsScene(
fadeOut(tween(customizingAnimationDuration)),
) {
ExpandedShadeHeader(
- viewModelFactory = viewModel.shadeHeaderViewModelFactory,
- createTintedIconManager = createTintedIconManager,
- createBatteryMeterViewController =
- createBatteryMeterViewController,
- statusBarIconController = statusBarIconController,
+ viewModel = headerViewModel,
modifier = Modifier.padding(horizontal = 16.dp),
)
}
- else ->
- CollapsedShadeHeader(
- viewModelFactory = viewModel.shadeHeaderViewModelFactory,
- createTintedIconManager = createTintedIconManager,
- createBatteryMeterViewController = createBatteryMeterViewController,
- statusBarIconController = statusBarIconController,
- )
+ else -> CollapsedShadeHeader(viewModel = headerViewModel)
}
Spacer(modifier = Modifier.height(16.dp))
// This view has its own horizontal padding
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
index 3ec14a23421c..2fa370458ab0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
@@ -45,7 +45,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.ContentScope
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
-import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
@@ -67,13 +66,10 @@ import com.android.systemui.shade.ui.composable.OverlayShade
import com.android.systemui.shade.ui.composable.OverlayShadeHeader
import com.android.systemui.shade.ui.composable.QuickSettingsOverlayHeader
import com.android.systemui.shade.ui.composable.SingleShadeMeasurePolicy
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
-import com.android.systemui.statusbar.phone.ui.StatusBarIconController
-import com.android.systemui.statusbar.phone.ui.TintedIconManager
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -84,11 +80,7 @@ class QuickSettingsShadeOverlay
constructor(
private val actionsViewModelFactory: QuickSettingsShadeOverlayActionsViewModel.Factory,
private val contentViewModelFactory: QuickSettingsShadeOverlayContentViewModel.Factory,
- private val tintedIconManagerFactory: TintedIconManager.Factory,
- private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
- private val statusBarIconController: StatusBarIconController,
- private val notificationIconContainerStatusBarViewBinder:
- NotificationIconContainerStatusBarViewBinder,
+ private val quickSettingsContainerViewModelFactory: QuickSettingsContainerViewModel.Factory,
private val notificationStackScrollView: Lazy<NotificationScrollView>,
private val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory,
) : Overlay {
@@ -107,35 +99,35 @@ constructor(
@Composable
override fun ContentScope.Content(modifier: Modifier) {
- val viewModel =
- rememberViewModel("QuickSettingsShadeOverlay") { contentViewModelFactory.create() }
+ val contentViewModel =
+ rememberViewModel("QuickSettingsShadeOverlayContent") {
+ contentViewModelFactory.create()
+ }
+ val quickSettingsContainerViewModel =
+ rememberViewModel("QuickSettingsShadeOverlayContainer") {
+ // TODO(b/393054014): Add support for brightness mirroring.
+ quickSettingsContainerViewModelFactory.create(supportsBrightnessMirroring = false)
+ }
val panelCornerRadius =
with(LocalDensity.current) { OverlayShade.Dimensions.PanelCornerRadius.toPx().toInt() }
- // set the bounds to null when the QuickSettings overlay disappears
- DisposableEffect(Unit) { onDispose { viewModel.onPanelShapeChanged(null) } }
+ // Set the bounds to null when the QuickSettings overlay disappears.
+ DisposableEffect(Unit) { onDispose { contentViewModel.onPanelShapeChanged(null) } }
Box(modifier = modifier) {
SnoozeableHeadsUpNotificationSpace(
stackScrollView = notificationStackScrollView.get(),
viewModel =
- rememberViewModel("QuickSettingsShadeOverlay") {
+ rememberViewModel("QuickSettingsShadeOverlayPlaceholder") {
notificationsPlaceholderViewModelFactory.create()
},
)
OverlayShade(
- isShadeLayoutWide = viewModel.isShadeLayoutWide,
panelAlignment = Alignment.TopEnd,
- onScrimClicked = viewModel::onScrimClicked,
+ onScrimClicked = contentViewModel::onScrimClicked,
header = {
OverlayShadeHeader(
- viewModelFactory = viewModel.shadeHeaderViewModelFactory,
- createTintedIconManager = tintedIconManagerFactory::create,
- createBatteryMeterViewController =
- batteryMeterViewControllerFactory::create,
- statusBarIconController = statusBarIconController,
- notificationIconContainerStatusBarViewBinder =
- notificationIconContainerStatusBarViewBinder,
+ viewModel = quickSettingsContainerViewModel.shadeHeaderViewModel,
modifier =
Modifier.element(NotificationsShade.Elements.StatusBar)
.layoutId(SingleShadeMeasurePolicy.LayoutId.ShadeHeader),
@@ -143,7 +135,7 @@ constructor(
},
) {
ShadeBody(
- viewModel = viewModel.quickSettingsContainerViewModel,
+ viewModel = quickSettingsContainerViewModel,
modifier =
Modifier.onPlaced { coordinates ->
val boundsInWindow = coordinates.boundsInWindow()
@@ -160,14 +152,12 @@ constructor(
topRadius = 0,
bottomRadius = panelCornerRadius,
)
- viewModel.onPanelShapeChanged(shape)
+ contentViewModel.onPanelShapeChanged(shape)
},
header = {
- if (viewModel.isShadeLayoutWide) {
+ if (quickSettingsContainerViewModel.showHeader) {
QuickSettingsOverlayHeader(
- viewModelFactory = viewModel.shadeHeaderViewModelFactory,
- createBatteryMeterViewController =
- batteryMeterViewControllerFactory::create,
+ viewModel = quickSettingsContainerViewModel.shadeHeaderViewModel,
modifier =
Modifier.padding(top = QuickSettingsShade.Dimensions.Padding),
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
index fc59d40ec443..3d2d7c37ce48 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
@@ -58,29 +58,31 @@ import com.android.systemui.res.R
/** Renders a lightweight shade UI container, as an overlay. */
@Composable
fun ContentScope.OverlayShade(
- isShadeLayoutWide: Boolean,
panelAlignment: Alignment,
onScrimClicked: () -> Unit,
modifier: Modifier = Modifier,
header: @Composable () -> Unit,
content: @Composable () -> Unit,
) {
+ val isFullWidth = isFullWidthShade()
Box(modifier) {
Scrim(onClicked = onScrimClicked)
- Box(modifier = Modifier.fillMaxSize().panelPadding(), contentAlignment = panelAlignment) {
+ Box(
+ modifier = Modifier.fillMaxSize().panelContainerPadding(isFullWidth),
+ contentAlignment = panelAlignment,
+ ) {
Panel(
- isShadeLayoutWide = isShadeLayoutWide,
modifier =
Modifier.overscroll(verticalOverscrollEffect)
.element(OverlayShade.Elements.Panel)
- .panelSize(),
- header = header,
+ .panelWidth(isFullWidth),
+ header = header.takeIf { isFullWidth },
content = content,
)
}
- if (isShadeLayoutWide) {
+ if (!isFullWidth) {
header()
}
}
@@ -100,9 +102,8 @@ private fun ContentScope.Scrim(onClicked: () -> Unit, modifier: Modifier = Modif
@Composable
private fun ContentScope.Panel(
- isShadeLayoutWide: Boolean,
modifier: Modifier = Modifier,
- header: @Composable () -> Unit,
+ header: (@Composable () -> Unit)?,
content: @Composable () -> Unit,
) {
Box(modifier = modifier.clip(OverlayShade.Shapes.RoundedCornerPanel)) {
@@ -117,9 +118,7 @@ private fun ContentScope.Panel(
)
Column {
- if (!isShadeLayoutWide) {
- header()
- }
+ header?.invoke()
// This content is intentionally rendered as a separate element from the background in
// order to allow for more flexibility when defining transitions.
@@ -129,14 +128,12 @@ private fun ContentScope.Panel(
}
@Composable
-private fun Modifier.panelSize(): Modifier {
- return this.then(
- if (isFullWidthShade()) {
- Modifier.fillMaxWidth()
- } else {
- Modifier.width(dimensionResource(id = R.dimen.shade_panel_width))
- }
- )
+private fun Modifier.panelWidth(isFullWidthPanel: Boolean): Modifier {
+ return if (isFullWidthPanel) {
+ fillMaxWidth()
+ } else {
+ width(dimensionResource(id = R.dimen.shade_panel_width))
+ }
}
@Composable
@@ -146,27 +143,23 @@ internal fun isFullWidthShade(): Boolean {
}
@Composable
-private fun Modifier.panelPadding(): Modifier {
- val widthSizeClass = LocalWindowSizeClass.current.widthSizeClass
+private fun Modifier.panelContainerPadding(isFullWidthPanel: Boolean): Modifier {
+ if (isFullWidthPanel) {
+ return this
+ }
val systemBars = WindowInsets.systemBarsIgnoringVisibility
val displayCutout = WindowInsets.displayCutout
val waterfall = WindowInsets.waterfall
val horizontalPadding =
PaddingValues(horizontal = dimensionResource(id = R.dimen.shade_panel_margin_horizontal))
-
- val combinedPadding =
+ return padding(
combinePaddings(
systemBars.asPaddingValues(),
displayCutout.asPaddingValues(),
waterfall.asPaddingValues(),
horizontalPadding,
)
-
- return if (widthSizeClass == WindowWidthSizeClass.Compact) {
- padding(bottom = combinedPadding.calculateBottomPadding())
- } else {
- padding(combinedPadding)
- }
+ )
}
/** Creates a union of [paddingValues] by using the max padding of each edge. */
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
index c5d28adce601..02de78bc84ce 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
@@ -74,23 +74,20 @@ import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout
import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
import com.android.systemui.compose.modifiers.sysuiResTag
-import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.privacy.OngoingPrivacyChip
import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.shade.ui.composable.ShadeHeader.Colors.chipBackground
import com.android.systemui.shade.ui.composable.ShadeHeader.Colors.chipHighlighted
import com.android.systemui.shade.ui.composable.ShadeHeader.Colors.onScrimDim
import com.android.systemui.shade.ui.composable.ShadeHeader.Dimensions.CollapsedHeight
import com.android.systemui.shade.ui.composable.ShadeHeader.Values.ClockScale
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
+import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel.HeaderChipHighlight
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder
import com.android.systemui.statusbar.phone.NotificationIconContainer
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.phone.StatusIconContainer
-import com.android.systemui.statusbar.phone.ui.StatusBarIconController
-import com.android.systemui.statusbar.phone.ui.TintedIconManager
import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernShadeCarrierGroupMobileView
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.ShadeCarrierGroupMobileIconViewModel
import com.android.systemui.statusbar.policy.Clock
@@ -137,14 +134,9 @@ object ShadeHeader {
/** The status bar that appears above the Shade scene on small screens */
@Composable
fun ContentScope.CollapsedShadeHeader(
- viewModelFactory: ShadeHeaderViewModel.Factory,
- createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
- createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
- statusBarIconController: StatusBarIconController,
+ viewModel: ShadeHeaderViewModel,
modifier: Modifier = Modifier,
) {
- val viewModel = rememberViewModel("CollapsedShadeHeader") { viewModelFactory.create() }
-
val cutoutLocation = LocalDisplayCutout.current.location
val horizontalPadding =
max(LocalScreenCornerRadius.current / 2f, Shade.Dimensions.HorizontalPadding)
@@ -157,12 +149,14 @@ fun ContentScope.CollapsedShadeHeader(
}
}
+ val longerDateText by viewModel.longerDateText.collectAsStateWithLifecycle()
+ val shorterDateText by viewModel.shorterDateText.collectAsStateWithLifecycle()
+
val isShadeLayoutWide = viewModel.isShadeLayoutWide
val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsStateWithLifecycle()
- // This layout assumes it is globally positioned at (0, 0) and is the
- // same size as the screen.
+ // This layout assumes it is globally positioned at (0, 0) and is the same size as the screen.
CutoutAwareShadeHeader(
modifier = modifier.sysuiResTag(ShadeHeader.TestTags.Root),
startContent = {
@@ -171,9 +165,11 @@ fun ContentScope.CollapsedShadeHeader(
horizontalArrangement = Arrangement.spacedBy(5.dp),
modifier = Modifier.padding(horizontal = horizontalPadding),
) {
- Clock(scale = 1f, viewModel = viewModel)
+ Clock(scale = 1f, onClick = viewModel::onClockClicked)
VariableDayDate(
- viewModel = viewModel,
+ longerDateText = longerDateText,
+ shorterDateText = shorterDateText,
+ chipHighlight = viewModel.notificationsChipHighlight,
modifier = Modifier.element(ShadeHeader.Elements.CollapsedContentStart),
)
}
@@ -202,17 +198,17 @@ fun ContentScope.CollapsedShadeHeader(
if (isShadeLayoutWide) {
ShadeCarrierGroup(viewModel = viewModel)
}
- SystemIconChip(viewModel = viewModel, isClickable = isShadeLayoutWide) {
+ SystemIconChip(
+ onClick = viewModel::onSystemIconChipClicked.takeIf { isShadeLayoutWide }
+ ) {
StatusIcons(
viewModel = viewModel,
- createTintedIconManager = createTintedIconManager,
- statusBarIconController = statusBarIconController,
useExpandedFormat = useExpandedTextFormat,
modifier = Modifier.padding(end = 6.dp).weight(1f, fill = false),
)
BatteryIcon(
- viewModel = viewModel,
- createBatteryMeterViewController = createBatteryMeterViewController,
+ createBatteryMeterViewController =
+ viewModel.createBatteryMeterViewController,
useExpandedFormat = useExpandedTextFormat,
modifier = Modifier.padding(vertical = 8.dp),
)
@@ -226,18 +222,15 @@ fun ContentScope.CollapsedShadeHeader(
/** The status bar that appears above the Quick Settings scene on small screens */
@Composable
fun ContentScope.ExpandedShadeHeader(
- viewModelFactory: ShadeHeaderViewModel.Factory,
- createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
- createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
- statusBarIconController: StatusBarIconController,
+ viewModel: ShadeHeaderViewModel,
modifier: Modifier = Modifier,
) {
- val viewModel = rememberViewModel("ExpandedShadeHeader") { viewModelFactory.create() }
-
val useExpandedFormat by remember {
derivedStateOf { shouldUseExpandedFormat(layoutState.transitionState) }
}
+ val longerDateText by viewModel.longerDateText.collectAsStateWithLifecycle()
+ val shorterDateText by viewModel.shorterDateText.collectAsStateWithLifecycle()
val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsStateWithLifecycle()
Box(modifier = modifier.sysuiResTag(ShadeHeader.TestTags.Root)) {
@@ -256,7 +249,7 @@ fun ContentScope.ExpandedShadeHeader(
Box {
Clock(
scale = 2.57f,
- viewModel = viewModel,
+ onClick = viewModel::onClockClicked,
modifier = Modifier.align(Alignment.CenterStart),
)
}
@@ -275,20 +268,23 @@ fun ContentScope.ExpandedShadeHeader(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.element(ShadeHeader.Elements.ExpandedContent),
) {
- VariableDayDate(viewModel = viewModel, modifier = Modifier.widthIn(max = 90.dp))
+ VariableDayDate(
+ longerDateText = longerDateText,
+ shorterDateText = shorterDateText,
+ chipHighlight = viewModel.notificationsChipHighlight,
+ modifier = Modifier.widthIn(max = 90.dp),
+ )
Spacer(modifier = Modifier.weight(1f))
- SystemIconChip(viewModel = viewModel) {
+ SystemIconChip {
StatusIcons(
viewModel = viewModel,
- createTintedIconManager = createTintedIconManager,
- statusBarIconController = statusBarIconController,
useExpandedFormat = useExpandedFormat,
modifier = Modifier.padding(end = 6.dp).weight(1f, fill = false),
)
BatteryIcon(
- viewModel = viewModel,
useExpandedFormat = useExpandedFormat,
- createBatteryMeterViewController = createBatteryMeterViewController,
+ createBatteryMeterViewController =
+ viewModel.createBatteryMeterViewController,
)
}
}
@@ -302,15 +298,9 @@ fun ContentScope.ExpandedShadeHeader(
*/
@Composable
fun ContentScope.OverlayShadeHeader(
- viewModelFactory: ShadeHeaderViewModel.Factory,
- createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
- createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
- statusBarIconController: StatusBarIconController,
- notificationIconContainerStatusBarViewBinder: NotificationIconContainerStatusBarViewBinder,
+ viewModel: ShadeHeaderViewModel,
modifier: Modifier = Modifier,
) {
- val viewModel = rememberViewModel("OverlayShadeHeader") { viewModelFactory.create() }
-
val horizontalPadding =
max(LocalScreenCornerRadius.current / 2f, Shade.Dimensions.HorizontalPadding)
@@ -318,8 +308,7 @@ fun ContentScope.OverlayShadeHeader(
val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsStateWithLifecycle()
- // This layout assumes it is globally positioned at (0, 0) and is the
- // same size as the screen.
+ // This layout assumes it is globally positioned at (0, 0) and is the same size as the screen.
CutoutAwareShadeHeader(
modifier = modifier.sysuiResTag(ShadeHeader.TestTags.Root),
startContent = {
@@ -330,21 +319,32 @@ fun ContentScope.OverlayShadeHeader(
if (isShadeLayoutWide) {
Clock(
scale = 1f,
- viewModel = viewModel,
+ onClick = viewModel::onClockClicked,
modifier = Modifier.padding(horizontal = 4.dp),
)
Spacer(modifier = Modifier.width(5.dp))
}
- NotificationIconChip(viewModel = viewModel) {
+ val chipHighlight = viewModel.notificationsChipHighlight
+ NotificationIconChip(
+ chipHighlight = chipHighlight,
+ onClick = viewModel::onNotificationIconChipClicked,
+ ) {
if (isShadeLayoutWide) {
NotificationIcons(
- viewModel = viewModel,
+ chipHighlight = chipHighlight,
notificationIconContainerStatusBarViewBinder =
- notificationIconContainerStatusBarViewBinder,
+ viewModel.notificationIconContainerStatusBarViewBinder,
modifier = Modifier.width(IntrinsicSize.Min).height(20.dp),
)
} else {
- VariableDayDate(viewModel = viewModel)
+ val longerDateText by viewModel.longerDateText.collectAsStateWithLifecycle()
+ val shorterDateText by
+ viewModel.shorterDateText.collectAsStateWithLifecycle()
+ VariableDayDate(
+ longerDateText = longerDateText,
+ shorterDateText = shorterDateText,
+ chipHighlight = viewModel.notificationsChipHighlight,
+ )
}
}
}
@@ -355,20 +355,22 @@ fun ContentScope.OverlayShadeHeader(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(horizontal = horizontalPadding),
) {
- SystemIconChip(viewModel = viewModel, isClickable = true, showBackground = true) {
+ val chipHighlight = viewModel.quickSettingsChipHighlight
+ SystemIconChip(
+ chipHighlight = chipHighlight,
+ onClick = viewModel::onSystemIconChipClicked,
+ ) {
StatusIcons(
viewModel = viewModel,
- createTintedIconManager = createTintedIconManager,
- statusBarIconController = statusBarIconController,
useExpandedFormat = false,
- highlightable = true,
modifier = Modifier.padding(end = 6.dp).weight(1f, fill = false),
+ chipHighlight = chipHighlight,
)
BatteryIcon(
- viewModel = viewModel,
- createBatteryMeterViewController = createBatteryMeterViewController,
+ createBatteryMeterViewController =
+ viewModel.createBatteryMeterViewController,
useExpandedFormat = false,
- highlightable = true,
+ chipHighlight = chipHighlight,
)
}
if (isPrivacyChipVisible) {
@@ -391,13 +393,7 @@ fun ContentScope.OverlayShadeHeader(
/** The header that appears at the top of the Quick Settings shade overlay. */
@Composable
-fun ContentScope.QuickSettingsOverlayHeader(
- viewModelFactory: ShadeHeaderViewModel.Factory,
- createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
- modifier: Modifier = Modifier,
-) {
- val viewModel = rememberViewModel("QuickSettingsOverlayHeader") { viewModelFactory.create() }
-
+fun QuickSettingsOverlayHeader(viewModel: ShadeHeaderViewModel, modifier: Modifier = Modifier) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
@@ -405,8 +401,7 @@ fun ContentScope.QuickSettingsOverlayHeader(
) {
ShadeCarrierGroup(viewModel = viewModel)
BatteryIcon(
- viewModel = viewModel,
- createBatteryMeterViewController = createBatteryMeterViewController,
+ createBatteryMeterViewController = viewModel.createBatteryMeterViewController,
useExpandedFormat = true,
)
}
@@ -468,11 +463,7 @@ private fun CutoutAwareShadeHeader(
}
@Composable
-private fun ContentScope.Clock(
- scale: Float,
- viewModel: ShadeHeaderViewModel,
- modifier: Modifier = Modifier,
-) {
+private fun ContentScope.Clock(scale: Float, onClick: () -> Unit, modifier: Modifier = Modifier) {
val layoutDirection = LocalLayoutDirection.current
Element(key = ShadeHeader.Elements.Clock, modifier = modifier) {
@@ -500,18 +491,17 @@ private fun ContentScope.Clock(
0.5f,
)
}
- .clickable { viewModel.onClockClicked() },
+ .clickable { onClick() },
)
}
}
@Composable
private fun BatteryIcon(
- viewModel: ShadeHeaderViewModel,
createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
useExpandedFormat: Boolean,
- highlightable: Boolean = false,
modifier: Modifier = Modifier,
+ chipHighlight: HeaderChipHighlight = HeaderChipHighlight.None,
) {
val localContext = LocalContext.current
val themedContext =
@@ -521,8 +511,6 @@ private fun BatteryIcon(
val inverseColor =
Utils.getColorAttrDefaultColor(themedContext, android.R.attr.textColorPrimaryInverse)
- val isHighlighted = viewModel.highlightQuickSettingsIcons
-
AndroidView(
factory = { context ->
val batteryIcon = BatteryMeterView(context, null)
@@ -544,18 +532,12 @@ private fun BatteryIcon(
// TODO(b/298525212): use MODE_ESTIMATE in collapsed view when the screen
// has no center cutout. See [QsBatteryModeController.getBatteryMode]
batteryIcon.setPercentShowMode(
- if (useExpandedFormat) {
- BatteryMeterView.MODE_ESTIMATE
- } else {
- BatteryMeterView.MODE_ON
- }
+ if (useExpandedFormat) BatteryMeterView.MODE_ESTIMATE else BatteryMeterView.MODE_ON
)
- if (highlightable) {
- if (isHighlighted) {
- batteryIcon.updateColors(primaryColor, inverseColor, inverseColor)
- } else {
- batteryIcon.updateColors(primaryColor, inverseColor, primaryColor)
- }
+ if (chipHighlight is HeaderChipHighlight.Strong) {
+ batteryIcon.updateColors(primaryColor, inverseColor, inverseColor)
+ } else if (chipHighlight is HeaderChipHighlight.Weak) {
+ batteryIcon.updateColors(primaryColor, inverseColor, primaryColor)
}
},
modifier = modifier,
@@ -590,14 +572,12 @@ private fun ShadeCarrierGroup(viewModel: ShadeHeaderViewModel, modifier: Modifie
@Composable
private fun NotificationIcons(
- viewModel: ShadeHeaderViewModel,
+ chipHighlight: HeaderChipHighlight,
notificationIconContainerStatusBarViewBinder: NotificationIconContainerStatusBarViewBinder,
modifier: Modifier = Modifier,
) {
val scope = rememberCoroutineScope()
- val isHighlighted = viewModel.highlightNotificationIcons
-
AndroidView(
factory = { context ->
NotificationIconContainer(context, null).also { view ->
@@ -610,7 +590,7 @@ private fun NotificationIcons(
}
}
},
- update = { it.setUseInverseOverrideIconColor(isHighlighted) },
+ update = { it.setUseInverseOverrideIconColor(chipHighlight is HeaderChipHighlight.Strong) },
modifier = modifier,
)
}
@@ -618,11 +598,9 @@ private fun NotificationIcons(
@Composable
private fun ContentScope.StatusIcons(
viewModel: ShadeHeaderViewModel,
- createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
- statusBarIconController: StatusBarIconController,
useExpandedFormat: Boolean,
- highlightable: Boolean = false,
modifier: Modifier = Modifier,
+ chipHighlight: HeaderChipHighlight = HeaderChipHighlight.None,
) {
val localContext = LocalContext.current
val themedContext =
@@ -632,8 +610,6 @@ private fun ContentScope.StatusIcons(
val inverseColor =
Utils.getColorAttrDefaultColor(themedContext, android.R.attr.textColorPrimaryInverse)
- val isHighlighted = viewModel.highlightQuickSettingsIcons
-
val carrierIconSlots =
listOf(stringResource(id = com.android.internal.R.string.status_bar_mobile))
val cameraSlot = stringResource(id = com.android.internal.R.string.status_bar_camera)
@@ -648,12 +624,14 @@ private fun ContentScope.StatusIcons(
viewModel.isLocationIndicationEnabled.collectAsStateWithLifecycle()
val iconContainer = remember { StatusIconContainer(themedContext, null) }
- val iconManager = remember { createTintedIconManager(iconContainer, StatusBarLocation.QS) }
+ val iconManager = remember {
+ viewModel.createTintedIconManager(iconContainer, StatusBarLocation.QS)
+ }
AndroidView(
factory = { context ->
iconManager.setTint(primaryColor, inverseColor)
- statusBarIconController.addIconGroup(iconManager)
+ viewModel.statusBarIconController.addIconGroup(iconManager)
iconContainer
},
@@ -686,12 +664,10 @@ private fun ContentScope.StatusIcons(
iconContainer.removeIgnoredSlot(locationSlot)
}
- if (highlightable) {
- if (isHighlighted) {
- iconManager.setTint(inverseColor, primaryColor)
- } else {
- iconManager.setTint(primaryColor, inverseColor)
- }
+ if (chipHighlight is HeaderChipHighlight.Strong) {
+ iconManager.setTint(inverseColor, primaryColor)
+ } else if (chipHighlight is HeaderChipHighlight.Weak) {
+ iconManager.setTint(primaryColor, inverseColor)
}
},
modifier = modifier,
@@ -700,15 +676,12 @@ private fun ContentScope.StatusIcons(
@Composable
private fun NotificationIconChip(
- viewModel: ShadeHeaderViewModel,
+ chipHighlight: HeaderChipHighlight,
+ onClick: () -> Unit,
modifier: Modifier = Modifier,
content: @Composable RowScope.() -> Unit,
) {
val interactionSource = remember { MutableInteractionSource() }
- val backgroundColor =
- if (viewModel.highlightNotificationIcons) MaterialTheme.colorScheme.chipHighlighted
- else MaterialTheme.colorScheme.chipBackground
-
Box(modifier = modifier) {
Row(
modifier =
@@ -716,16 +689,15 @@ private fun NotificationIconChip(
.clickable(
interactionSource = interactionSource,
indication = null,
- onClick = { viewModel.onNotificationIconChipClicked() },
+ onClick = { onClick() },
)
- .thenIf(DualShade.isEnabled) {
- Modifier.graphicsLayer {
- shape = RoundedCornerShape(25.dp)
- clip = true
- }
- .background(backgroundColor)
- .padding(horizontal = 8.dp, vertical = 4.dp)
- }
+ .clip(RoundedCornerShape(25.dp))
+ .background(
+ if (chipHighlight is HeaderChipHighlight.Strong)
+ MaterialTheme.colorScheme.chipHighlighted
+ else MaterialTheme.colorScheme.chipBackground
+ )
+ .padding(horizontal = 8.dp, vertical = 4.dp)
) {
content()
}
@@ -734,10 +706,9 @@ private fun NotificationIconChip(
@Composable
private fun SystemIconChip(
- viewModel: ShadeHeaderViewModel,
- isClickable: Boolean = false,
- showBackground: Boolean = false,
modifier: Modifier = Modifier,
+ chipHighlight: HeaderChipHighlight = HeaderChipHighlight.None,
+ onClick: (() -> Unit)? = null,
content: @Composable RowScope.() -> Unit,
) {
val interactionSource = remember { MutableInteractionSource() }
@@ -746,14 +717,14 @@ private fun SystemIconChip(
Modifier.clip(RoundedCornerShape(CollapsedHeight / 4))
.background(MaterialTheme.colorScheme.onScrimDim)
val backgroundColor =
- if (viewModel.highlightQuickSettingsIcons) MaterialTheme.colorScheme.chipHighlighted
+ if (chipHighlight is HeaderChipHighlight.Strong) MaterialTheme.colorScheme.chipHighlighted
else MaterialTheme.colorScheme.chipBackground
Row(
verticalAlignment = Alignment.CenterVertically,
modifier =
modifier
- .thenIf(showBackground) {
+ .thenIf(chipHighlight !is HeaderChipHighlight.None) {
Modifier.graphicsLayer {
shape = RoundedCornerShape(25.dp)
clip = true
@@ -761,11 +732,11 @@ private fun SystemIconChip(
.background(backgroundColor)
.padding(horizontal = 8.dp, vertical = 4.dp)
}
- .thenIf(isClickable) {
+ .thenIf(onClick != null) {
Modifier.clickable(
interactionSource = interactionSource,
indication = null,
- onClick = { viewModel.onSystemIconChipClicked() },
+ onClick = { onClick?.invoke() },
)
}
.thenIf(isHovered) { hoverModifier },
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index f829a0d6facf..5040490da8f6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -101,6 +101,7 @@ import com.android.systemui.scene.session.ui.composable.SaveableSession
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.Scene
import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
import com.android.systemui.shade.ui.viewmodel.ShadeSceneContentViewModel
import com.android.systemui.shade.ui.viewmodel.ShadeUserActionsViewModel
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
@@ -124,8 +125,6 @@ object Shade {
object Dimensions {
val HorizontalPadding = 16.dp
- val ScrimOverscrollLimit = 32.dp
- const val ScrimVisibilityThreshold = 5f
}
}
@@ -160,15 +159,22 @@ constructor(
override val userActions: Flow<Map<UserAction, UserActionResult>> = actionsViewModel.actions
@Composable
- override fun ContentScope.Content(modifier: Modifier) =
+ override fun ContentScope.Content(modifier: Modifier) {
+ val viewModel =
+ rememberViewModel("ShadeScene-viewModel") { contentViewModelFactory.create() }
+ val headerViewModel =
+ rememberViewModel("ShadeScene-headerViewModel") {
+ viewModel.shadeHeaderViewModelFactory.create()
+ }
+ val notificationsPlaceholderViewModel =
+ rememberViewModel("ShadeScene-notifPlaceholderViewModel") {
+ notificationsPlaceholderViewModelFactory.create()
+ }
ShadeScene(
notificationStackScrollView.get(),
- viewModel =
- rememberViewModel("ShadeScene-viewModel") { contentViewModelFactory.create() },
- notificationsPlaceholderViewModel =
- rememberViewModel("ShadeScene-notifPlaceholderViewModel") {
- notificationsPlaceholderViewModelFactory.create()
- },
+ viewModel = viewModel,
+ headerViewModel = headerViewModel,
+ notificationsPlaceholderViewModel = notificationsPlaceholderViewModel,
createTintedIconManager = tintedIconManagerFactory::create,
createBatteryMeterViewController = batteryMeterViewControllerFactory::create,
statusBarIconController = statusBarIconController,
@@ -180,6 +186,7 @@ constructor(
usingCollapsedLandscapeMedia =
Utils.useCollapsedMediaInLandscape(LocalContext.current.resources),
)
+ }
init {
qqsMediaHost.expansion = EXPANDED
@@ -196,6 +203,7 @@ constructor(
private fun ContentScope.ShadeScene(
notificationStackScrollView: NotificationScrollView,
viewModel: ShadeSceneContentViewModel,
+ headerViewModel: ShadeHeaderViewModel,
notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
@@ -207,13 +215,13 @@ private fun ContentScope.ShadeScene(
shadeSession: SaveableSession,
usingCollapsedLandscapeMedia: Boolean,
) {
-
val shadeMode by viewModel.shadeMode.collectAsStateWithLifecycle()
when (shadeMode) {
is ShadeMode.Single ->
SingleShade(
notificationStackScrollView = notificationStackScrollView,
viewModel = viewModel,
+ headerViewModel = headerViewModel,
notificationsPlaceholderViewModel = notificationsPlaceholderViewModel,
createTintedIconManager = createTintedIconManager,
createBatteryMeterViewController = createBatteryMeterViewController,
@@ -228,10 +236,8 @@ private fun ContentScope.ShadeScene(
SplitShade(
notificationStackScrollView = notificationStackScrollView,
viewModel = viewModel,
+ headerViewModel = headerViewModel,
notificationsPlaceholderViewModel = notificationsPlaceholderViewModel,
- createTintedIconManager = createTintedIconManager,
- createBatteryMeterViewController = createBatteryMeterViewController,
- statusBarIconController = statusBarIconController,
mediaCarouselController = mediaCarouselController,
mediaHost = qsMediaHost,
modifier = modifier,
@@ -245,6 +251,7 @@ private fun ContentScope.ShadeScene(
private fun ContentScope.SingleShade(
notificationStackScrollView: NotificationScrollView,
viewModel: ShadeSceneContentViewModel,
+ headerViewModel: ShadeHeaderViewModel,
notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
@@ -332,10 +339,7 @@ private fun ContentScope.SingleShade(
},
content = {
CollapsedShadeHeader(
- viewModelFactory = viewModel.shadeHeaderViewModelFactory,
- createTintedIconManager = createTintedIconManager,
- createBatteryMeterViewController = createBatteryMeterViewController,
- statusBarIconController = statusBarIconController,
+ viewModel = headerViewModel,
modifier = Modifier.layoutId(SingleShadeMeasurePolicy.LayoutId.ShadeHeader),
)
@@ -413,10 +417,8 @@ private fun ContentScope.SingleShade(
private fun ContentScope.SplitShade(
notificationStackScrollView: NotificationScrollView,
viewModel: ShadeSceneContentViewModel,
+ headerViewModel: ShadeHeaderViewModel,
notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
- createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
- createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
- statusBarIconController: StatusBarIconController,
mediaCarouselController: MediaCarouselController,
mediaHost: MediaHost,
modifier: Modifier = Modifier,
@@ -509,10 +511,7 @@ private fun ContentScope.SplitShade(
Column(modifier = Modifier.fillMaxSize()) {
CollapsedShadeHeader(
- viewModelFactory = viewModel.shadeHeaderViewModelFactory,
- createTintedIconManager = createTintedIconManager,
- createBatteryMeterViewController = createBatteryMeterViewController,
- statusBarIconController = statusBarIconController,
+ viewModel = headerViewModel,
modifier =
Modifier.then(brightnessMirrorShowingModifier)
.padding(horizontal = { unfoldTranslationXForStartSide.roundToInt() }),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt
index 93eca86e15cf..64aada52626b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt
@@ -5,17 +5,19 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.theme.colorAttr
-import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
+import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel.HeaderChipHighlight
@Composable
-fun VariableDayDate(viewModel: ShadeHeaderViewModel, modifier: Modifier = Modifier) {
- val longerText = viewModel.longerDateText.collectAsStateWithLifecycle()
- val shorterText = viewModel.shorterDateText.collectAsStateWithLifecycle()
-
+fun VariableDayDate(
+ longerDateText: String,
+ shorterDateText: String,
+ chipHighlight: HeaderChipHighlight,
+ modifier: Modifier = Modifier,
+) {
val textColor =
- if (viewModel.highlightNotificationIcons) colorAttr(android.R.attr.textColorPrimaryInverse)
+ if (chipHighlight is HeaderChipHighlight.Strong)
+ colorAttr(android.R.attr.textColorPrimaryInverse)
else colorAttr(android.R.attr.textColorPrimary)
Layout(
@@ -23,7 +25,7 @@ fun VariableDayDate(viewModel: ShadeHeaderViewModel, modifier: Modifier = Modifi
listOf(
{
Text(
- text = longerText.value,
+ text = longerDateText,
style = MaterialTheme.typography.bodyMedium,
color = textColor,
maxLines = 1,
@@ -31,7 +33,7 @@ fun VariableDayDate(viewModel: ShadeHeaderViewModel, modifier: Modifier = Modifi
},
{
Text(
- text = shorterText.value,
+ text = shorterDateText,
style = MaterialTheme.typography.bodyMedium,
color = textColor,
maxLines = 1,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt
index 675960832edc..43db50ad675f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt
@@ -124,35 +124,35 @@ class NotificationsShadeOverlayContentViewModelTest : SysuiTestCase() {
}
@Test
- fun showHeader_showsOnNarrowScreen() =
+ fun showClock_showsOnNarrowScreen() =
testScope.runTest {
kosmos.shadeRepository.setShadeLayoutWide(false)
// Shown when notifications are present.
kosmos.activeNotificationListRepository.setActiveNotifs(1)
runCurrent()
- assertThat(underTest.showHeader).isTrue()
+ assertThat(underTest.showClock).isTrue()
// Hidden when notifications are not present.
kosmos.activeNotificationListRepository.setActiveNotifs(0)
runCurrent()
- assertThat(underTest.showHeader).isFalse()
+ assertThat(underTest.showClock).isFalse()
}
@Test
- fun showHeader_hidesOnWideScreen() =
+ fun showClock_hidesOnWideScreen() =
testScope.runTest {
kosmos.shadeRepository.setShadeLayoutWide(true)
// Hidden when notifications are present.
kosmos.activeNotificationListRepository.setActiveNotifs(1)
runCurrent()
- assertThat(underTest.showHeader).isFalse()
+ assertThat(underTest.showClock).isFalse()
// Hidden when notifications are not present.
kosmos.activeNotificationListRepository.setActiveNotifs(0)
runCurrent()
- assertThat(underTest.showHeader).isFalse()
+ assertThat(underTest.showClock).isFalse()
}
private fun TestScope.lockDevice() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelTest.kt
new file mode 100644
index 000000000000..6e26fa119888
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelTest.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.ui.viewmodel
+
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.qs.composefragment.dagger.usingMediaInComposeFragment
+import com.android.systemui.scene.domain.startable.sceneContainerStartable
+import com.android.systemui.shade.domain.interactor.enableDualShade
+import com.android.systemui.shade.domain.interactor.shadeModeInteractor
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+@EnableSceneContainer
+class QuickSettingsContainerViewModelTest : SysuiTestCase() {
+
+ private val kosmos =
+ testKosmos().apply {
+ usingMediaInComposeFragment = false // This is not for the compose fragment
+ }
+ private val testScope = kosmos.testScope
+
+ private val shadeModeInteractor = kosmos.shadeModeInteractor
+
+ private val underTest by lazy {
+ kosmos.quickSettingsContainerViewModelFactory.create(supportsBrightnessMirroring = false)
+ }
+
+ @Before
+ fun setUp() {
+ kosmos.sceneContainerStartable.start()
+ kosmos.enableDualShade()
+ underTest.activateIn(testScope)
+ }
+
+ @Test
+ fun showHeader_showsOnNarrowScreen() =
+ testScope.runTest {
+ kosmos.enableDualShade(wideLayout = false)
+ val isShadeLayoutWide by collectLastValue(shadeModeInteractor.isShadeLayoutWide)
+ assertThat(isShadeLayoutWide).isFalse()
+
+ assertThat(underTest.showHeader).isTrue()
+ }
+
+ @Test
+ fun showHeader_hidesOnWideScreen() =
+ testScope.runTest {
+ kosmos.enableDualShade(wideLayout = true)
+ val isShadeLayoutWide by collectLastValue(shadeModeInteractor.isShadeLayoutWide)
+ assertThat(isShadeLayoutWide).isTrue()
+
+ assertThat(underTest.showHeader).isFalse()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt
index 01714d7a4b87..b532554f5dfd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt
@@ -127,24 +127,6 @@ class QuickSettingsShadeOverlayContentViewModelTest : SysuiTestCase() {
}
@Test
- fun showHeader_showsOnNarrowScreen() =
- testScope.runTest {
- kosmos.enableDualShade(wideLayout = false)
- runCurrent()
-
- assertThat(underTest.showHeader).isTrue()
- }
-
- @Test
- fun showHeader_hidesOnWideScreen() =
- testScope.runTest {
- kosmos.enableDualShade(wideLayout = true)
- runCurrent()
-
- assertThat(underTest.showHeader).isFalse()
- }
-
- @Test
fun onPanelShapeChanged() =
testScope.runTest {
var actual: ShadeScrimShape? = null
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
index 8ce20d2a05e9..d08c8a7c5974 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
@@ -23,6 +23,9 @@ import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.disableDualShade
import com.android.systemui.shade.domain.interactor.enableDualShade
+import com.android.systemui.shade.domain.interactor.enableSingleShade
+import com.android.systemui.shade.domain.interactor.enableSplitShade
+import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel.HeaderChipHighlight
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.fakeMobileIconsInteractor
import com.android.systemui.testKosmos
@@ -273,6 +276,116 @@ class ShadeHeaderViewModelTest : SysuiTestCase() {
assertThat(currentOverlays).doesNotContain(Overlays.QuickSettingsShade)
}
+ @Test
+ fun highlightChips_notifsOpenInSingleShade_bothNone() =
+ testScope.runTest {
+ kosmos.enableSingleShade()
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+ setScene(Scenes.Shade)
+ assertThat(currentScene).isEqualTo(Scenes.Shade)
+ assertThat(currentOverlays).isEmpty()
+
+ assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.None)
+ assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.None)
+ }
+
+ @Test
+ fun highlightChips_notifsOpenInSplitShade_bothNone() =
+ testScope.runTest {
+ kosmos.enableSplitShade()
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+ setScene(Scenes.Shade)
+ assertThat(currentScene).isEqualTo(Scenes.Shade)
+ assertThat(currentOverlays).isEmpty()
+
+ assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.None)
+ assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.None)
+ }
+
+ @Test
+ fun highlightChips_quickSettingsOpenInSingleShade_bothNone() =
+ testScope.runTest {
+ kosmos.enableSingleShade()
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+ setScene(Scenes.QuickSettings)
+ assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
+ assertThat(currentOverlays).isEmpty()
+
+ assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.None)
+ assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.None)
+ }
+
+ @Test
+ fun highlightChips_notifsOpenInDualShade_notifsStrongQuickSettingsWeak() =
+ testScope.runTest {
+ kosmos.enableDualShade()
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+
+ // Test the lockscreen scenario.
+ setScene(Scenes.Lockscreen)
+ setOverlay(Overlays.NotificationsShade)
+ assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.Strong)
+ assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.Weak)
+
+ // Test the unlocked scenario.
+ setDeviceEntered(true)
+ setScene(Scenes.Gone)
+ setOverlay(Overlays.NotificationsShade)
+ assertThat(currentScene).isEqualTo(Scenes.Gone)
+ assertThat(currentOverlays).isNotEmpty()
+ assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.Strong)
+ assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.Weak)
+ }
+
+ @Test
+ fun highlightChips_quickSettingsOpenInDualShade_notifsWeakQuickSettingsStrong() =
+ testScope.runTest {
+ kosmos.enableDualShade()
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+
+ // Test the lockscreen scenario.
+ setScene(Scenes.Lockscreen)
+ setOverlay(Overlays.QuickSettingsShade)
+ assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.Weak)
+ assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.Strong)
+
+ // Test the unlocked scenario.
+ setDeviceEntered(true)
+ setScene(Scenes.Gone)
+ setOverlay(Overlays.QuickSettingsShade)
+ assertThat(currentScene).isEqualTo(Scenes.Gone)
+ assertThat(currentOverlays).isNotEmpty()
+ assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.Weak)
+ assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.Strong)
+ }
+
+ @Test
+ fun highlightChips_noOverlaysInDualShade_bothNone() =
+ testScope.runTest {
+ kosmos.enableDualShade()
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+
+ // Test the lockscreen scenario.
+ setScene(Scenes.Lockscreen)
+ assertThat(currentOverlays).isEmpty()
+ assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.None)
+ assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.None)
+
+ // Test the unlocked scenario.
+ setDeviceEntered(true)
+ setScene(Scenes.Gone)
+ assertThat(currentScene).isEqualTo(Scenes.Gone)
+ assertThat(currentOverlays).isEmpty()
+ assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.None)
+ assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.None)
+ }
+
companion object {
private val SUB_1 =
SubscriptionModel(
diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt
index 8aad61a8c7cb..c7b165415aea 100644
--- a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt
@@ -52,18 +52,11 @@ constructor(
private val hydrator = Hydrator("NotificationsShadeOverlayContentViewModel.hydrator")
- val isShadeLayoutWide: Boolean by
+ val showClock: Boolean by
hydrator.hydratedStateOf(
- traceName = "isShadeLayoutWide",
- initialValue = shadeInteractor.isShadeLayoutWide.value,
- source = shadeInteractor.isShadeLayoutWide,
- )
-
- val showHeader: Boolean by
- hydrator.hydratedStateOf(
- traceName = "showHeader",
+ traceName = "showClock",
initialValue =
- shouldShowHeader(
+ shouldShowClock(
isShadeLayoutWide = shadeInteractor.isShadeLayoutWide.value,
areAnyNotificationsPresent =
activeNotificationsInteractor.areAnyNotificationsPresentValue,
@@ -72,7 +65,7 @@ constructor(
combine(
shadeInteractor.isShadeLayoutWide,
activeNotificationsInteractor.areAnyNotificationsPresent,
- this::shouldShowHeader,
+ this::shouldShowClock,
),
)
@@ -110,7 +103,7 @@ constructor(
shadeInteractor.collapseNotificationsShade(loggingReason = "shade scrim clicked")
}
- private fun shouldShowHeader(
+ private fun shouldShowClock(
isShadeLayoutWide: Boolean,
areAnyNotificationsPresent: Boolean,
): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index 85b677b65aeb..cf3b4969b07d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -573,8 +573,7 @@ constructor(
onDispose { qqsVisible.value = false }
}
val squishiness by
- viewModel.containerViewModel.quickQuickSettingsViewModel.squishinessViewModel
- .squishiness
+ viewModel.quickQuickSettingsViewModel.squishinessViewModel.squishiness
.collectAsStateWithLifecycle()
Column(modifier = modifier.sysuiResTag(ResIdTags.quickQsPanel)) {
@@ -607,9 +606,7 @@ constructor(
) {
val Tiles =
@Composable {
- QuickQuickSettings(
- viewModel = viewModel.containerViewModel.quickQuickSettingsViewModel
- )
+ QuickQuickSettings(viewModel = viewModel.quickQuickSettingsViewModel)
}
val Media =
@Composable {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
index 219fc2fdc5ec..0dade7438720 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
@@ -59,6 +59,7 @@ import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.qs.panels.domain.interactor.TileSquishinessInteractor
import com.android.systemui.qs.panels.ui.viewmodel.InFirstPageViewModel
import com.android.systemui.qs.panels.ui.viewmodel.MediaInRowInLandscapeViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.QuickQuickSettingsViewModel
import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel
import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.Scenes
@@ -94,6 +95,7 @@ class QSFragmentComposeViewModel
@AssistedInject
constructor(
containerViewModelFactory: QuickSettingsContainerViewModel.Factory,
+ quickQuickSettingsViewModelFactory: QuickQuickSettingsViewModel.Factory,
@Main private val resources: Resources,
footerActionsViewModelFactory: FooterActionsViewModel.Factory,
private val footerActionsController: FooterActionsController,
@@ -102,7 +104,7 @@ constructor(
DisableFlagsInteractor: DisableFlagsInteractor,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val largeScreenShadeInterpolator: LargeScreenShadeInterpolator,
- private val shadeInteractor: ShadeInteractor,
+ shadeInteractor: ShadeInteractor,
@ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
private val largeScreenHeaderHelper: LargeScreenHeaderHelper,
private val squishinessInteractor: TileSquishinessInteractor,
@@ -118,6 +120,8 @@ constructor(
) : Dumpable, ExclusiveActivatable() {
val containerViewModel = containerViewModelFactory.create(true)
+ val quickQuickSettingsViewModel = quickQuickSettingsViewModelFactory.create()
+
private val qqsMediaInRowViewModel = mediaInRowInLandscapeViewModelFactory.create(LOCATION_QQS)
private val qsMediaInRowViewModel = mediaInRowInLandscapeViewModelFactory.create(LOCATION_QS)
@@ -475,6 +479,7 @@ constructor(
}
launch { hydrator.activate() }
launch { containerViewModel.activate() }
+ launch { quickQuickSettingsViewModel.activate() }
launch { qqsMediaInRowViewModel.activate() }
launch { qsMediaInRowViewModel.activate() }
awaitCancellation()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt
index c7db04a6b7b2..aa8e4242f64e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt
@@ -19,43 +19,52 @@ package com.android.systemui.qs.ui.viewmodel
import androidx.compose.runtime.getValue
import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel
import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.qs.panels.ui.viewmodel.DetailsViewModel
import com.android.systemui.qs.panels.ui.viewmodel.EditModeViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.QuickQuickSettingsViewModel
import com.android.systemui.qs.panels.ui.viewmodel.TileGridViewModel
import com.android.systemui.qs.panels.ui.viewmodel.toolbar.ToolbarViewModel
+import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
class QuickSettingsContainerViewModel
@AssistedInject
constructor(
brightnessSliderViewModelFactory: BrightnessSliderViewModel.Factory,
- quickQuickSettingsViewModelFactory: QuickQuickSettingsViewModel.Factory,
shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory,
@Assisted supportsBrightnessMirroring: Boolean,
val tileGridViewModel: TileGridViewModel,
val editModeViewModel: EditModeViewModel,
val detailsViewModel: DetailsViewModel,
val toolbarViewModelFactory: ToolbarViewModel.Factory,
+ shadeModeInteractor: ShadeModeInteractor,
) : ExclusiveActivatable() {
+ private val hydrator = Hydrator("QuickSettingsContainerViewModel.hydrator")
+
val brightnessSliderViewModel =
brightnessSliderViewModelFactory.create(supportsBrightnessMirroring)
- val quickQuickSettingsViewModel = quickQuickSettingsViewModelFactory.create()
-
val shadeHeaderViewModel = shadeHeaderViewModelFactory.create()
+ val showHeader: Boolean by
+ hydrator.hydratedStateOf(
+ traceName = "showHeader",
+ initialValue = !shadeModeInteractor.isShadeLayoutWide.value,
+ source = shadeModeInteractor.isShadeLayoutWide.map { !it },
+ )
+
override suspend fun onActivated(): Nothing {
coroutineScope {
+ launch { hydrator.activate() }
launch { brightnessSliderViewModel.activate() }
- launch { quickQuickSettingsViewModel.activate() }
launch { shadeHeaderViewModel.activate() }
awaitCancellation()
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt
index d9df1ef36847..0add3f515ebf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt
@@ -16,13 +16,10 @@
package com.android.systemui.qs.ui.viewmodel
-import androidx.compose.runtime.getValue
import com.android.systemui.lifecycle.ExclusiveActivatable
-import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
import dagger.assisted.AssistedFactory
@@ -31,7 +28,6 @@ import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
/**
@@ -46,39 +42,10 @@ constructor(
val shadeInteractor: ShadeInteractor,
val sceneInteractor: SceneInteractor,
val notificationStackAppearanceInteractor: NotificationStackAppearanceInteractor,
- val shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory,
- quickSettingsContainerViewModelFactory: QuickSettingsContainerViewModel.Factory,
) : ExclusiveActivatable() {
- private val hydrator = Hydrator("QuickSettingsContainerViewModel.hydrator")
-
- val isShadeLayoutWide: Boolean by
- hydrator.hydratedStateOf(
- traceName = "isShadeLayoutWide",
- initialValue = shadeInteractor.isShadeLayoutWide.value,
- source = shadeInteractor.isShadeLayoutWide,
- )
-
- val showHeader: Boolean by
- hydrator.hydratedStateOf(
- traceName = "showHeader",
- initialValue = !shadeInteractor.isShadeLayoutWide.value,
- source = shadeInteractor.isShadeLayoutWide.map { !it },
- )
-
- val quickSettingsContainerViewModel = quickSettingsContainerViewModelFactory.create(false)
-
- val showQuickSettingsOverlayHeader: Boolean by
- hydrator.hydratedStateOf(
- traceName = "showQuickSettingsOverlayHeader",
- initialValue = shadeInteractor.isShadeLayoutWide.value,
- source = shadeInteractor.isShadeLayoutWide,
- )
-
override suspend fun onActivated(): Nothing {
coroutineScope {
- launch { hydrator.activate() }
-
launch {
sceneInteractor.currentScene.collect { currentScene ->
when (currentScene) {
@@ -101,8 +68,6 @@ constructor(
)
}
}
-
- launch { quickSettingsContainerViewModel.activate() }
}
awaitCancellation()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
index 96128df1b723..51fcf7da3c13 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
@@ -23,8 +23,10 @@ import android.icu.text.DateFormat
import android.icu.text.DisplayContext
import android.os.UserHandle
import android.provider.Settings
+import android.view.ViewGroup
import androidx.compose.runtime.getValue
import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.Hydrator
@@ -40,6 +42,10 @@ import com.android.systemui.shade.domain.interactor.PrivacyChipInteractor
import com.android.systemui.shade.domain.interactor.ShadeHeaderClockInteractor
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder
+import com.android.systemui.statusbar.phone.StatusBarLocation
+import com.android.systemui.statusbar.phone.ui.StatusBarIconController
+import com.android.systemui.statusbar.phone.ui.TintedIconManager
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
import dagger.assisted.AssistedFactory
@@ -67,27 +73,46 @@ constructor(
val mobileIconsViewModel: MobileIconsViewModel,
private val privacyChipInteractor: PrivacyChipInteractor,
private val clockInteractor: ShadeHeaderClockInteractor,
+ private val tintedIconManagerFactory: TintedIconManager.Factory,
+ private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
+ val statusBarIconController: StatusBarIconController,
+ val notificationIconContainerStatusBarViewBinder: NotificationIconContainerStatusBarViewBinder,
private val broadcastDispatcher: BroadcastDispatcher,
) : ExclusiveActivatable() {
private val hydrator = Hydrator("ShadeHeaderViewModel.hydrator")
- val highlightNotificationIcons: Boolean by
+ val createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager =
+ tintedIconManagerFactory::create
+
+ val createBatteryMeterViewController:
+ (ViewGroup, StatusBarLocation) -> BatteryMeterViewController =
+ batteryMeterViewControllerFactory::create
+
+ val notificationsChipHighlight: HeaderChipHighlight by
hydrator.hydratedStateOf(
- traceName = "highlightNotificationIcons",
- initialValue = false,
+ traceName = "notificationsChipHighlight",
+ initialValue = HeaderChipHighlight.None,
source =
sceneInteractor.currentOverlays.map { overlays ->
- Overlays.NotificationsShade in overlays
+ when {
+ Overlays.NotificationsShade in overlays -> HeaderChipHighlight.Strong
+ Overlays.QuickSettingsShade in overlays -> HeaderChipHighlight.Weak
+ else -> HeaderChipHighlight.None
+ }
},
)
- val highlightQuickSettingsIcons: Boolean by
+ val quickSettingsChipHighlight: HeaderChipHighlight by
hydrator.hydratedStateOf(
- traceName = "highlightQuickSettingsIcons",
- initialValue = false,
+ traceName = "quickSettingsChipHighlight",
+ initialValue = HeaderChipHighlight.None,
source =
sceneInteractor.currentOverlays.map { overlays ->
- Overlays.QuickSettingsShade in overlays
+ when {
+ Overlays.QuickSettingsShade in overlays -> HeaderChipHighlight.Strong
+ Overlays.NotificationsShade in overlays -> HeaderChipHighlight.Weak
+ else -> HeaderChipHighlight.None
+ }
},
)
@@ -225,6 +250,15 @@ constructor(
)
}
+ /** Represents the background highlight of a header icons chip. */
+ sealed interface HeaderChipHighlight {
+ data object None : HeaderChipHighlight
+
+ data object Weak : HeaderChipHighlight
+
+ data object Strong : HeaderChipHighlight
+ }
+
private fun updateDateTexts(invalidateFormats: Boolean) {
if (invalidateFormats) {
longerDateFormat.value = getFormatFromPattern(longerPattern)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
index 65e580cafcb5..583a9def8094 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
@@ -33,6 +33,7 @@ import com.android.systemui.qs.footerActionsViewModelFactory
import com.android.systemui.qs.panels.domain.interactor.tileSquishinessInteractor
import com.android.systemui.qs.panels.ui.viewmodel.inFirstPageViewModel
import com.android.systemui.qs.panels.ui.viewmodel.mediaInRowInLandscapeViewModelFactory
+import com.android.systemui.qs.panels.ui.viewmodel.quickQuickSettingsViewModelFactory
import com.android.systemui.qs.ui.viewmodel.quickSettingsContainerViewModelFactory
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.largeScreenHeaderHelper
@@ -48,6 +49,7 @@ val Kosmos.qsFragmentComposeViewModelFactory by
): QSFragmentComposeViewModel {
return QSFragmentComposeViewModel(
quickSettingsContainerViewModelFactory,
+ quickQuickSettingsViewModelFactory,
mainResources,
footerActionsViewModelFactory,
footerActionsController,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt
index f8fa5db4ddf7..3fc73cbc5552 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt
@@ -20,9 +20,9 @@ import com.android.systemui.brightness.ui.viewmodel.brightnessSliderViewModelFac
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.qs.panels.ui.viewmodel.detailsViewModel
import com.android.systemui.qs.panels.ui.viewmodel.editModeViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.quickQuickSettingsViewModelFactory
import com.android.systemui.qs.panels.ui.viewmodel.tileGridViewModel
import com.android.systemui.qs.panels.ui.viewmodel.toolbar.toolbarViewModelFactory
+import com.android.systemui.shade.domain.interactor.shadeModeInteractor
import com.android.systemui.shade.ui.viewmodel.shadeHeaderViewModelFactory
val Kosmos.quickSettingsContainerViewModelFactory by
@@ -33,13 +33,13 @@ val Kosmos.quickSettingsContainerViewModelFactory by
): QuickSettingsContainerViewModel {
return QuickSettingsContainerViewModel(
brightnessSliderViewModelFactory,
- quickQuickSettingsViewModelFactory,
shadeHeaderViewModelFactory,
supportsBrightnessMirroring,
tileGridViewModel,
editModeViewModel,
detailsViewModel,
toolbarViewModelFactory,
+ shadeModeInteractor,
)
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt
index 4f3b8f3541e1..108a20ab73d6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt
@@ -19,7 +19,6 @@ package com.android.systemui.qs.ui.viewmodel
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
-import com.android.systemui.shade.ui.viewmodel.shadeHeaderViewModelFactory
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
val Kosmos.quickSettingsShadeOverlayContentViewModel: QuickSettingsShadeOverlayContentViewModel by
@@ -28,7 +27,5 @@ val Kosmos.quickSettingsShadeOverlayContentViewModel: QuickSettingsShadeOverlayC
shadeInteractor = shadeInteractor,
sceneInteractor = sceneInteractor,
notificationStackAppearanceInteractor = notificationStackAppearanceInteractor,
- shadeHeaderViewModelFactory = shadeHeaderViewModelFactory,
- quickSettingsContainerViewModelFactory = quickSettingsContainerViewModelFactory,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt
index 7eb9f3472482..2be8acb845b9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.shade.ui.viewmodel
import android.content.applicationContext
+import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.plugins.activityStarter
@@ -24,8 +25,12 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.domain.interactor.privacyChipInteractor
import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder
+import com.android.systemui.statusbar.phone.ui.StatusBarIconController
+import com.android.systemui.statusbar.phone.ui.TintedIconManager
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.mobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.mobileIconsViewModel
+import org.mockito.kotlin.mock
val Kosmos.shadeHeaderViewModel: ShadeHeaderViewModel by
Kosmos.Fixture {
@@ -38,6 +43,11 @@ val Kosmos.shadeHeaderViewModel: ShadeHeaderViewModel by
mobileIconsViewModel = mobileIconsViewModel,
privacyChipInteractor = privacyChipInteractor,
clockInteractor = shadeHeaderClockInteractor,
+ tintedIconManagerFactory = mock<TintedIconManager.Factory>(),
+ batteryMeterViewControllerFactory = mock<BatteryMeterViewController.Factory>(),
+ statusBarIconController = mock<StatusBarIconController>(),
+ notificationIconContainerStatusBarViewBinder =
+ mock<NotificationIconContainerStatusBarViewBinder>(),
broadcastDispatcher = broadcastDispatcher,
)
}