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

Commit 850027f9 authored by Shawn Lee's avatar Shawn Lee Committed by Android (Google) Code Review
Browse files

Merge "[flexiglass] Implement QS/Lockscreen scrim transitions" into main

parents 05bd34fc 53c2e150
Loading
Loading
Loading
Loading
+23 −1
Original line number Diff line number Diff line
@@ -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()
    }
+15 −15
Original line number Diff line number Diff line
@@ -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)
+26 −1
Original line number Diff line number Diff line
@@ -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()) },
        )
    }
}
+8 −6
Original line number Diff line number Diff line
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
+2 −19
Original line number Diff line number Diff line
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
Loading