Loading packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt +20 −1 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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. Loading packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt +2 −1 Original line number Diff line number Diff line Loading @@ -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 Loading packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt +37 −14 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -178,6 +181,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( Loading Loading @@ -217,6 +223,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) { Loading @@ -236,17 +254,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() Loading @@ -269,15 +284,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, Loading @@ -303,7 +325,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( Loading @@ -318,6 +340,7 @@ private fun SceneScope.QuickSettingsScene( FooterActionsWithAnimatedVisibility( viewModel = footerActionsViewModel, isCustomizing = isCustomizing, customizingAnimationDuration = customizingAnimationDuration, lifecycleOwner = lifecycleOwner, modifier = Modifier.align(Alignment.CenterHorizontally), ) Loading packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt +14 −5 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -301,6 +303,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) } Loading @@ -320,6 +325,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) { Loading Loading @@ -390,16 +401,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, Loading Loading @@ -432,6 +440,7 @@ private fun SceneScope.SplitShade( FooterActionsWithAnimatedVisibility( viewModel = footerActionsViewModel, isCustomizing = isCustomizing, customizingAnimationDuration = customizingAnimationDuration, lifecycleOwner = lifecycleOwner, modifier = Modifier.align(Alignment.CenterHorizontally) Loading packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt +40 −5 Original line number Diff line number Diff line Loading @@ -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 Loading
packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt +20 −1 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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. Loading
packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt +2 −1 Original line number Diff line number Diff line Loading @@ -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 Loading
packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt +37 −14 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -178,6 +181,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( Loading Loading @@ -217,6 +223,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) { Loading @@ -236,17 +254,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() Loading @@ -269,15 +284,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, Loading @@ -303,7 +325,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( Loading @@ -318,6 +340,7 @@ private fun SceneScope.QuickSettingsScene( FooterActionsWithAnimatedVisibility( viewModel = footerActionsViewModel, isCustomizing = isCustomizing, customizingAnimationDuration = customizingAnimationDuration, lifecycleOwner = lifecycleOwner, modifier = Modifier.align(Alignment.CenterHorizontally), ) Loading
packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt +14 −5 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -301,6 +303,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) } Loading @@ -320,6 +325,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) { Loading Loading @@ -390,16 +401,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, Loading Loading @@ -432,6 +440,7 @@ private fun SceneScope.SplitShade( FooterActionsWithAnimatedVisibility( viewModel = footerActionsViewModel, isCustomizing = isCustomizing, customizingAnimationDuration = customizingAnimationDuration, lifecycleOwner = lifecycleOwner, modifier = Modifier.align(Alignment.CenterHorizontally) Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt +40 −5 Original line number Diff line number Diff line Loading @@ -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