summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt24
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt30
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt27
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsTransition.kt14
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt21
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsTransition.kt14
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt14
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt21
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsTransition.kt49
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt47
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt10
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt14
14 files changed, 239 insertions, 81 deletions
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt
index 76bd4ec2778a..8144d15020fa 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt
@@ -16,11 +16,16 @@
package com.android.systemui.common.ui.compose.windowinsets
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.asPaddingValues
+import androidx.compose.foundation.layout.displayCutout
+import androidx.compose.foundation.layout.systemBars
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.flow.StateFlow
@@ -31,6 +36,9 @@ val LocalDisplayCutout = staticCompositionLocalOf { DisplayCutout() }
/** The corner radius in px of the current display. */
val LocalScreenCornerRadius = staticCompositionLocalOf { 0.dp }
+/** The screen height in px without accounting for any screen insets (cutouts, status/nav bars) */
+val LocalRawScreenHeight = staticCompositionLocalOf { 0f }
+
@Composable
fun ScreenDecorProvider(
displayCutout: StateFlow<DisplayCutout>,
@@ -39,9 +47,23 @@ fun ScreenDecorProvider(
) {
val cutout by displayCutout.collectAsState()
val screenCornerRadiusDp = with(LocalDensity.current) { screenCornerRadius.toDp() }
+
+ val density = LocalDensity.current
+ val navBarHeight =
+ with(density) { WindowInsets.systemBars.asPaddingValues().calculateBottomPadding().toPx() }
+ val statusBarHeight = WindowInsets.systemBars.asPaddingValues().calculateTopPadding()
+ val displayCutoutHeight = WindowInsets.displayCutout.asPaddingValues().calculateTopPadding()
+ val screenHeight =
+ with(density) {
+ (LocalConfiguration.current.screenHeightDp.dp +
+ maxOf(statusBarHeight, displayCutoutHeight))
+ .toPx()
+ } + navBarHeight
+
CompositionLocalProvider(
LocalScreenCornerRadius provides screenCornerRadiusDp,
- LocalDisplayCutout provides cutout
+ LocalDisplayCutout provides cutout,
+ LocalRawScreenHeight provides screenHeight,
) {
content()
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 579e837a62d0..985d3a137914 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -24,7 +24,6 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
-import androidx.compose.foundation.layout.displayCutout
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.offset
@@ -57,7 +56,6 @@ import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.layout.positionInWindow
-import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.Dp
@@ -67,15 +65,17 @@ import androidx.compose.ui.util.lerp
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.NestedScrollBehavior
import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.SceneTransitionLayoutState
import com.android.compose.modifiers.height
+import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight
import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
-import com.android.systemui.notifications.ui.composable.Notifications.TransitionThresholds.EXPANSION_FOR_MAX_CORNER_RADIUS
-import com.android.systemui.notifications.ui.composable.Notifications.TransitionThresholds.EXPANSION_FOR_MAX_SCRIM_ALPHA
import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.ui.composable.ShadeHeader
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimRounding
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationTransitionThresholds.EXPANSION_FOR_MAX_CORNER_RADIUS
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationTransitionThresholds.EXPANSION_FOR_MAX_SCRIM_ALPHA
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import kotlin.math.roundToInt
@@ -168,14 +168,7 @@ fun SceneScope.NotificationScrollingStack(
val navBarHeight =
with(density) { WindowInsets.systemBars.asPaddingValues().calculateBottomPadding().toPx() }
- val statusBarHeight = WindowInsets.systemBars.asPaddingValues().calculateTopPadding()
- val displayCutoutHeight = WindowInsets.displayCutout.asPaddingValues().calculateTopPadding()
- val screenHeight =
- with(density) {
- (LocalConfiguration.current.screenHeightDp.dp +
- maxOf(statusBarHeight, displayCutoutHeight))
- .toPx()
- } + navBarHeight
+ val screenHeight = LocalRawScreenHeight.current
val stackHeight = viewModel.stackHeight.collectAsState()
@@ -253,7 +246,7 @@ fun SceneScope.NotificationScrollingStack(
scrimCornerRadius,
screenCornerRadius,
{ expansionFraction },
- layoutState.isTransitioningBetween(Scenes.Gone, Scenes.Shade)
+ layoutState.isNotificationScrimTransitioning(),
)
.let { scrimRounding.value.toRoundedCornerShape(it) }
clip = true
@@ -288,7 +281,7 @@ fun SceneScope.NotificationScrollingStack(
Modifier.fillMaxSize()
.graphicsLayer {
alpha =
- if (layoutState.isTransitioningBetween(Scenes.Gone, Scenes.Shade)) {
+ if (layoutState.isNotificationScrimTransitioning()) {
(expansionFraction / EXPANSION_FOR_MAX_SCRIM_ALPHA).coerceAtMost(1f)
} else 1f
}
@@ -425,7 +418,7 @@ private fun Modifier.debugBackground(
this
}
-fun ShadeScrimRounding.toRoundedCornerShape(radius: Dp): RoundedCornerShape {
+private fun ShadeScrimRounding.toRoundedCornerShape(radius: Dp): RoundedCornerShape {
val topRadius = if (isTopRounded) radius else 0.dp
val bottomRadius = if (isBottomRounded) radius else 0.dp
return RoundedCornerShape(
@@ -436,6 +429,13 @@ fun ShadeScrimRounding.toRoundedCornerShape(radius: Dp): RoundedCornerShape {
)
}
+private fun SceneTransitionLayoutState.isNotificationScrimTransitioning(): Boolean {
+ return isTransitioningBetween(Scenes.Gone, Scenes.Shade) ||
+ isTransitioningBetween(Scenes.Lockscreen, Scenes.Shade) ||
+ isTransitioningBetween(Scenes.Gone, Scenes.QuickSettings) ||
+ isTransitioningBetween(Scenes.Lockscreen, Scenes.QuickSettings)
+}
+
private const val TAG = "FlexiNotifs"
private val DEBUG_STACK_COLOR = Color(1f, 0f, 0f, 0.2f)
private val DEBUG_HUN_COLOR = Color(0f, 0f, 1f, 0.2f)
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 a3768346e7c6..fc32440e40fe 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
@@ -38,6 +38,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBars
+import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.rememberScrollState
@@ -50,19 +51,23 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.CompositingStrategy
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.SceneScope
import com.android.compose.animation.scene.TransitionState
import com.android.compose.animation.scene.animateSceneFloatAsState
import com.android.compose.windowsizeclass.LocalWindowSizeClass
import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.notifications.ui.composable.NotificationScrollingStack
import com.android.systemui.qs.footer.ui.compose.FooterActionsWithAnimatedVisibility
import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel
import com.android.systemui.res.R
@@ -72,10 +77,12 @@ 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.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import com.android.systemui.statusbar.phone.StatusBarIconController
import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager
import com.android.systemui.statusbar.phone.StatusBarLocation
import javax.inject.Inject
+import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.stateIn
@@ -87,6 +94,7 @@ class QuickSettingsScene
constructor(
@Application private val applicationScope: CoroutineScope,
private val viewModel: QuickSettingsSceneViewModel,
+ private val notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
private val tintedIconManagerFactory: TintedIconManager.Factory,
private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
private val statusBarIconController: StatusBarIconController,
@@ -106,6 +114,7 @@ constructor(
) {
QuickSettingsScene(
viewModel = viewModel,
+ notificationsPlaceholderViewModel = notificationsPlaceholderViewModel,
createTintedIconManager = tintedIconManagerFactory::create,
createBatteryMeterViewController = batteryMeterViewControllerFactory::create,
statusBarIconController = statusBarIconController,
@@ -117,6 +126,7 @@ constructor(
@Composable
private fun SceneScope.QuickSettingsScene(
viewModel: QuickSettingsSceneViewModel,
+ notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
statusBarIconController: StatusBarIconController,
@@ -135,8 +145,17 @@ private fun SceneScope.QuickSettingsScene(
)
// TODO(b/280887232): implement the real UI.
- Box(modifier = modifier.fillMaxSize().graphicsLayer { alpha = contentAlpha }) {
+ Box(
+ modifier =
+ modifier.fillMaxSize().graphicsLayer {
+ // 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.
+ compositingStrategy = CompositingStrategy.Offscreen
+ alpha = contentAlpha
+ }
+ ) {
val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
+ val screenHeight = LocalRawScreenHeight.current
BackHandler(
enabled = isCustomizing,
@@ -273,5 +292,11 @@ private fun SceneScope.QuickSettingsScene(
modifier = Modifier.align(Alignment.CenterHorizontally),
)
}
+ NotificationScrollingStack(
+ viewModel = notificationsPlaceholderViewModel,
+ maxScrimTop = { screenHeight },
+ modifier =
+ Modifier.fillMaxWidth().offset { IntOffset(x = 0, y = screenHeight.roundToInt()) },
+ )
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsTransition.kt
index 5bd158349f5e..851719d387f5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsTransition.kt
@@ -1,12 +1,14 @@
package com.android.systemui.scene.ui.composable.transitions
import androidx.compose.animation.core.tween
-import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
-import com.android.systemui.scene.shared.model.Scenes
+import kotlin.time.Duration.Companion.milliseconds
-fun TransitionBuilder.goneToQuickSettingsTransition() {
- spec = tween(durationMillis = 500)
-
- translate(Scenes.QuickSettings.rootElementKey, Edge.Top, true)
+fun TransitionBuilder.goneToQuickSettingsTransition(
+ durationScale: Double = 1.0,
+) {
+ spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
+ toQuickSettingsTransition()
}
+
+private val DefaultDuration = 500.milliseconds
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
index 9b59708fe81d..a0f410ab27fb 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
@@ -1,31 +1,14 @@
package com.android.systemui.scene.ui.composable.transitions
import androidx.compose.animation.core.tween
-import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
-import com.android.systemui.notifications.ui.composable.Notifications
-import com.android.systemui.qs.ui.composable.QuickSettings
-import com.android.systemui.shade.ui.composable.ShadeHeader
import kotlin.time.Duration.Companion.milliseconds
fun TransitionBuilder.goneToShadeTransition(
durationScale: Double = 1.0,
) {
- spec = tween(durationMillis = DefaultDuration.times(durationScale).inWholeMilliseconds.toInt())
-
- fractionRange(start = .58f) {
- fade(ShadeHeader.Elements.Clock)
- fade(ShadeHeader.Elements.CollapsedContentStart)
- fade(ShadeHeader.Elements.CollapsedContentEnd)
- fade(ShadeHeader.Elements.PrivacyChip)
- fade(QuickSettings.Elements.SplitShadeQuickSettings)
- fade(QuickSettings.Elements.FooterActions)
- }
- translate(
- QuickSettings.Elements.QuickQuickSettings,
- y = -ShadeHeader.Dimensions.CollapsedHeight * .66f
- )
- translate(Notifications.Elements.NotificationScrim, Edge.Top, false)
+ spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
+ toShadeTransition()
}
private val DefaultDuration = 500.milliseconds
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsTransition.kt
index 962d8227a016..319438c256dd 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsTransition.kt
@@ -1,12 +1,14 @@
package com.android.systemui.scene.ui.composable.transitions
import androidx.compose.animation.core.tween
-import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
-import com.android.systemui.scene.shared.model.Scenes
+import kotlin.time.Duration.Companion.milliseconds
-fun TransitionBuilder.lockscreenToQuickSettingsTransition() {
- spec = tween(durationMillis = 500)
-
- translate(Scenes.QuickSettings.rootElementKey, Edge.Top, true)
+fun TransitionBuilder.lockscreenToQuickSettingsTransition(
+ durationScale: Double = 1.0,
+) {
+ spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
+ toQuickSettingsTransition()
}
+
+private val DefaultDuration = 500.milliseconds
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt
index 48ab68a6f097..f078b8c9b78b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt
@@ -2,23 +2,13 @@ package com.android.systemui.scene.ui.composable.transitions
import androidx.compose.animation.core.tween
import com.android.compose.animation.scene.TransitionBuilder
-import com.android.systemui.notifications.ui.composable.Notifications
-import com.android.systemui.qs.ui.composable.QuickSettings
-import com.android.systemui.shade.ui.composable.Shade
-import com.android.systemui.shade.ui.composable.ShadeHeader
import kotlin.time.Duration.Companion.milliseconds
fun TransitionBuilder.lockscreenToShadeTransition(
durationScale: Double = 1.0,
) {
- spec = tween(durationMillis = DefaultDuration.times(durationScale).inWholeMilliseconds.toInt())
-
- fractionRange(end = 0.5f) { fade(Shade.Elements.BackgroundScrim) }
- translate(QuickSettings.Elements.Content, y = -ShadeHeader.Dimensions.CollapsedHeight * .66f)
- fractionRange(start = 0.5f) {
- fade(QuickSettings.Elements.Content)
- fade(Notifications.Elements.NotificationScrim)
- }
+ spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
+ toShadeTransition()
}
private val DefaultDuration = 500.milliseconds
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
index ffb6f3109015..a9e5be9b84d5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
@@ -6,9 +6,12 @@ import com.android.compose.animation.scene.TransitionBuilder
import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.shade.ui.composable.ShadeHeader
+import kotlin.time.Duration.Companion.milliseconds
-fun TransitionBuilder.shadeToQuickSettingsTransition() {
- spec = tween(durationMillis = 500)
+fun TransitionBuilder.shadeToQuickSettingsTransition(
+ durationScale: Double = 1.0,
+) {
+ spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
translate(Notifications.Elements.NotificationScrim, Edge.Bottom)
timestampRange(endMillis = 83) { fade(QuickSettings.Elements.FooterActions) }
@@ -24,9 +27,15 @@ fun TransitionBuilder.shadeToQuickSettingsTransition() {
)
translate(ShadeHeader.Elements.ShadeCarrierGroup, y = -ShadeHeader.Dimensions.CollapsedHeight)
- fractionRange(end = .14f) { fade(ShadeHeader.Elements.CollapsedContentStart) }
- fractionRange(end = .14f) { fade(ShadeHeader.Elements.CollapsedContentEnd) }
+ fractionRange(end = .14f) {
+ fade(ShadeHeader.Elements.CollapsedContentStart)
+ fade(ShadeHeader.Elements.CollapsedContentEnd)
+ }
- fractionRange(start = .58f) { fade(ShadeHeader.Elements.ExpandedContent) }
- fractionRange(start = .58f) { fade(ShadeHeader.Elements.ShadeCarrierGroup) }
+ fractionRange(start = .58f) {
+ fade(ShadeHeader.Elements.ExpandedContent)
+ fade(ShadeHeader.Elements.ShadeCarrierGroup)
+ }
}
+
+private val DefaultDuration = 500.milliseconds
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsTransition.kt
new file mode 100644
index 000000000000..e0a6310634c0
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsTransition.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 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.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.notifications.ui.composable.Notifications
+import com.android.systemui.qs.ui.composable.QuickSettings
+import com.android.systemui.shade.ui.composable.ShadeHeader
+import kotlin.time.Duration.Companion.milliseconds
+
+fun TransitionBuilder.toQuickSettingsTransition(
+ durationScale: Double = 1.0,
+) {
+ spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
+
+ translate(
+ ShadeHeader.Elements.ExpandedContent,
+ y = -(ShadeHeader.Dimensions.ExpandedHeight - ShadeHeader.Dimensions.CollapsedHeight)
+ )
+ translate(ShadeHeader.Elements.Clock, y = -ShadeHeader.Dimensions.CollapsedHeight)
+ translate(ShadeHeader.Elements.ShadeCarrierGroup, y = -ShadeHeader.Dimensions.CollapsedHeight)
+
+ fractionRange(start = .58f) {
+ fade(ShadeHeader.Elements.ExpandedContent)
+ fade(ShadeHeader.Elements.Clock)
+ fade(ShadeHeader.Elements.ShadeCarrierGroup)
+ }
+
+ translate(QuickSettings.Elements.Content, y = -ShadeHeader.Dimensions.ExpandedHeight * .66f)
+ translate(Notifications.Elements.NotificationScrim, Edge.Top, false)
+}
+
+private val DefaultDuration = 500.milliseconds
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt
new file mode 100644
index 000000000000..2f5921703367
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 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.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.notifications.ui.composable.Notifications
+import com.android.systemui.qs.ui.composable.QuickSettings
+import com.android.systemui.shade.ui.composable.ShadeHeader
+import kotlin.time.Duration.Companion.milliseconds
+
+fun TransitionBuilder.toShadeTransition(
+ durationScale: Double = 1.0,
+) {
+ spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
+
+ fractionRange(start = .58f) {
+ fade(ShadeHeader.Elements.Clock)
+ fade(ShadeHeader.Elements.CollapsedContentStart)
+ fade(ShadeHeader.Elements.CollapsedContentEnd)
+ fade(ShadeHeader.Elements.PrivacyChip)
+ fade(QuickSettings.Elements.SplitShadeQuickSettings)
+ fade(QuickSettings.Elements.FooterActions)
+ }
+ translate(
+ QuickSettings.Elements.QuickQuickSettings,
+ y = -ShadeHeader.Dimensions.CollapsedHeight * .66f
+ )
+ translate(Notifications.Elements.NotificationScrim, Edge.Top, false)
+}
+
+private val DefaultDuration = 500.milliseconds
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 c6c6f5773101..516e14001698 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
@@ -531,8 +531,14 @@ private fun shouldUseExpandedFormat(state: TransitionState): Boolean {
state.currentScene == Scenes.QuickSettings
}
is TransitionState.Transition -> {
- (state.isTransitioning(Scenes.Shade, Scenes.QuickSettings) && state.progress >= 0.5) ||
- (state.isTransitioning(Scenes.QuickSettings, Scenes.Shade) && state.progress < 0.5)
+ ((state.isTransitioning(Scenes.Shade, Scenes.QuickSettings) ||
+ state.isTransitioning(Scenes.Gone, Scenes.QuickSettings) ||
+ state.isTransitioning(Scenes.Lockscreen, Scenes.QuickSettings)) &&
+ state.progress >= 0.5) ||
+ ((state.isTransitioning(Scenes.QuickSettings, Scenes.Shade) ||
+ state.isTransitioning(Scenes.QuickSettings, Scenes.Gone) ||
+ state.isTransitioning(Scenes.QuickSettings, Scenes.Lockscreen)) &&
+ state.progress <= 0.5)
}
}
}
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 84b1a4b77f07..944d6ef76272 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
@@ -47,6 +47,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.CompositingStrategy
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.layout
@@ -200,13 +201,15 @@ private fun SceneScope.SingleShade(
animateSceneFloatAsState(value = 1f, key = QuickSettings.SharedValues.TilesSquishiness)
val isClickable by viewModel.isClickable.collectAsState()
- Box(
- modifier =
- modifier
- .element(Shade.Elements.BackgroundScrim)
- .background(colorResource(R.color.shade_scrim_background_dark)),
- )
- Box {
+ // 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.
+ Box(modifier = modifier.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)) {
+ Box(
+ modifier =
+ Modifier.fillMaxSize()
+ .element(Shade.Elements.BackgroundScrim)
+ .background(colorResource(R.color.shade_scrim_background_dark)),
+ )
Layout(
contents =
listOf(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
index 516ec319ceb9..4ae5e69dc8df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
@@ -22,11 +22,12 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.scene.shared.model.Scenes.Shade
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimClipping
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationTransitionThresholds.EXPANSION_FOR_DELAYED_STACK_FADE_IN
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationTransitionThresholds.EXPANSION_FOR_MAX_SCRIM_ALPHA
import com.android.systemui.util.kotlin.FlowDumperImpl
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -52,8 +53,9 @@ constructor(
val expandFraction: Flow<Float> =
combine(
shadeInteractor.shadeExpansion,
+ shadeInteractor.qsExpansion,
sceneInteractor.transitionState,
- ) { shadeExpansion, transitionState ->
+ ) { shadeExpansion, qsExpansion, transitionState ->
when (transitionState) {
is ObservableTransitionState.Idle -> {
if (transitionState.scene == Scenes.Lockscreen) {
@@ -70,6 +72,16 @@ constructor(
transitionState.toScene == Scenes.Shade)
) {
1f
+ } else if (
+ (transitionState.fromScene == Scenes.Gone ||
+ transitionState.fromScene == Scenes.Lockscreen) &&
+ transitionState.toScene == Scenes.QuickSettings
+ ) {
+ // during QS expansion, increase fraction at same rate as scrim alpha,
+ // but start when scrim alpha is at EXPANSION_FOR_DELAYED_STACK_FADE_IN.
+ (qsExpansion / EXPANSION_FOR_MAX_SCRIM_ALPHA -
+ EXPANSION_FOR_DELAYED_STACK_FADE_IN)
+ .coerceIn(0f, 1f)
} else {
shadeExpansion
}
@@ -125,5 +137,5 @@ constructor(
/** Whether the notification stack is scrollable or not. */
val isScrollable: Flow<Boolean> =
- sceneInteractor.currentScene.map { it == Shade }.dumpWhileCollecting("isScrollable")
+ sceneInteractor.currentScene.map { it == Scenes.Shade }.dumpWhileCollecting("isScrollable")
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index ca19da58a135..bf3b2c94ab7e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -93,10 +93,10 @@ constructor(
val headsUpHeight: StateFlow<Float> = interactor.headsUpHeight.dumpValue("headsUpHeight")
/**
- * The amount [0-1] that the shade has been opened. At 0, the shade is closed; at 1, the shade
- * is open.
+ * The amount [0-1] that the shade or quick settings has been opened. At 0, the shade is closed;
+ * at 1, either the shade or quick settings is open.
*/
- val expandFraction: Flow<Float> = shadeInteractor.shadeExpansion.dumpValue("expandFraction")
+ val expandFraction: Flow<Float> = shadeInteractor.anyExpansion.dumpValue("expandFraction")
/**
* The amount in px that the notification stack should scroll due to internal expansion. This
@@ -111,3 +111,11 @@ constructor(
interactor.setScrolledToTop(scrolledToTop)
}
}
+
+// Expansion fraction thresholds (between 0-1f) at which the corresponding value should be
+// at its maximum, given they are at their minimum value at expansion = 0f.
+object NotificationTransitionThresholds {
+ const val EXPANSION_FOR_MAX_CORNER_RADIUS = 0.1f
+ const val EXPANSION_FOR_MAX_SCRIM_ALPHA = 0.3f
+ const val EXPANSION_FOR_DELAYED_STACK_FADE_IN = 0.5f
+}