summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author burakov <burakov@google.com> 2025-02-18 00:20:04 +0000
committer burakov <burakov@google.com> 2025-03-06 09:55:23 +0000
commit2a84385f076a487c6029a56fc809b6294ffd1ecb (patch)
treeed2565358396d32fd027d1757e54aeb3c084e1a6
parent732243fc4bc7c7b4c566011cd0b2730a55f7390f (diff)
[Dual Shade] Refactor + show the status bar clock on QS shade.
BONUS: Remove `isShadeLayout` from `ShadeHeaderViewModel`. Fix: 397197000 Test: Added unit tests. Test: Existing unit tests still pass. Test: Manually by opening the quick settings shade on both narrow and wide screens and verifying the status bar clock is shown in both cases. Flag: com.android.systemui.scene_container Change-Id: I57b812607508121d6fe59b42e2b3bc490bd27b0d
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt3
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt34
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt15
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt29
5 files changed, 75 insertions, 36 deletions
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 061fdd99eb1b..0a711487ccb1 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
@@ -356,7 +356,8 @@ private fun ContentScope.QuickSettingsScene(
modifier = Modifier.padding(horizontal = 16.dp),
)
}
- else -> CollapsedShadeHeader(viewModel = headerViewModel)
+ else ->
+ CollapsedShadeHeader(viewModel = headerViewModel, isSplitShade = false)
}
Spacer(modifier = Modifier.height(16.dp))
// This view has its own horizontal padding
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 23baeacd76ec..86c8fc34a63c 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
@@ -127,6 +127,7 @@ object ShadeHeader {
@Composable
fun ContentScope.CollapsedShadeHeader(
viewModel: ShadeHeaderViewModel,
+ isSplitShade: Boolean,
modifier: Modifier = Modifier,
) {
val cutoutLocation = LocalDisplayCutout.current.location
@@ -141,8 +142,6 @@ fun ContentScope.CollapsedShadeHeader(
}
}
- 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.
@@ -154,7 +153,7 @@ fun ContentScope.CollapsedShadeHeader(
horizontalArrangement = Arrangement.spacedBy(5.dp),
modifier = Modifier.padding(horizontal = horizontalPadding),
) {
- Clock(scale = 1f, onClick = viewModel::onClockClicked)
+ Clock(onClick = viewModel::onClockClicked)
VariableDayDate(
longerDateText = viewModel.longerDateText,
shorterDateText = viewModel.shorterDateText,
@@ -184,11 +183,11 @@ fun ContentScope.CollapsedShadeHeader(
Modifier.element(ShadeHeader.Elements.CollapsedContentEnd)
.padding(horizontal = horizontalPadding),
) {
- if (isShadeLayoutWide) {
+ if (isSplitShade) {
ShadeCarrierGroup(viewModel = viewModel)
}
SystemIconChip(
- onClick = viewModel::onSystemIconChipClicked.takeIf { isShadeLayoutWide }
+ onClick = viewModel::onSystemIconChipClicked.takeIf { isSplitShade }
) {
StatusIcons(
viewModel = viewModel,
@@ -233,13 +232,11 @@ fun ContentScope.ExpandedShadeHeader(
.defaultMinSize(minHeight = ShadeHeader.Dimensions.ExpandedHeight),
) {
Box(modifier = Modifier.fillMaxWidth()) {
- Box {
- Clock(
- scale = 2.57f,
- onClick = viewModel::onClockClicked,
- modifier = Modifier.align(Alignment.CenterStart),
- )
- }
+ Clock(
+ onClick = viewModel::onClockClicked,
+ modifier = Modifier.align(Alignment.CenterStart),
+ scale = 2.57f,
+ )
Box(
modifier =
Modifier.element(ShadeHeader.Elements.ShadeCarrierGroup).fillMaxWidth()
@@ -291,8 +288,6 @@ fun ContentScope.OverlayShadeHeader(
val horizontalPadding =
max(LocalScreenCornerRadius.current / 2f, Shade.Dimensions.HorizontalPadding)
- 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.
@@ -301,16 +296,15 @@ fun ContentScope.OverlayShadeHeader(
startContent = {
Row(
verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(5.dp),
modifier = Modifier.padding(horizontal = horizontalPadding),
) {
val chipHighlight = viewModel.notificationsChipHighlight
- if (isShadeLayoutWide) {
+ if (viewModel.showClock) {
Clock(
- scale = 1f,
onClick = viewModel::onClockClicked,
modifier = Modifier.padding(horizontal = 4.dp),
)
- Spacer(modifier = Modifier.width(5.dp))
}
NotificationsChip(
onClick = viewModel::onNotificationIconChipClicked,
@@ -437,7 +431,11 @@ private fun CutoutAwareShadeHeader(
}
@Composable
-private fun ContentScope.Clock(scale: Float, onClick: () -> Unit, modifier: Modifier = Modifier) {
+private fun ContentScope.Clock(
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+ scale: Float = 1f,
+) {
val layoutDirection = LocalLayoutDirection.current
ElementWithValues(key = ShadeHeader.Elements.Clock, modifier = modifier) {
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 5040490da8f6..885d34fb95c9 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
@@ -56,11 +56,11 @@ import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
+import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.ContentScope
import com.android.compose.animation.scene.ElementKey
@@ -68,6 +68,7 @@ import com.android.compose.animation.scene.LowestZIndexContentPicker
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.animateContentDpAsState
+import com.android.compose.animation.scene.animateContentFloatAsState
import com.android.compose.animation.scene.animateSceneFloatAsState
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.modifiers.padding
@@ -223,9 +224,6 @@ private fun ContentScope.ShadeScene(
viewModel = viewModel,
headerViewModel = headerViewModel,
notificationsPlaceholderViewModel = notificationsPlaceholderViewModel,
- createTintedIconManager = createTintedIconManager,
- createBatteryMeterViewController = createBatteryMeterViewController,
- statusBarIconController = statusBarIconController,
mediaCarouselController = mediaCarouselController,
mediaHost = qqsMediaHost,
modifier = modifier,
@@ -253,9 +251,6 @@ private fun ContentScope.SingleShade(
viewModel: ShadeSceneContentViewModel,
headerViewModel: ShadeHeaderViewModel,
notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
- createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
- createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
- statusBarIconController: StatusBarIconController,
mediaCarouselController: MediaCarouselController,
mediaHost: MediaHost,
modifier: Modifier = Modifier,
@@ -340,6 +335,7 @@ private fun ContentScope.SingleShade(
content = {
CollapsedShadeHeader(
viewModel = headerViewModel,
+ isSplitShade = false,
modifier = Modifier.layoutId(SingleShadeMeasurePolicy.LayoutId.ShadeHeader),
)
@@ -434,15 +430,13 @@ private fun ContentScope.SplitShade(
val footerActionsViewModel =
remember(lifecycleOwner, viewModel) { viewModel.getFooterActionsViewModel(lifecycleOwner) }
val tileSquishiness by
- animateSceneFloatAsState(
+ animateContentFloatAsState(
value = 1f,
key = QuickSettings.SharedValues.TilesSquishiness,
canOverflow = false,
)
val unfoldTranslationXForStartSide by
viewModel.unfoldTranslationX(isOnStartSide = true).collectAsStateWithLifecycle(0f)
- val unfoldTranslationXForEndSide by
- viewModel.unfoldTranslationX(isOnStartSide = false).collectAsStateWithLifecycle(0f)
val notificationStackPadding = dimensionResource(id = R.dimen.notification_side_paddings)
val navBarBottomHeight = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
@@ -512,6 +506,7 @@ private fun ContentScope.SplitShade(
Column(modifier = Modifier.fillMaxSize()) {
CollapsedShadeHeader(
viewModel = headerViewModel,
+ isSplitShade = true,
modifier =
Modifier.then(brightnessMirrorShowingModifier)
.padding(horizontal = { unfoldTranslationXForStartSide.roundToInt() }),
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 a832f486ef32..04eb709b8894 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
@@ -94,6 +94,36 @@ class ShadeHeaderViewModelTest : SysuiTestCase() {
}
@Test
+ fun showClock_wideLayout_returnsTrue() =
+ testScope.runTest {
+ kosmos.enableDualShade(wideLayout = true)
+
+ setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.NotificationsShade)
+ assertThat(underTest.showClock).isTrue()
+
+ setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.QuickSettingsShade)
+ assertThat(underTest.showClock).isTrue()
+ }
+
+ @Test
+ fun showClock_narrowLayoutOnNotificationsShade_returnsFalse() =
+ testScope.runTest {
+ kosmos.enableDualShade(wideLayout = false)
+ setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.NotificationsShade)
+
+ assertThat(underTest.showClock).isFalse()
+ }
+
+ @Test
+ fun showClock_narrowLayoutOnQuickSettingsShade_returnsTrue() =
+ testScope.runTest {
+ kosmos.enableDualShade(wideLayout = false)
+ setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.QuickSettingsShade)
+
+ assertThat(underTest.showClock).isTrue()
+ }
+
+ @Test
fun onShadeCarrierGroupClicked_launchesNetworkSettings() =
testScope.runTest {
val activityStarter = kosmos.activityStarter
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 20b44d73e097..5609326362fc 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
@@ -26,6 +26,7 @@ import androidx.compose.material3.ColorScheme
import androidx.compose.runtime.getValue
import androidx.compose.ui.graphics.Color
import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.compose.animation.scene.OverlayKey
import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.Hydrator
@@ -86,6 +87,22 @@ constructor(
(ViewGroup, StatusBarLocation) -> BatteryMeterViewController =
batteryMeterViewControllerFactory::create
+ val showClock: Boolean by
+ hydrator.hydratedStateOf(
+ traceName = "showClock",
+ initialValue =
+ shouldShowClock(
+ isShadeLayoutWide = shadeInteractor.isShadeLayoutWide.value,
+ overlays = sceneInteractor.currentOverlays.value,
+ ),
+ source =
+ combine(
+ shadeInteractor.isShadeLayoutWide,
+ sceneInteractor.currentOverlays,
+ ::shouldShowClock,
+ ),
+ )
+
val notificationsChipHighlight: HeaderChipHighlight by
hydrator.hydratedStateOf(
traceName = "notificationsChipHighlight",
@@ -114,13 +131,6 @@ constructor(
},
)
- val isShadeLayoutWide: Boolean by
- hydrator.hydratedStateOf(
- traceName = "isShadeLayoutWide",
- initialValue = shadeInteractor.isShadeLayoutWide.value,
- source = shadeInteractor.isShadeLayoutWide,
- )
-
/** True if there is exactly one mobile connection. */
val isSingleCarrier: StateFlow<Boolean> = mobileIconsInteractor.isSingleCarrier
@@ -271,6 +281,11 @@ constructor(
}
}
+ private fun shouldShowClock(isShadeLayoutWide: Boolean, overlays: Set<OverlayKey>): Boolean {
+ // Notifications shade on narrow layout renders its own clock. Hide the header clock.
+ return isShadeLayoutWide || Overlays.NotificationsShade !in overlays
+ }
+
private fun getFormatFromPattern(pattern: String?): DateFormat {
val format = DateFormat.getInstanceForSkeleton(pattern, Locale.getDefault())
format.setContext(DisplayContext.CAPITALIZATION_FOR_STANDALONE)