Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 12087a56 authored by burakov's avatar burakov Committed by Danny Burakov
Browse files

[Dual Shade] Introduce `verticalContainerReveal` for shade transitions.

This also shifts the creation of the scene transitions spec to run
dynamically in the composable, where it can be passed parameters such as
container reveal haptics.

Fix: 393558661
Bug: 391151101
Bug: 376438969
Test: Unit tests still pass.
Test: Manually verified by expanding and collapsing the shade on both
 narrow and large screens.
Flag: com.android.systemui.scene_container
Change-Id: Ica08597f27d0e6163eba2b8345de64e140ab0d60
parent af9d3ada
Loading
Loading
Loading
Loading
+9 −3
Original line number Diff line number Diff line
@@ -34,13 +34,13 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.LocalView
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.OverlayKey
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayout
import com.android.compose.animation.scene.SceneTransitions
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.observableTransitionState
@@ -85,7 +85,7 @@ fun SceneContainer(
    sceneByKey: Map<SceneKey, Scene>,
    overlayByKey: Map<OverlayKey, Overlay>,
    initialSceneKey: SceneKey,
    sceneTransitions: SceneTransitions,
    transitionsBuilder: SceneContainerTransitionsBuilder,
    dataSourceDelegator: SceneDataSourceDelegator,
    qsSceneAdapter: Provider<QSSceneAdapter>,
    sceneJankMonitorFactory: SceneJankMonitor.Factory,
@@ -97,6 +97,12 @@ fun SceneContainer(
    val sceneJankMonitor =
        rememberActivated(traceName = "sceneJankMonitor") { sceneJankMonitorFactory.create() }

    val hapticFeedback = LocalHapticFeedback.current
    val sceneTransitions =
        remember(hapticFeedback) {
            transitionsBuilder.build(viewModel.hapticsViewModel.getRevealHaptics(hapticFeedback))
        }

    val state =
        rememberMutableSceneTransitionLayoutState(
            initialScene = initialSceneKey,
@@ -161,7 +167,7 @@ fun SceneContainer(
        if (isFullWidthShade()) stretchOverscrollEffectFactory else offsetOverscrollEffectFactory

    // Inflate qsView here so that shade has the correct qqs height in the first measure pass after
    // rebooting
    // rebooting.
    if (
        viewModel.allContentKeys.contains(Scenes.QuickSettings) ||
            viewModel.allContentKeys.contains(Scenes.Shade)
+192 −154
Original line number Diff line number Diff line
package com.android.systemui.scene.ui.composable

import com.android.compose.animation.scene.SceneTransitions
import com.android.compose.animation.scene.TransitionKey
import com.android.compose.animation.scene.reveal.ContainerRevealHaptics
import com.android.compose.animation.scene.transitions
import com.android.internal.jank.Cuj
import com.android.systemui.notifications.ui.composable.Notifications
@@ -32,7 +34,7 @@ import com.android.systemui.scene.ui.composable.transitions.toQuickSettingsShade
import com.android.systemui.shade.ui.composable.Shade

/**
 * Comprehensive definition of all transitions between scenes in [SceneContainer].
 * Comprehensive definition of all transitions between scenes and overlays in [SceneContainer].
 *
 * Transitions are automatically reversible, so define only one transition per scene pair. By\
 * convention, use the more common transition direction when defining the pair order, e.g.
@@ -43,7 +45,9 @@ import com.android.systemui.shade.ui.composable.Shade
 *
 * Please keep the list sorted alphabetically.
 */
val SceneContainerTransitions = transitions {
class SceneContainerTransitions : SceneContainerTransitionsBuilder {
    override fun build(revealHaptics: ContainerRevealHaptics): SceneTransitions {
        return transitions {
            interruptionHandler = SceneContainerInterruptionHandler

            // Scene transitions
@@ -52,17 +56,25 @@ val SceneContainerTransitions = transitions {
            from(Scenes.Dream, to = Scenes.Bouncer) { dreamToBouncerTransition() }
            from(Scenes.Dream, to = Scenes.Communal) { dreamToCommunalTransition() }
            from(Scenes.Dream, to = Scenes.Gone) { dreamToGoneTransition() }
    from(Scenes.Dream, to = Scenes.Shade, cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE) {
            from(
                Scenes.Dream,
                to = Scenes.Shade,
                cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, // NOTYPO
            ) {
                dreamToShadeTransition()
            }
    from(Scenes.Gone, to = Scenes.Shade, cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE) {
            from(
                Scenes.Gone,
                to = Scenes.Shade,
                cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, // NOTYPO
            ) {
                goneToShadeTransition()
            }
            from(
                Scenes.Gone,
                to = Scenes.Shade,
                key = ToSplitShade,
        cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE,
                cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, // NOTYPO
            ) {
                goneToSplitShadeTransition()
            }
@@ -70,14 +82,14 @@ val SceneContainerTransitions = transitions {
                Scenes.Gone,
                to = Scenes.Shade,
                key = SlightlyFasterShadeCollapse,
        cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE,
                cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, // NOTYPO
            ) {
                goneToShadeTransition(durationScale = 0.9)
            }
            from(
                Scenes.Gone,
                to = Scenes.QuickSettings,
        cuj = Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE,
                cuj = Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE, // NOTYPO
            ) {
                goneToQuickSettingsTransition()
            }
@@ -85,7 +97,7 @@ val SceneContainerTransitions = transitions {
                Scenes.Gone,
                to = Scenes.QuickSettings,
                key = SlightlyFasterShadeCollapse,
        cuj = Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE,
                cuj = Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE, // NOTYPO
            ) {
                goneToQuickSettingsTransition(durationScale = 0.9)
            }
@@ -101,14 +113,18 @@ val SceneContainerTransitions = transitions {
            }
            from(Scenes.Lockscreen, to = Scenes.Communal) { lockscreenToCommunalTransition() }
            from(Scenes.Lockscreen, to = Scenes.Dream) { lockscreenToDreamTransition() }
    from(Scenes.Lockscreen, to = Scenes.Shade, cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE) {
            from(
                Scenes.Lockscreen,
                to = Scenes.Shade,
                cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, // NOTYPO
            ) {
                lockscreenToShadeTransition()
            }
            from(
                Scenes.Lockscreen,
                to = Scenes.Shade,
                key = ToSplitShade,
        cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE,
                cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, // NOTYPO
            ) {
                lockscreenToSplitShadeTransition()
                sharedElement(Shade.Elements.BackgroundScrim, enabled = false)
@@ -117,14 +133,14 @@ val SceneContainerTransitions = transitions {
                Scenes.Lockscreen,
                to = Scenes.Shade,
                key = SlightlyFasterShadeCollapse,
        cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE,
                cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, // NOTYPO
            ) {
                lockscreenToShadeTransition(durationScale = 0.9)
            }
            from(
                Scenes.Lockscreen,
                to = Scenes.QuickSettings,
        cuj = Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE,
                cuj = Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE, // NOTYPO
            ) {
                lockscreenToQuickSettingsTransition()
            }
@@ -132,74 +148,96 @@ val SceneContainerTransitions = transitions {
            from(
                Scenes.QuickSettings,
                to = Scenes.Shade,
        cuj = Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE,
                cuj = Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE, // NOTYPO
            ) {
                reversed { shadeToQuickSettingsTransition() }
        sharedElement(Notifications.Elements.HeadsUpNotificationPlaceholder, enabled = false)
                sharedElement(
                    Notifications.Elements.HeadsUpNotificationPlaceholder,
                    enabled = false,
                )
            }
            from(
                Scenes.Shade,
                to = Scenes.QuickSettings,
        cuj = Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE,
                cuj = Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE, // NOTYPO
            ) {
                shadeToQuickSettingsTransition()
            }
    from(Scenes.Shade, to = Scenes.Lockscreen, cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE) {
            from(
                Scenes.Shade,
                to = Scenes.Lockscreen,
                cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, // NOTYPO
            ) {
                reversed { lockscreenToShadeTransition() }
                sharedElement(Notifications.Elements.NotificationStackPlaceholder, enabled = false)
        sharedElement(Notifications.Elements.HeadsUpNotificationPlaceholder, enabled = false)
                sharedElement(
                    Notifications.Elements.HeadsUpNotificationPlaceholder,
                    enabled = false,
                )
            }
            from(
                Scenes.Shade,
                to = Scenes.Lockscreen,
                key = ToSplitShade,
        cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE,
                cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, // NOTYPO
            ) {
                reversed { lockscreenToSplitShadeTransition() }
            }
    from(Scenes.Communal, to = Scenes.Shade, cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE) {
            from(
                Scenes.Communal,
                to = Scenes.Shade,
                cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, // NOTYPO
            ) {
                communalToShadeTransition()
            }
            from(Scenes.Communal, to = Scenes.Bouncer) { communalToBouncerTransition() }

            // Overlay transitions

    to(Overlays.NotificationsShade, cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE) {
        toNotificationsShadeTransition()
            to(
                Overlays.NotificationsShade,
                cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, // NOTYPO
            ) {
                toNotificationsShadeTransition(revealHaptics = revealHaptics)
            }
    to(Overlays.QuickSettingsShade, cuj = Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE) {
        toQuickSettingsShadeTransition()
            to(
                Overlays.QuickSettingsShade,
                cuj = Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE, // NOTYPO
            ) {
                toQuickSettingsShadeTransition(revealHaptics = revealHaptics)
            }
            from(
                Scenes.Gone,
                to = Overlays.NotificationsShade,
                key = SlightlyFasterShadeCollapse,
        cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE,
                cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, // NOTYPO
            ) {
        toNotificationsShadeTransition(durationScale = 0.9)
                toNotificationsShadeTransition(durationScale = 0.9, revealHaptics = revealHaptics)
            }
            from(
                Scenes.Gone,
                to = Overlays.QuickSettingsShade,
                key = SlightlyFasterShadeCollapse,
        cuj = Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE,
                cuj = Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE, // NOTYPO
            ) {
        toQuickSettingsShadeTransition(durationScale = 0.9)
                toQuickSettingsShadeTransition(durationScale = 0.9, revealHaptics = revealHaptics)
            }
            from(
                Scenes.Lockscreen,
                to = Overlays.NotificationsShade,
                key = SlightlyFasterShadeCollapse,
        cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE,
                cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, // NOTYPO
            ) {
        toNotificationsShadeTransition(durationScale = 0.9)
                toNotificationsShadeTransition(durationScale = 0.9, revealHaptics = revealHaptics)
            }
            from(
                Scenes.Lockscreen,
                to = Overlays.QuickSettingsShade,
                key = SlightlyFasterShadeCollapse,
        cuj = Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE,
                cuj = Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE, // NOTYPO
            ) {
        toQuickSettingsShadeTransition(durationScale = 0.9)
                toQuickSettingsShadeTransition(durationScale = 0.9, revealHaptics = revealHaptics)
            }
        }
    }
}
+41 −0
Original line number Diff line number Diff line
/*
 * 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.scene.ui.composable

import com.android.compose.animation.scene.SceneTransitions
import com.android.compose.animation.scene.reveal.ContainerRevealHaptics
import com.android.compose.animation.scene.transitions

/**
 * Builder of the comprehensive definition of all transitions between scenes and overlays in the
 * scene container.
 */
interface SceneContainerTransitionsBuilder {

    /** Build the [SceneContainer] transitions spec. */
    fun build(revealHaptics: ContainerRevealHaptics): SceneTransitions
}

/**
 * Implementation of [SceneContainerTransitionsBuilder] that returns a constant [SceneTransitions]
 * instance, ignoring any parameters passed to [build].
 */
class ConstantSceneContainerTransitionsBuilder(
    private val transitions: SceneTransitions = transitions { /* No transitions */ }
) : SceneContainerTransitionsBuilder {
    override fun build(revealHaptics: ContainerRevealHaptics): SceneTransitions = transitions
}
+7 −12
Original line number Diff line number Diff line
@@ -17,16 +17,18 @@
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.compose.animation.scene.reveal.ContainerRevealHaptics
import com.android.compose.animation.scene.reveal.verticalContainerReveal
import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys
import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.notifications.ui.composable.NotificationsShade
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.shade.ui.composable.OverlayShade
import kotlin.time.Duration.Companion.milliseconds

fun TransitionBuilder.toNotificationsShadeTransition(durationScale: Double = 1.0) {
fun TransitionBuilder.toNotificationsShadeTransition(
    durationScale: Double = 1.0,
    revealHaptics: ContainerRevealHaptics,
) {
    spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())

    // Ensure the clock isn't clipped by the shade outline during the transition from lockscreen.
@@ -34,14 +36,7 @@ fun TransitionBuilder.toNotificationsShadeTransition(durationScale: Double = 1.0
        ClockElementKeys.smallClockElementKey,
        elevateInContent = Overlays.NotificationsShade,
    )
    scaleSize(OverlayShade.Elements.Panel, height = 0f)
    // Avoid translating the status bar with the shade panel.
    translate(NotificationsShade.Elements.StatusBar)
    // Slide in the shade panel from the top edge.
    translate(OverlayShade.Elements.Panel, Edge.Top)

    fractionRange(end = .5f) { fade(OverlayShade.Elements.Scrim) }
    fractionRange(start = .5f) { fade(Notifications.Elements.NotificationScrim) }
    verticalContainerReveal(OverlayShade.Elements.Panel, revealHaptics)
}

private val DefaultDuration = 300.milliseconds
+7 −4
Original line number Diff line number Diff line
@@ -17,16 +17,19 @@
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.compose.animation.scene.reveal.ContainerRevealHaptics
import com.android.compose.animation.scene.reveal.verticalContainerReveal
import com.android.systemui.shade.ui.composable.OverlayShade
import kotlin.time.Duration.Companion.milliseconds

fun TransitionBuilder.toQuickSettingsShadeTransition(durationScale: Double = 1.0) {
fun TransitionBuilder.toQuickSettingsShadeTransition(
    durationScale: Double = 1.0,
    revealHaptics: ContainerRevealHaptics,
) {
    spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())

    translate(OverlayShade.Elements.Panel, Edge.Top)
    fractionRange(end = .5f) { fade(OverlayShade.Elements.Scrim) }
    verticalContainerReveal(OverlayShade.Elements.Panel, revealHaptics)
}

private val DefaultDuration = 300.milliseconds
Loading