diff options
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 +} |