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

Commit bbc93975 authored by Fabián Kozynski's avatar Fabián Kozynski
Browse files

Animation of Edit mode in scene container

When animating, the header will fade in/out while shrinking vertically
(moving QS container up). The footer will do the opposite movement.

Test: atest QSSceneAdapterImplTest
Test: manual
Fixes: 316602167
Flag: ACONFIG com.android.systemui.scene_container DEVELOPMENT

Change-Id: Ie92df3a43c37bf9c7452f99436606e263120de94
parent 2b4aa4df
Loading
Loading
Loading
Loading
+20 −1
Original line number Diff line number Diff line
@@ -17,6 +17,11 @@
package com.android.systemui.qs.footer.ui.compose

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.LocalIndication
@@ -87,10 +92,24 @@ import kotlinx.coroutines.launch
fun SceneScope.FooterActionsWithAnimatedVisibility(
    viewModel: FooterActionsViewModel,
    isCustomizing: Boolean,
    customizingAnimationDuration: Int,
    lifecycleOwner: LifecycleOwner,
    modifier: Modifier = Modifier,
) {
    AnimatedVisibility(visible = !isCustomizing, modifier = modifier.fillMaxWidth()) {
    AnimatedVisibility(
        visible = !isCustomizing,
        enter =
            expandVertically(
                animationSpec = tween(customizingAnimationDuration),
                initialHeight = { 0 },
            ) + fadeIn(tween(customizingAnimationDuration)),
        exit =
            shrinkVertically(
                animationSpec = tween(customizingAnimationDuration),
                targetHeight = { 0 },
            ) + fadeOut(tween(customizingAnimationDuration)),
        modifier = modifier.fillMaxWidth()
    ) {
        QuickSettingsTheme {
            // This view has its own horizontal padding
            // TODO(b/321716470) This should use a lifecycle tied to the scene.
+2 −1
Original line number Diff line number Diff line
@@ -162,7 +162,8 @@ private fun QuickSettingsContent(
    modifier: Modifier = Modifier,
) {
    val qsView by qsSceneAdapter.qsView.collectAsState(null)
    val isCustomizing by qsSceneAdapter.isCustomizing.collectAsState()
    val isCustomizing by
        qsSceneAdapter.isCustomizerShowing.collectAsState(qsSceneAdapter.isCustomizerShowing.value)
    QuickSettingsTheme {
        val context = LocalContext.current

+37 −14
Original line number Diff line number Diff line
@@ -19,12 +19,15 @@ package com.android.systemui.qs.ui.composable
import android.view.ViewGroup
import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.clipScrollableContainer
import androidx.compose.foundation.gestures.Orientation
@@ -174,6 +177,9 @@ private fun SceneScope.QuickSettingsScene(
                }
    ) {
        val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
        val isCustomizerShowing by viewModel.qsSceneAdapter.isCustomizerShowing.collectAsState()
        val customizingAnimationDuration by
            viewModel.qsSceneAdapter.customizerAnimationDuration.collectAsState()
        val screenHeight = LocalRawScreenHeight.current

        BackHandler(
@@ -213,6 +219,18 @@ private fun SceneScope.QuickSettingsScene(
        val navBarBottomHeight =
            WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
        val density = LocalDensity.current
        val bottomPadding by
            animateDpAsState(
                targetValue = if (isCustomizing) 0.dp else navBarBottomHeight,
                animationSpec = tween(customizingAnimationDuration),
                label = "animateQSSceneBottomPaddingAsState"
            )
        val topPadding by
            animateDpAsState(
                targetValue = if (isCustomizing) ShadeHeader.Dimensions.CollapsedHeight else 0.dp,
                animationSpec = tween(customizingAnimationDuration),
                label = "animateQSSceneTopPaddingAsState"
            )

        LaunchedEffect(navBarBottomHeight, density) {
            with(density) {
@@ -232,17 +250,14 @@ private fun SceneScope.QuickSettingsScene(
            horizontalAlignment = Alignment.CenterHorizontally,
            modifier =
                Modifier.fillMaxSize()
                    .then(
                        if (isCustomizing) {
                            Modifier.padding(top = 48.dp)
                        } else {
                            Modifier.padding(bottom = navBarBottomHeight)
                        }
                    .padding(
                        top = topPadding.coerceAtLeast(0.dp),
                        bottom = bottomPadding.coerceAtLeast(0.dp)
                    )
        ) {
            Box(modifier = Modifier.fillMaxSize().weight(1f)) {
                val shadeHeaderAndQuickSettingsModifier =
                    if (isCustomizing) {
                    if (isCustomizerShowing) {
                        Modifier.fillMaxHeight().align(Alignment.TopCenter)
                    } else {
                        Modifier.verticalNestedScrollToScene()
@@ -265,15 +280,22 @@ private fun SceneScope.QuickSettingsScene(
                                visible = !isCustomizing,
                                enter =
                                    expandVertically(
                                        animationSpec = tween(100),
                                        initialHeight = { collapsedHeaderHeight },
                                    ) + fadeIn(tween(100)),
                                        animationSpec = tween(customizingAnimationDuration),
                                        expandFrom = Alignment.Top,
                                    ) +
                                        slideInVertically(
                                            animationSpec = tween(customizingAnimationDuration),
                                        ) +
                                        fadeIn(tween(customizingAnimationDuration)),
                                exit =
                                    shrinkVertically(
                                        animationSpec = tween(100),
                                        targetHeight = { collapsedHeaderHeight },
                                        animationSpec = tween(customizingAnimationDuration),
                                        shrinkTowards = Alignment.Top,
                                    ) + fadeOut(tween(100)),
                                    ) +
                                        slideOutVertically(
                                            animationSpec = tween(customizingAnimationDuration),
                                        ) +
                                        fadeOut(tween(customizingAnimationDuration)),
                            ) {
                                ExpandedShadeHeader(
                                    viewModel = viewModel.shadeHeaderViewModel,
@@ -299,7 +321,7 @@ private fun SceneScope.QuickSettingsScene(
                        viewModel.qsSceneAdapter,
                        { viewModel.qsSceneAdapter.qsHeight },
                        isSplitShade = false,
                        modifier = Modifier.sysuiResTag("expanded_qs_scroll_view"),
                        modifier = Modifier.sysuiResTag("expanded_qs_scroll_view")
                    )

                    MediaCarousel(
@@ -314,6 +336,7 @@ private fun SceneScope.QuickSettingsScene(
            FooterActionsWithAnimatedVisibility(
                viewModel = footerActionsViewModel,
                isCustomizing = isCustomizing,
                customizingAnimationDuration = customizingAnimationDuration,
                lifecycleOwner = lifecycleOwner,
                modifier = Modifier.align(Alignment.CenterHorizontally),
            )
+14 −5
Original line number Diff line number Diff line
@@ -17,7 +17,9 @@
package com.android.systemui.shade.ui.composable

import android.view.ViewGroup
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.clipScrollableContainer
@@ -300,6 +302,9 @@ private fun SceneScope.SplitShade(
    modifier: Modifier = Modifier,
) {
    val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
    val isCustomizerShowing by viewModel.qsSceneAdapter.isCustomizerShowing.collectAsState()
    val customizingAnimationDuration by
        viewModel.qsSceneAdapter.customizerAnimationDuration.collectAsState()
    val lifecycleOwner = LocalLifecycleOwner.current
    val footerActionsViewModel =
        remember(lifecycleOwner, viewModel) { viewModel.getFooterActionsViewModel(lifecycleOwner) }
@@ -319,6 +324,12 @@ private fun SceneScope.SplitShade(
            .collectAsState(0f)

    val navBarBottomHeight = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
    val bottomPadding by
        animateDpAsState(
            targetValue = if (isCustomizing) 0.dp else navBarBottomHeight,
            animationSpec = tween(customizingAnimationDuration),
            label = "animateQSSceneBottomPaddingAsState"
        )
    val density = LocalDensity.current
    LaunchedEffect(navBarBottomHeight, density) {
        with(density) {
@@ -386,16 +397,13 @@ private fun SceneScope.SplitShade(
                    )
                    Column(
                        verticalArrangement = Arrangement.Top,
                        modifier =
                            Modifier.fillMaxSize().thenIf(!isCustomizing) {
                                Modifier.padding(bottom = navBarBottomHeight)
                            },
                        modifier = Modifier.fillMaxSize().padding(bottom = bottomPadding),
                    ) {
                        Column(
                            modifier =
                                Modifier.fillMaxSize()
                                    .weight(1f)
                                    .thenIf(!isCustomizing) {
                                    .thenIf(!isCustomizerShowing) {
                                        Modifier.verticalNestedScrollToScene()
                                            .verticalScroll(
                                                quickSettingsScrollState,
@@ -428,6 +436,7 @@ private fun SceneScope.SplitShade(
                        FooterActionsWithAnimatedVisibility(
                            viewModel = footerActionsViewModel,
                            isCustomizing = isCustomizing,
                            customizingAnimationDuration = customizingAnimationDuration,
                            lifecycleOwner = lifecycleOwner,
                            modifier =
                                Modifier.align(Alignment.CenterHorizontally)
+40 −5
Original line number Diff line number Diff line
@@ -273,21 +273,56 @@ class QSSceneAdapterImplTest : SysuiTestCase() {
        }

    @Test
    fun customizing_QS() =
    fun customizing_QS_noAnimations() =
        testScope.runTest {
            val customizing by collectLastValue(underTest.isCustomizing)
            val customizerState by collectLastValue(underTest.customizerState)

            underTest.inflate(context)
            runCurrent()
            underTest.setState(QSSceneAdapter.State.QS)

            assertThat(customizing).isFalse()
            assertThat(customizerState).isEqualTo(CustomizerState.Hidden)

            underTest.setCustomizerShowing(true)
            assertThat(customizing).isTrue()
            assertThat(customizerState).isEqualTo(CustomizerState.Showing)

            underTest.setCustomizerShowing(false)
            assertThat(customizing).isFalse()
            assertThat(customizerState).isEqualTo(CustomizerState.Hidden)
        }

    // This matches the calls made by QSCustomizer
    @Test
    fun customizing_QS_animations_correctStates() =
        testScope.runTest {
            val customizerState by collectLastValue(underTest.customizerState)
            val animatingInDuration = 100L
            val animatingOutDuration = 50L

            underTest.inflate(context)
            runCurrent()
            underTest.setState(QSSceneAdapter.State.QS)

            assertThat(customizerState).isEqualTo(CustomizerState.Hidden)

            // Start showing customizer with animation
            underTest.setCustomizerAnimating(true)
            underTest.setCustomizerShowing(true, animatingInDuration)
            assertThat(customizerState)
                .isEqualTo(CustomizerState.AnimatingIntoCustomizer(animatingInDuration))

            // Finish animation
            underTest.setCustomizerAnimating(false)
            assertThat(customizerState).isEqualTo(CustomizerState.Showing)

            // Start closing customizer with animation
            underTest.setCustomizerAnimating(true)
            underTest.setCustomizerShowing(false, animatingOutDuration)
            assertThat(customizerState)
                .isEqualTo(CustomizerState.AnimatingOutOfCustomizer(animatingOutDuration))

            // Finish animation
            underTest.setCustomizerAnimating(false)
            assertThat(customizerState).isEqualTo(CustomizerState.Hidden)
        }

    @Test
Loading