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

Commit c7948012 authored by Shawn Lee's avatar Shawn Lee
Browse files

[flexiglass] Clean up Gone -> Shade -> QS transitions

Various cleanups:
- Cleans up NotificationScrim transition into QS scene
- Ensures Shade and QS background scrim are rendered behind other elements during scene transitions
- Disabled SharedNotificationContainerBinder controlling NSSL alpha when flexi-notifs is enabled (fixes flickering)
- Animated corner radius during Gone -> Shade transition
- Device corner radius is provided via CompositionLocal, notifs scrim corner radius source of truth moved out of StackAppearanceRepository
- Fixes Shade -> Gone transition when notif scrim is expanded

Bug: 296118689
Test: manual
Flag: ACONFIG com.android.systemui.scene_container DEVELOPMENT
Change-Id: I8461125ab6e76c15cccdab1d92df41a5dacbd442
parent 33ca55d6
Loading
Loading
Loading
Loading
+5 −3
Original line number Original line Diff line number Diff line
@@ -29,12 +29,13 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleOwner
import com.android.compose.theme.PlatformTheme
import com.android.compose.theme.PlatformTheme
import com.android.compose.ui.platform.DensityAwareComposeView
import com.android.compose.ui.platform.DensityAwareComposeView
import com.android.internal.policy.ScreenDecorationsUtils
import com.android.systemui.bouncer.ui.BouncerDialogFactory
import com.android.systemui.bouncer.ui.BouncerDialogFactory
import com.android.systemui.bouncer.ui.composable.BouncerContent
import com.android.systemui.bouncer.ui.composable.BouncerContent
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout
import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout
import com.android.systemui.common.ui.compose.windowinsets.DisplayCutoutProvider
import com.android.systemui.common.ui.compose.windowinsets.ScreenDecorProvider
import com.android.systemui.communal.ui.compose.CommunalContainer
import com.android.systemui.communal.ui.compose.CommunalContainer
import com.android.systemui.communal.ui.compose.CommunalHub
import com.android.systemui.communal.ui.compose.CommunalHub
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
@@ -126,8 +127,9 @@ object ComposeFacade : BaseComposeFacade {
        return ComposeView(context).apply {
        return ComposeView(context).apply {
            setContent {
            setContent {
                PlatformTheme {
                PlatformTheme {
                    DisplayCutoutProvider(
                    ScreenDecorProvider(
                        displayCutout = displayCutoutFromWindowInsets(scope, context, windowInsets)
                        displayCutout = displayCutoutFromWindowInsets(scope, context, windowInsets),
                        screenCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
                    ) {
                    ) {
                        SceneContainer(
                        SceneContainer(
                            viewModel = viewModel,
                            viewModel = viewModel,
+14 −3
Original line number Original line Diff line number Diff line
@@ -21,17 +21,28 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.getValue
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.StateFlow


/** The bounds and [CutoutLocation] of the current display. */
/** The bounds and [CutoutLocation] of the current display. */
val LocalDisplayCutout = staticCompositionLocalOf { DisplayCutout() }
val LocalDisplayCutout = staticCompositionLocalOf { DisplayCutout() }


/** The corner radius in px of the current display. */
val LocalScreenCornerRadius = staticCompositionLocalOf { 0.dp }

@Composable
@Composable
fun DisplayCutoutProvider(
fun ScreenDecorProvider(
    displayCutout: StateFlow<DisplayCutout>,
    displayCutout: StateFlow<DisplayCutout>,
    screenCornerRadius: Float,
    content: @Composable () -> Unit,
    content: @Composable () -> Unit,
) {
) {
    val cutout by displayCutout.collectAsState()
    val cutout by displayCutout.collectAsState()

    val screenCornerRadiusDp = with(LocalDensity.current) { screenCornerRadius.toDp() }
    CompositionLocalProvider(LocalDisplayCutout provides cutout) { content() }
    CompositionLocalProvider(
        LocalScreenCornerRadius provides screenCornerRadiusDp,
        LocalDisplayCutout provides cutout
    ) {
        content()
    }
}
}
+79 −21
Original line number Original line Diff line number Diff line
@@ -51,11 +51,13 @@ import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.dp
@@ -63,10 +65,15 @@ import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.NestedScrollBehavior
import com.android.compose.animation.scene.NestedScrollBehavior
import com.android.compose.animation.scene.SceneScope
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.height
import com.android.compose.modifiers.height
import com.android.compose.ui.util.lerp
import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
import com.android.systemui.notifications.ui.composable.Notifications.Form
import com.android.systemui.notifications.ui.composable.Notifications.Form
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.scene.ui.composable.Gone
import com.android.systemui.scene.ui.composable.Gone
import com.android.systemui.scene.ui.composable.Shade
import com.android.systemui.scene.ui.composable.Shade
import com.android.systemui.shade.ui.composable.ShadeHeader
import com.android.systemui.shade.ui.composable.ShadeHeader
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationStackAppearanceViewBinder.SCRIM_CORNER_RADIUS
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import kotlin.math.roundToInt
import kotlin.math.roundToInt


@@ -77,6 +84,13 @@ object Notifications {
        val ShelfSpace = ElementKey("ShelfSpace")
        val ShelfSpace = ElementKey("ShelfSpace")
    }
    }


    // 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 TransitionThresholds {
        const val EXPANSION_FOR_MAX_CORNER_RADIUS = 0.1f
        const val EXPANSION_FOR_MAX_SCRIM_ALPHA = 0.3f
    }

    enum class Form {
    enum class Form {
        HunFromTop,
        HunFromTop,
        Stack,
        Stack,
@@ -125,19 +139,19 @@ fun SceneScope.NotificationScrollingStack(
    modifier: Modifier = Modifier,
    modifier: Modifier = Modifier,
) {
) {
    val density = LocalDensity.current
    val density = LocalDensity.current
    val cornerRadius by viewModel.cornerRadiusDp.collectAsState()
    val screenCornerRadius = LocalScreenCornerRadius.current
    val expansionFraction by viewModel.expandFraction.collectAsState(0f)
    val expansionFraction by viewModel.expandFraction.collectAsState(0f)


    val navBarHeight =
    val navBarHeight =
        with(density) { WindowInsets.systemBars.asPaddingValues().calculateBottomPadding().toPx() }
        with(density) { WindowInsets.systemBars.asPaddingValues().calculateBottomPadding().toPx() }
    val statusBarHeight =
    val statusBarHeight = WindowInsets.systemBars.asPaddingValues().calculateTopPadding()
        with(density) { WindowInsets.systemBars.asPaddingValues().calculateTopPadding().toPx() }
    val displayCutoutHeight = WindowInsets.displayCutout.asPaddingValues().calculateTopPadding()
    val displayCutoutHeight =
        with(density) { WindowInsets.displayCutout.asPaddingValues().calculateTopPadding().toPx() }
    val screenHeight =
    val screenHeight =
        with(density) { LocalConfiguration.current.screenHeightDp.dp.toPx() } +
        with(density) {
            navBarHeight +
            (LocalConfiguration.current.screenHeightDp.dp +
            maxOf(statusBarHeight, displayCutoutHeight)
                    maxOf(statusBarHeight, displayCutoutHeight))
                .toPx()
        } + navBarHeight


    val contentHeight = viewModel.intrinsicContentHeight.collectAsState()
    val contentHeight = viewModel.intrinsicContentHeight.collectAsState()


@@ -171,26 +185,53 @@ fun SceneScope.NotificationScrollingStack(
            .collect { shouldCollapse -> if (shouldCollapse) scrimOffset.value = 0f }
            .collect { shouldCollapse -> if (shouldCollapse) scrimOffset.value = 0f }
    }
    }


    Box(modifier = modifier.element(Notifications.Elements.NotificationScrim)) {
    Box(
        Spacer(
        modifier =
        modifier =
                Modifier.fillMaxSize()
            modifier
                .element(Notifications.Elements.NotificationScrim)
                .offset {
                    // if scrim is expanded while transitioning to Gone scene, increase the offset
                    // in step with the transition so that it is 0 when it completes.
                    if (
                        scrimOffset.value < 0 &&
                            layoutState.isTransitioning(from = Shade, to = Gone)
                    ) {
                        IntOffset(x = 0, y = (scrimOffset.value * expansionFraction).roundToInt())
                    } else {
                        IntOffset(x = 0, y = scrimOffset.value.roundToInt())
                    }
                }
                .graphicsLayer {
                .graphicsLayer {
                        shape = RoundedCornerShape(cornerRadius.dp)
                    shape =
                        calculateCornerRadius(
                                screenCornerRadius,
                                { expansionFraction },
                                layoutState.isTransitioningBetween(Gone, Shade)
                            )
                            .let {
                                RoundedCornerShape(
                                    topStart = it,
                                    topEnd = it,
                                )
                            }
                    clip = true
                    clip = true
                }
                }
                    .drawBehind { drawRect(Color.Black, blendMode = BlendMode.DstOut) }
    ) {
        // Creates a cutout in the background scrim in the shape of the notifications scrim.
        // Only visible when notif scrim alpha < 1, during shade expansion.
        Spacer(
            modifier =
                Modifier.fillMaxSize().drawBehind {
                    drawRect(Color.Black, blendMode = BlendMode.DstOut)
                }
        )
        )
        Box(
        Box(
            modifier =
            modifier =
                Modifier.fillMaxSize()
                Modifier.fillMaxSize()
                    .offset { IntOffset(0, scrimOffset.value.roundToInt()) }
                    .graphicsLayer {
                    .graphicsLayer {
                        shape = RoundedCornerShape(cornerRadius.dp)
                        clip = true
                        alpha =
                        alpha =
                            if (layoutState.isTransitioningBetween(Gone, Shade)) {
                            if (layoutState.isTransitioningBetween(Gone, Shade)) {
                                (expansionFraction / 0.3f).coerceAtMost(1f)
                                (expansionFraction / EXPANSION_FOR_MAX_SCRIM_ALPHA).coerceAtMost(1f)
                            } else 1f
                            } else 1f
                    }
                    }
                    .background(MaterialTheme.colorScheme.surface)
                    .background(MaterialTheme.colorScheme.surface)
@@ -278,10 +319,10 @@ private fun SceneScope.NotificationPlaceholder(
                .onSizeChanged { size: IntSize ->
                .onSizeChanged { size: IntSize ->
                    debugLog(viewModel) { "STACK onSizeChanged: size=$size" }
                    debugLog(viewModel) { "STACK onSizeChanged: size=$size" }
                }
                }
                .onPlaced { coordinates: LayoutCoordinates ->
                .onGloballyPositioned { coordinates: LayoutCoordinates ->
                    viewModel.onContentTopChanged(coordinates.positionInWindow().y)
                    viewModel.onContentTopChanged(coordinates.positionInWindow().y)
                    debugLog(viewModel) {
                    debugLog(viewModel) {
                        "STACK onPlaced:" +
                        "STACK onGloballyPositioned:" +
                            " size=${coordinates.size}" +
                            " size=${coordinates.size}" +
                            " position=${coordinates.positionInWindow()}" +
                            " position=${coordinates.positionInWindow()}" +
                            " bounds=${coordinates.boundsInWindow()}"
                            " bounds=${coordinates.boundsInWindow()}"
@@ -310,6 +351,23 @@ private fun SceneScope.NotificationPlaceholder(
    }
    }
}
}


private fun calculateCornerRadius(
    screenCornerRadius: Dp,
    expansionFraction: () -> Float,
    transitioning: Boolean,
): Dp {
    return if (transitioning) {
        lerp(
                start = screenCornerRadius.value,
                stop = SCRIM_CORNER_RADIUS,
                fraction = (expansionFraction() / EXPANSION_FOR_MAX_CORNER_RADIUS).coerceAtMost(1f),
            )
            .dp
    } else {
        SCRIM_CORNER_RADIUS.dp
    }
}

private inline fun debugLog(
private inline fun debugLog(
    viewModel: NotificationsPlaceholderViewModel,
    viewModel: NotificationsPlaceholderViewModel,
    msg: () -> Any,
    msg: () -> Any,
+2 −38
Original line number Original line Diff line number Diff line
@@ -36,7 +36,6 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
@@ -47,11 +46,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.dp
@@ -62,7 +56,6 @@ import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.qs.footer.ui.compose.FooterActions
import com.android.systemui.qs.footer.ui.compose.FooterActions
import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel
import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneKey
@@ -122,8 +115,6 @@ private fun SceneScope.QuickSettingsScene(
    statusBarIconController: StatusBarIconController,
    statusBarIconController: StatusBarIconController,
    modifier: Modifier = Modifier,
    modifier: Modifier = Modifier,
) {
) {
    val cornerRadius by viewModel.notifications.cornerRadiusDp.collectAsState()

    // TODO(b/280887232): implement the real UI.
    // TODO(b/280887232): implement the real UI.
    Box(modifier = modifier.fillMaxSize()) {
    Box(modifier = modifier.fillMaxSize()) {
        val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
        val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
@@ -155,9 +146,9 @@ private fun SceneScope.QuickSettingsScene(
        // a background that extends to the edges.
        // a background that extends to the edges.
        Spacer(
        Spacer(
            modifier =
            modifier =
                Modifier.element(Shade.Elements.ScrimBackground)
                Modifier.element(Shade.Elements.BackgroundScrim)
                    .fillMaxSize()
                    .fillMaxSize()
                    .background(MaterialTheme.colorScheme.scrim, shape = Shade.Shapes.Scrim)
                    .background(MaterialTheme.colorScheme.scrim)
        )
        )
        Column(
        Column(
            horizontalAlignment = Alignment.CenterHorizontally,
            horizontalAlignment = Alignment.CenterHorizontally,
@@ -242,32 +233,5 @@ private fun SceneScope.QuickSettingsScene(
                }
                }
            }
            }
        }
        }
        // Scrim with height 0 aligned to bottom of the screen to facilitate shared element
        // transition from Shade scene.
        Box(
            modifier =
                Modifier.element(Notifications.Elements.NotificationScrim)
                    .fillMaxWidth()
                    .height(0.dp)
                    .graphicsLayer {
                        shape = RoundedCornerShape(cornerRadius.dp)
                        clip = true
                        alpha = 1f
                    }
                    .background(MaterialTheme.colorScheme.surface)
                    .align(Alignment.BottomCenter)
                    .onPlaced { coordinates: LayoutCoordinates ->
                        viewModel.notifications.onContentTopChanged(
                            coordinates.positionInWindow().y
                        )
                        val boundsInWindow = coordinates.boundsInWindow()
                        viewModel.notifications.onBoundsChanged(
                            left = boundsInWindow.left,
                            top = boundsInWindow.top,
                            right = boundsInWindow.right,
                            bottom = boundsInWindow.bottom,
                        )
                    }
        )
    }
    }
}
}
+3 −3
Original line number Original line Diff line number Diff line
@@ -16,18 +16,18 @@


package com.android.systemui.scene.ui.composable
package com.android.systemui.scene.ui.composable


import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.Modifier
import com.android.compose.animation.scene.SceneScope
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.Edge
import com.android.systemui.scene.shared.model.Edge
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.scene.shared.model.UserAction
import com.android.systemui.scene.shared.model.UserAction
import com.android.systemui.shade.ui.composable.Shade
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import javax.inject.Inject
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -63,6 +63,6 @@ constructor(
    override fun SceneScope.Content(
    override fun SceneScope.Content(
        modifier: Modifier,
        modifier: Modifier,
    ) {
    ) {
        Box(modifier = Modifier.fillMaxSize().element(Notifications.Elements.NotificationScrim))
        Spacer(modifier.fillMaxSize())
    }
    }
}
}
Loading