diff options
53 files changed, 879 insertions, 651 deletions
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt index cce06007385b..d4bad23a1ee9 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt @@ -36,7 +36,7 @@ import com.android.compose.animation.scene.ContentKey import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.ElementMatcher -import com.android.compose.animation.scene.LowestZIndexScenePicker +import com.android.compose.animation.scene.LowestZIndexContentPicker import com.android.compose.animation.scene.MutableSceneTransitionLayoutState import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.SceneScope @@ -62,7 +62,7 @@ import kotlin.time.DurationUnit object Communal { object Elements { - val Scrim = ElementKey("Scrim", scenePicker = LowestZIndexScenePicker) + val Scrim = ElementKey("Scrim", contentPicker = LowestZIndexContentPicker) val Grid = ElementKey("CommunalContent") val LockIcon = ElementKey("CommunalLockIcon") val IndicationArea = ElementKey("CommunalIndicationArea") diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt index 6feaf6d8ceec..9c72d933da32 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt @@ -71,7 +71,7 @@ constructor( applyPadding: Boolean, modifier: Modifier = Modifier, ) { - MovableElement( + Element( key = if (isStart) StartButtonElementKey else EndButtonElementKey, modifier = modifier, ) { @@ -98,7 +98,7 @@ constructor( fun SceneScope.IndicationArea( modifier: Modifier = Modifier, ) { - MovableElement( + Element( key = IndicationAreaElementKey, modifier = modifier.indicationAreaPadding(), ) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt index 218779da20b9..bcdb259f161f 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt @@ -120,7 +120,7 @@ constructor( ) } - MovableElement(key = largeClockElementKey, modifier = modifier) { + Element(key = largeClockElementKey, modifier = modifier) { content { AndroidView( factory = { context -> diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt index 44bda956b9f6..33ed14b2e7cc 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt @@ -65,68 +65,74 @@ constructor( ) { val resources = LocalContext.current.resources - MovableElement(key = ClockElementKeys.smartspaceElementKey, modifier = modifier) { - Column( - modifier = - modifier - .onTopPlacementChanged(onTopChanged) - .padding( - top = { lockscreenContentViewModel.getSmartSpacePaddingTop(resources) }, - bottom = { - resources.getDimensionPixelSize( - R.dimen.keyguard_status_view_bottom_margin - ) - } - ) - ) { - if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) { - return@Column - } + Element(key = ClockElementKeys.smartspaceElementKey, modifier = modifier) { + content { + Column( + modifier = + modifier + .onTopPlacementChanged(onTopChanged) + .padding( + top = { + lockscreenContentViewModel.getSmartSpacePaddingTop(resources) + }, + bottom = { + resources.getDimensionPixelSize( + R.dimen.keyguard_status_view_bottom_margin + ) + } + ) + ) { + if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) { + return@Column + } - val paddingBelowClockStart = dimensionResource(R.dimen.below_clock_padding_start) - val paddingBelowClockEnd = dimensionResource(R.dimen.below_clock_padding_end) + val paddingBelowClockStart = + dimensionResource(R.dimen.below_clock_padding_start) + val paddingBelowClockEnd = dimensionResource(R.dimen.below_clock_padding_end) - if (keyguardSmartspaceViewModel.isDateWeatherDecoupled) { - Row( - verticalAlignment = Alignment.CenterVertically, + if (keyguardSmartspaceViewModel.isDateWeatherDecoupled) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = + Modifier.fillMaxWidth() + // All items will be constrained to be as tall as the shortest + // item. + .height(IntrinsicSize.Min) + .padding( + start = paddingBelowClockStart, + ), + ) { + Date( + modifier = + Modifier.burnInAware( + viewModel = aodBurnInViewModel, + params = burnInParams, + ), + ) + Spacer(modifier = Modifier.width(4.dp)) + Weather( + modifier = + Modifier.burnInAware( + viewModel = aodBurnInViewModel, + params = burnInParams, + ), + ) + } + } + + Card( modifier = Modifier.fillMaxWidth() - // All items will be constrained to be as tall as the shortest item. - .height(IntrinsicSize.Min) .padding( start = paddingBelowClockStart, - ), - ) { - Date( - modifier = - Modifier.burnInAware( - viewModel = aodBurnInViewModel, - params = burnInParams, - ), - ) - Spacer(modifier = Modifier.width(4.dp)) - Weather( - modifier = - Modifier.burnInAware( + end = paddingBelowClockEnd, + ) + .burnInAware( viewModel = aodBurnInViewModel, params = burnInParams, ), - ) - } + ) } - - Card( - modifier = - Modifier.fillMaxWidth() - .padding( - start = paddingBelowClockStart, - end = paddingBelowClockEnd, - ) - .burnInAware( - viewModel = aodBurnInViewModel, - params = burnInParams, - ), - ) } } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/WeatherClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/WeatherClockSection.kt index 9a82da251b7f..2e39524baaad 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/WeatherClockSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/WeatherClockSection.kt @@ -128,7 +128,7 @@ constructor( elementKey: ElementKey, modifier: Modifier = Modifier, ) { - MovableElement(key = elementKey, modifier) { + Element(key = elementKey, modifier) { content { AndroidView( factory = { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt index f8bd633d99a6..26ab10b459d8 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt @@ -28,7 +28,7 @@ import androidx.compose.ui.layout.layout import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.viewinterop.AndroidView -import com.android.compose.animation.scene.ElementKey +import com.android.compose.animation.scene.MovableElementKey import com.android.compose.animation.scene.SceneScope import com.android.systemui.media.controls.ui.controller.MediaCarouselController import com.android.systemui.media.controls.ui.view.MediaHost @@ -38,7 +38,10 @@ import com.android.systemui.util.animation.MeasurementInput object MediaCarousel { object Elements { internal val Content = - ElementKey(debugName = "MediaCarouselContent", scenePicker = MediaScenePicker) + MovableElementKey( + debugName = "MediaCarouselContent", + contentPicker = MediaContentPicker, + ) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaScenePicker.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaContentPicker.kt index 7b497e84db62..3f04f3728bef 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaScenePicker.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaContentPicker.kt @@ -16,18 +16,19 @@ package com.android.systemui.media.controls.ui.composable +import com.android.compose.animation.scene.ContentKey +import com.android.compose.animation.scene.ElementContentPicker import com.android.compose.animation.scene.ElementKey -import com.android.compose.animation.scene.ElementScenePicker -import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.SceneTransitionLayoutState -import com.android.compose.animation.scene.TransitionState +import com.android.compose.animation.scene.StaticElementContentPicker +import com.android.compose.animation.scene.content.state.ContentState import com.android.systemui.scene.shared.model.Scenes -/** [ElementScenePicker] implementation for the media carousel object. */ -object MediaScenePicker : ElementScenePicker { +/** [ElementContentPicker] implementation for the media carousel object. */ +object MediaContentPicker : StaticElementContentPicker { const val SHADE_FRACTION = 0.66f - private val scenes = + override val contents = setOf( Scenes.Lockscreen, Scenes.Shade, @@ -36,12 +37,12 @@ object MediaScenePicker : ElementScenePicker { Scenes.Communal ) - override fun sceneDuringTransition( + override fun contentDuringTransition( element: ElementKey, - transition: TransitionState.Transition, - fromSceneZIndex: Float, - toSceneZIndex: Float - ): SceneKey? { + transition: ContentState.Transition<*>, + fromContentZIndex: Float, + toContentZIndex: Float + ): ContentKey { return when { shouldElevateMedia(transition) -> { Scenes.Shade @@ -52,22 +53,23 @@ object MediaScenePicker : ElementScenePicker { transition.isTransitioningBetween(Scenes.QuickSettings, Scenes.Shade) -> { Scenes.QuickSettings } + transition.toContent in contents -> transition.toContent else -> { - when { - scenes.contains(transition.toScene) -> transition.toScene - scenes.contains(transition.fromScene) -> transition.fromScene - else -> null + check(transition.fromContent in contents) { + "Media player should not be composed for the transition from " + + "${transition.fromContent} to ${transition.toContent}" } + transition.fromContent } } } /** Returns true when the media should be laid on top of the rest for the given [transition]. */ - fun shouldElevateMedia(transition: TransitionState.Transition): Boolean { + fun shouldElevateMedia(transition: ContentState.Transition<*>): Boolean { return transition.isTransitioningBetween(Scenes.Lockscreen, Scenes.Shade) } } -fun MediaScenePicker.shouldElevateMedia(layoutState: SceneTransitionLayoutState): Boolean { +fun MediaContentPicker.shouldElevateMedia(layoutState: SceneTransitionLayoutState): Boolean { return layoutState.currentTransition?.let { shouldElevateMedia(it) } ?: false } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt index 2eb7b3f89af5..84782fdfc0af 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt @@ -78,7 +78,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.util.lerp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.ElementKey -import com.android.compose.animation.scene.LowestZIndexScenePicker +import com.android.compose.animation.scene.LowestZIndexContentPicker import com.android.compose.animation.scene.NestedScrollBehavior import com.android.compose.animation.scene.SceneScope import com.android.compose.modifiers.thenIf @@ -105,7 +105,7 @@ object Notifications { val NotificationScrim = ElementKey("NotificationScrim") val NotificationStackPlaceholder = ElementKey("NotificationStackPlaceholder") val HeadsUpNotificationPlaceholder = - ElementKey("HeadsUpNotificationPlaceholder", scenePicker = LowestZIndexScenePicker) + ElementKey("HeadsUpNotificationPlaceholder", contentPicker = LowestZIndexContentPicker) val ShelfSpace = ElementKey("ShelfSpace") val NotificationStackCutoffGuideline = ElementKey("NotificationStackCutoffGuideline") } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt index 8058dcde8cf8..f3994360ea18 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt @@ -33,10 +33,11 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.ElementKey -import com.android.compose.animation.scene.MovableElementScenePicker +import com.android.compose.animation.scene.MovableElementContentPicker +import com.android.compose.animation.scene.MovableElementKey import com.android.compose.animation.scene.SceneScope -import com.android.compose.animation.scene.TransitionState import com.android.compose.animation.scene.ValueKey +import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.modifiers.thenIf import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.qs.ui.adapter.QSSceneAdapter @@ -55,7 +56,10 @@ object QuickSettings { object Elements { val Content = - ElementKey("QuickSettingsContent", scenePicker = MovableElementScenePicker(SCENES)) + MovableElementKey( + "QuickSettingsContent", + contentPicker = MovableElementContentPicker(SCENES) + ) val QuickQuickSettings = ElementKey("QuickQuickSettings") val SplitShadeQuickSettings = ElementKey("SplitShadeQuickSettings") val FooterActions = ElementKey("QuickSettingsFooterActions") diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt index 2800eee661c6..cdcd840b2f34 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt @@ -68,11 +68,11 @@ import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.SceneScope -import com.android.compose.animation.scene.TransitionState import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.animateSceneDpAsState import com.android.compose.animation.scene.animateSceneFloatAsState +import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.modifiers.thenIf import com.android.compose.windowsizeclass.LocalWindowSizeClass import com.android.systemui.battery.BatteryMeterViewController diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt index 2f8c24826376..a9da733116fc 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt @@ -25,7 +25,7 @@ import com.android.compose.animation.scene.TransitionBuilder import com.android.compose.animation.scene.UserActionDistance import com.android.compose.animation.scene.UserActionDistanceScope import com.android.systemui.media.controls.ui.composable.MediaCarousel -import com.android.systemui.media.controls.ui.composable.MediaScenePicker +import com.android.systemui.media.controls.ui.composable.MediaContentPicker import com.android.systemui.notifications.ui.composable.Notifications import com.android.systemui.qs.ui.composable.QuickSettings import com.android.systemui.shade.ui.composable.Shade @@ -54,7 +54,8 @@ fun TransitionBuilder.goneToSplitShadeTransition( fractionRange(end = .33f) { fade(Shade.Elements.BackgroundScrim) } fractionRange(start = .33f) { - val qsTranslation = ShadeHeader.Dimensions.CollapsedHeight * MediaScenePicker.SHADE_FRACTION + val qsTranslation = + ShadeHeader.Dimensions.CollapsedHeight * MediaContentPicker.SHADE_FRACTION val qsExpansionDiff = ShadeHeader.Dimensions.ExpandedHeight - ShadeHeader.Dimensions.CollapsedHeight translate(MediaCarousel.Elements.Content, y = -(qsExpansionDiff + qsTranslation)) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt index 7d46c7570dac..21dfc49cbe7b 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt @@ -26,7 +26,7 @@ import com.android.compose.animation.scene.TransitionBuilder import com.android.compose.animation.scene.UserActionDistance import com.android.compose.animation.scene.UserActionDistanceScope import com.android.systemui.media.controls.ui.composable.MediaCarousel -import com.android.systemui.media.controls.ui.composable.MediaScenePicker +import com.android.systemui.media.controls.ui.composable.MediaContentPicker import com.android.systemui.notifications.ui.composable.Notifications import com.android.systemui.qs.ui.composable.QuickSettings import com.android.systemui.scene.shared.model.Scenes @@ -62,7 +62,7 @@ fun TransitionBuilder.toShadeTransition( fade(QuickSettings.Elements.FooterActions) } - val qsTranslation = ShadeHeader.Dimensions.CollapsedHeight * MediaScenePicker.SHADE_FRACTION + val qsTranslation = ShadeHeader.Dimensions.CollapsedHeight * MediaContentPicker.SHADE_FRACTION val qsExpansionDiff = ShadeHeader.Dimensions.ExpandedHeight - ShadeHeader.Dimensions.CollapsedHeight diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt index 865622326fcd..facbcaffcb5a 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt @@ -49,7 +49,7 @@ import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.ElementKey -import com.android.compose.animation.scene.LowestZIndexScenePicker +import com.android.compose.animation.scene.LowestZIndexContentPicker import com.android.compose.animation.scene.SceneScope import com.android.compose.windowsizeclass.LocalWindowSizeClass import com.android.systemui.keyguard.ui.composable.LockscreenContent @@ -186,10 +186,10 @@ private fun combinePaddings(vararg paddingValues: PaddingValues): PaddingValues object OverlayShade { object Elements { - val Scrim = ElementKey("OverlayShadeScrim", scenePicker = LowestZIndexScenePicker) - val Panel = ElementKey("OverlayShadePanel", scenePicker = LowestZIndexScenePicker) + val Scrim = ElementKey("OverlayShadeScrim", contentPicker = LowestZIndexContentPicker) + val Panel = ElementKey("OverlayShadePanel", contentPicker = LowestZIndexContentPicker) val PanelBackground = - ElementKey("OverlayShadePanelBackground", scenePicker = LowestZIndexScenePicker) + ElementKey("OverlayShadePanelBackground", contentPicker = LowestZIndexContentPicker) } object Colors { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt index b5a10ca1e478..1cd48bf2e628 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt @@ -59,11 +59,11 @@ import androidx.compose.ui.unit.max import androidx.compose.ui.viewinterop.AndroidView import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.ElementKey -import com.android.compose.animation.scene.LowestZIndexScenePicker +import com.android.compose.animation.scene.LowestZIndexContentPicker import com.android.compose.animation.scene.SceneScope -import com.android.compose.animation.scene.TransitionState import com.android.compose.animation.scene.ValueKey import com.android.compose.animation.scene.animateElementFloatAsState +import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.modifiers.thenIf import com.android.compose.windowsizeclass.LocalWindowSizeClass import com.android.settingslib.Utils @@ -93,8 +93,8 @@ object ShadeHeader { val ExpandedContent = ElementKey("ShadeHeaderExpandedContent") val CollapsedContentStart = ElementKey("ShadeHeaderCollapsedContentStart") val CollapsedContentEnd = ElementKey("ShadeHeaderCollapsedContentEnd") - val PrivacyChip = ElementKey("PrivacyChip", scenePicker = LowestZIndexScenePicker) - val Clock = ElementKey("ShadeHeaderClock", scenePicker = LowestZIndexScenePicker) + val PrivacyChip = ElementKey("PrivacyChip", contentPicker = LowestZIndexContentPicker) + val Clock = ElementKey("ShadeHeaderClock", contentPicker = LowestZIndexContentPicker) val ShadeCarrierGroup = ElementKey("ShadeCarrierGroup") } @@ -110,6 +110,7 @@ object ShadeHeader { object Colors { val ColorScheme.shadeHeaderText: Color get() = Color.White + val ColorScheme.onScrimDim: Color get() = Color.DarkGray } @@ -148,8 +149,8 @@ fun SceneScope.CollapsedShadeHeader( } val isLargeScreenLayout = - LocalWindowSizeClass.current.widthSizeClass == WindowWidthSizeClass.Medium || - LocalWindowSizeClass.current.widthSizeClass == WindowWidthSizeClass.Expanded + LocalWindowSizeClass.current.widthSizeClass == WindowWidthSizeClass.Medium || + LocalWindowSizeClass.current.widthSizeClass == WindowWidthSizeClass.Expanded val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsStateWithLifecycle() @@ -197,8 +198,8 @@ fun SceneScope.CollapsedShadeHeader( ) { if (isLargeScreenLayout) { ShadeCarrierGroup( - viewModel = viewModel, - modifier = Modifier.align(Alignment.CenterVertically), + viewModel = viewModel, + modifier = Modifier.align(Alignment.CenterVertically), ) } SystemIconContainer( @@ -552,19 +553,20 @@ private fun SystemIconContainer( val interactionSource = remember { MutableInteractionSource() } val isHovered by interactionSource.collectIsHoveredAsState() - val hoverModifier = Modifier - .clip(RoundedCornerShape(CollapsedHeight / 4)) + val hoverModifier = + Modifier.clip(RoundedCornerShape(CollapsedHeight / 4)) .background(MaterialTheme.colorScheme.onScrimDim) Row( - modifier = modifier + modifier = + modifier .height(CollapsedHeight) .padding(vertical = CollapsedHeight / 4) .thenIf(isClickable) { Modifier.clickable( - interactionSource = interactionSource, - indication = null, - onClick = { viewModel.onSystemIconContainerClicked() }, + interactionSource = interactionSource, + indication = null, + onClick = { viewModel.onSystemIconContainerClicked() }, ) } .thenIf(isHovered) { hoverModifier }, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt index 18ca0f75245e..77b48d3d307e 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt @@ -63,14 +63,14 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.ElementKey -import com.android.compose.animation.scene.LowestZIndexScenePicker +import com.android.compose.animation.scene.LowestZIndexContentPicker import com.android.compose.animation.scene.NestedScrollBehavior import com.android.compose.animation.scene.SceneScope -import com.android.compose.animation.scene.TransitionState import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.animateSceneDpAsState import com.android.compose.animation.scene.animateSceneFloatAsState +import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.modifiers.padding import com.android.compose.modifiers.thenIf import com.android.compose.windowsizeclass.LocalWindowSizeClass @@ -81,7 +81,7 @@ import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadi import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.dagger.SysUISingleton import com.android.systemui.media.controls.ui.composable.MediaCarousel -import com.android.systemui.media.controls.ui.composable.MediaScenePicker +import com.android.systemui.media.controls.ui.composable.MediaContentPicker import com.android.systemui.media.controls.ui.composable.shouldElevateMedia import com.android.systemui.media.controls.ui.controller.MediaCarouselController import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager @@ -118,7 +118,7 @@ object Shade { object Elements { val MediaCarousel = ElementKey("ShadeMediaCarousel") val BackgroundScrim = - ElementKey("ShadeBackgroundScrim", scenePicker = LowestZIndexScenePicker) + ElementKey("ShadeBackgroundScrim", contentPicker = LowestZIndexContentPicker) val SplitShadeStartColumn = ElementKey("SplitShadeStartColumn") } @@ -376,7 +376,7 @@ private fun SceneScope.SingleShade( layout(constraints.maxWidth, constraints.maxHeight) { val qsZIndex = - if (MediaScenePicker.shouldElevateMedia(layoutState)) { + if (MediaContentPicker.shouldElevateMedia(layoutState)) { 1f } else { 0f @@ -563,7 +563,7 @@ private fun SceneScope.SplitShade( mediaHost = mediaHost, modifier = Modifier.fillMaxWidth().thenIf( - MediaScenePicker.shouldElevateMedia(layoutState) + MediaContentPicker.shouldElevateMedia(layoutState) ) { Modifier.zIndex(1f) }, diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt index afbc8e71c940..e13ca39149f3 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt @@ -32,6 +32,7 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastCoerceIn import androidx.compose.ui.util.fastLastOrNull +import com.android.compose.animation.scene.content.state.TransitionState import kotlin.math.roundToInt /** diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt index 82c85d1fa38b..1fc1f989b095 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt @@ -19,6 +19,7 @@ package com.android.compose.animation.scene import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.AnimationVector1D import androidx.compose.animation.core.SpringSpec +import com.android.compose.animation.scene.content.state.TransitionState import kotlin.math.absoluteValue import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt index 0f7e3eaf75ad..a43028a340f4 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt @@ -30,8 +30,10 @@ import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.round import androidx.compose.ui.util.fastCoerceIn -import com.android.compose.animation.scene.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified import com.android.compose.animation.scene.content.Scene +import com.android.compose.animation.scene.content.state.ContentState +import com.android.compose.animation.scene.content.state.ContentState.HasOverscrollProperties.Companion.DistanceUnspecified +import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.nestedscroll.PriorityNestedScrollConnection import kotlin.math.absoluteValue import kotlinx.coroutines.CoroutineScope @@ -608,7 +610,7 @@ private class SwipeTransition( var lastDistance: Float = DistanceUnspecified, ) : TransitionState.Transition(_fromScene.key, _toScene.key, replacedTransition), - TransitionState.HasOverscrollProperties { + ContentState.HasOverscrollProperties { var _currentScene by mutableStateOf(_fromScene) override val currentScene: SceneKey get() = _currentScene.key @@ -645,7 +647,7 @@ private class SwipeTransition( override val isInitiatedByUserInput = true - override var bouncingScene: SceneKey? = null + override var bouncingContent: SceneKey? = null /** The current offset caused by the drag gesture. */ var dragOffset by mutableFloatStateOf(0f) @@ -764,7 +766,7 @@ private class SwipeTransition( animationSpec = swipeSpec, initialVelocity = initialVelocity, ) { - if (bouncingScene == null) { + if (bouncingContent == null) { val isBouncing = if (isTargetGreater) { if (startedWhenOvercrollingTargetScene) { @@ -781,7 +783,7 @@ private class SwipeTransition( } if (isBouncing) { - bouncingScene = targetScene + bouncingContent = targetScene // Immediately stop this transition if we are bouncing on a // scene that does not bounce. diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt index 0b5e58faf1db..ec7c77bdb3b7 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt @@ -49,13 +49,15 @@ import androidx.compose.ui.util.fastCoerceIn import androidx.compose.ui.util.fastLastOrNull import androidx.compose.ui.util.lerp import com.android.compose.animation.scene.content.Content +import com.android.compose.animation.scene.content.state.ContentState +import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.animation.scene.transformation.PropertyTransformation import com.android.compose.animation.scene.transformation.SharedElementTransformation import com.android.compose.ui.util.lerp import kotlin.math.roundToInt import kotlinx.coroutines.launch -/** An element on screen, that can be composed in one or more scenes. */ +/** An element on screen, that can be composed in one or more contents. */ @Stable internal class Element(val key: ElementKey) { /** The mapping between a content and the state this element has in that content, if any. */ @@ -67,7 +69,7 @@ internal class Element(val key: ElementKey) { * The last transition that was used when computing the state (size, position and alpha) of this * element in any content, or `null` if it was last laid out when idle. */ - var lastTransition: TransitionState.Transition? = null + var lastTransition: ContentState.Transition<*>? = null /** Whether this element was ever drawn in a content. */ var wasDrawnInAnyContent = false @@ -86,7 +88,7 @@ internal class Element(val key: ElementKey) { var targetSize by mutableStateOf(SizeUnspecified) var targetOffset by mutableStateOf(Offset.Unspecified) - /** The last state this element had in this scene. */ + /** The last state this element had in this content. */ var lastOffset = Offset.Unspecified var lastSize = SizeUnspecified var lastScale = Scale.Unspecified @@ -103,7 +105,7 @@ internal class Element(val key: ElementKey) { /** * The delta values to add to this element state to have smoother interruptions. These * should be multiplied by the - * [current interruption progress][TransitionState.Transition.interruptionProgress] so that + * [current interruption progress][ContentState.Transition.interruptionProgress] so that * they nicely animate from their values down to 0. */ var offsetInterruptionDelta = Offset.Zero @@ -310,7 +312,7 @@ internal class ElementNode( } private fun Placeable.PlacementScope.place( - transition: TransitionState.Transition?, + transition: ContentState.Transition<*>?, placeable: Placeable, ) { with(layoutImpl.lookaheadScope) { @@ -475,7 +477,7 @@ private fun elementTransition( layoutImpl: SceneTransitionLayoutImpl, element: Element, transitions: List<TransitionState.Transition>, -): TransitionState.Transition? { +): ContentState.Transition<*>? { val transition = transitions.fastLastOrNull { transition -> transition.fromScene in element.stateByContent || @@ -502,8 +504,8 @@ private fun elementTransition( private fun prepareInterruption( layoutImpl: SceneTransitionLayoutImpl, element: Element, - transition: TransitionState.Transition, - previousTransition: TransitionState.Transition, + transition: ContentState.Transition<*>, + previousTransition: ContentState.Transition<*>, ) { if (transition.replacedTransition == previousTransition) { return @@ -514,10 +516,10 @@ private fun prepareInterruption( return stateByContent[key]?.also { it.selfUpdateValuesBeforeInterruption() } } - val previousFromState = updateStateInContent(previousTransition.fromScene) - val previousToState = updateStateInContent(previousTransition.toScene) - val fromState = updateStateInContent(transition.fromScene) - val toState = updateStateInContent(transition.toScene) + val previousFromState = updateStateInContent(previousTransition.fromContent) + val previousToState = updateStateInContent(previousTransition.toContent) + val fromState = updateStateInContent(transition.fromContent) + val toState = updateStateInContent(transition.toContent) reconcileStates(element, previousTransition) reconcileStates(element, transition) @@ -545,31 +547,31 @@ private fun prepareInterruption( } /** - * Reconcile the state of [element] in the fromScene and toScene of [transition] so that the values - * before interruption have their expected values, taking shared transitions into account. + * Reconcile the state of [element] in the formContent and toContent of [transition] so that the + * values before interruption have their expected values, taking shared transitions into account. */ private fun reconcileStates( element: Element, - transition: TransitionState.Transition, + transition: ContentState.Transition<*>, ) { - val fromSceneState = element.stateByContent[transition.fromScene] ?: return - val toSceneState = element.stateByContent[transition.toScene] ?: return + val fromContentState = element.stateByContent[transition.fromContent] ?: return + val toContentState = element.stateByContent[transition.toContent] ?: return if (!isSharedElementEnabled(element.key, transition)) { return } if ( - fromSceneState.offsetBeforeInterruption != Offset.Unspecified && - toSceneState.offsetBeforeInterruption == Offset.Unspecified + fromContentState.offsetBeforeInterruption != Offset.Unspecified && + toContentState.offsetBeforeInterruption == Offset.Unspecified ) { - // Element is shared and placed in fromScene only. - toSceneState.updateValuesBeforeInterruption(fromSceneState) + // Element is shared and placed in fromContent only. + toContentState.updateValuesBeforeInterruption(fromContentState) } else if ( - toSceneState.offsetBeforeInterruption != Offset.Unspecified && - fromSceneState.offsetBeforeInterruption == Offset.Unspecified + toContentState.offsetBeforeInterruption != Offset.Unspecified && + fromContentState.offsetBeforeInterruption == Offset.Unspecified ) { - // Element is shared and placed in toScene only. - fromSceneState.updateValuesBeforeInterruption(toSceneState) + // Element is shared and placed in toContent only. + fromContentState.updateValuesBeforeInterruption(toContentState) } } @@ -614,12 +616,12 @@ private fun Element.State.clearValuesBeforeInterruption() { /** * Compute what [value] should be if we take the - * [interruption progress][TransitionState.Transition.interruptionProgress] of [transition] into + * [interruption progress][ContentState.Transition.interruptionProgress] of [transition] into * account. */ private inline fun <T> computeInterruptedValue( layoutImpl: SceneTransitionLayoutImpl, - transition: TransitionState.Transition?, + transition: ContentState.Transition<*>?, value: T, unspecifiedValue: T, zeroValue: T, @@ -660,13 +662,13 @@ private inline fun <T> computeInterruptedValue( /** * Set the interruption delta of a *placement/drawing*-related value (offset, alpha, scale). This - * ensures that the delta is also set on the other scene in the transition for shared elements, so - * that there is no jump cut if the scene where the element is placed has changed. + * ensures that the delta is also set on the other content in the transition for shared elements, so + * that there is no jump cut if the content where the element is placed has changed. */ private inline fun <T> setPlacementInterruptionDelta( element: Element, stateInContent: Element.State, - transition: TransitionState.Transition?, + transition: ContentState.Transition<*>?, delta: T, setter: (Element.State, T) -> Unit, ) { @@ -677,14 +679,14 @@ private inline fun <T> setPlacementInterruptionDelta( return } - // If the element is shared, also set the delta on the other scene so that it is used by that - // scene if we start overscrolling it and change the scene where the element is placed. - val otherScene = - if (stateInContent.content == transition.fromScene) transition.toScene - else transition.fromScene - val otherSceneState = element.stateByContent[otherScene] ?: return + // If the element is shared, also set the delta on the other content so that it is used by that + // content if we start overscrolling it and change the content where the element is placed. + val otherContent = + if (stateInContent.content == transition.fromContent) transition.toContent + else transition.fromContent + val otherContentState = element.stateByContent[otherContent] ?: return if (isSharedElementEnabled(element.key, transition)) { - setter(otherSceneState, delta) + setter(otherContentState, delta) } } @@ -692,7 +694,7 @@ private fun shouldPlaceElement( layoutImpl: SceneTransitionLayoutImpl, content: ContentKey, element: Element, - transition: TransitionState.Transition?, + transition: ContentState.Transition<*>?, ): Boolean { // Always place the element if we are idle. if (transition == null) { @@ -701,14 +703,14 @@ private fun shouldPlaceElement( // Don't place the element in this content if this content is not part of the current element // transition. - if (content != transition.fromScene && content != transition.toScene) { + if (content != transition.fromContent && content != transition.toContent) { return false } // Place the element if it is not shared. if ( - transition.fromScene !in element.stateByContent || - transition.toScene !in element.stateByContent + transition.fromContent !in element.stateByContent || + transition.toContent !in element.stateByContent ) { return true } @@ -730,7 +732,7 @@ internal fun shouldPlaceOrComposeSharedElement( layoutImpl: SceneTransitionLayoutImpl, content: ContentKey, element: ElementKey, - transition: TransitionState.Transition, + transition: ContentState.Transition<*>, ): Boolean { // If we are overscrolling, only place/compose the element in the overscrolling scene. val overscrollScene = transition.currentOverscrollSpec?.scene @@ -738,45 +740,47 @@ internal fun shouldPlaceOrComposeSharedElement( return content == overscrollScene } - val scenePicker = element.scenePicker - val fromScene = transition.fromScene - val toScene = transition.toScene - + val scenePicker = element.contentPicker val pickedScene = - scenePicker.sceneDuringTransition( - element = element, - transition = transition, - fromSceneZIndex = layoutImpl.scenes.getValue(fromScene).zIndex, - toSceneZIndex = layoutImpl.scenes.getValue(toScene).zIndex, - ) ?: return false + when (transition) { + is TransitionState.Transition -> { + scenePicker.contentDuringTransition( + element = element, + transition = transition, + fromContentZIndex = layoutImpl.scene(transition.fromScene).zIndex, + toContentZIndex = layoutImpl.scene(transition.toScene).zIndex, + ) + } + } return pickedScene == content } private fun isSharedElementEnabled( element: ElementKey, - transition: TransitionState.Transition, + transition: ContentState.Transition<*>, ): Boolean { return sharedElementTransformation(element, transition)?.enabled ?: true } internal fun sharedElementTransformation( element: ElementKey, - transition: TransitionState.Transition, + transition: ContentState.Transition<*>, ): SharedElementTransformation? { val transformationSpec = transition.transformationSpec - val sharedInFromScene = transformationSpec.transformations(element, transition.fromScene).shared - val sharedInToScene = transformationSpec.transformations(element, transition.toScene).shared + val sharedInFromContent = + transformationSpec.transformations(element, transition.fromContent).shared + val sharedInToContent = transformationSpec.transformations(element, transition.toContent).shared - // The sharedElement() transformation must either be null or be the same in both scenes. - if (sharedInFromScene != sharedInToScene) { + // The sharedElement() transformation must either be null or be the same in both contents. + if (sharedInFromContent != sharedInToContent) { error( - "Different sharedElement() transformations matched $element (from=$sharedInFromScene " + - "to=$sharedInToScene)" + "Different sharedElement() transformations matched $element " + + "(from=$sharedInFromContent to=$sharedInToContent)" ) } - return sharedInFromScene + return sharedInFromContent } /** @@ -789,16 +793,14 @@ internal fun sharedElementTransformation( private fun isElementOpaque( content: Content, element: Element, - transition: TransitionState.Transition?, + transition: ContentState.Transition<*>?, ): Boolean { if (transition == null) { return true } - val fromScene = transition.fromScene - val toScene = transition.toScene - val fromState = element.stateByContent[fromScene] - val toState = element.stateByContent[toScene] + val fromState = element.stateByContent[transition.fromContent] + val toState = element.stateByContent[transition.toContent] if (fromState == null && toState == null) { // TODO(b/311600838): Throw an exception instead once layers of disposed elements are not @@ -825,7 +827,7 @@ private fun isElementOpaque( private fun elementAlpha( layoutImpl: SceneTransitionLayoutImpl, element: Element, - transition: TransitionState.Transition?, + transition: ContentState.Transition<*>?, stateInContent: Element.State, ): Float { val alpha = @@ -856,7 +858,7 @@ private fun elementAlpha( private fun interruptedAlpha( layoutImpl: SceneTransitionLayoutImpl, element: Element, - transition: TransitionState.Transition?, + transition: ContentState.Transition<*>?, stateInContent: Element.State, alpha: Float, ): Float { @@ -886,7 +888,7 @@ private fun interruptedAlpha( private fun measure( layoutImpl: SceneTransitionLayoutImpl, element: Element, - transition: TransitionState.Transition?, + transition: ContentState.Transition<*>?, stateInContent: Element.State, measurable: Measurable, constraints: Constraints, @@ -950,7 +952,7 @@ private fun Placeable.size(): IntSize = IntSize(width, height) private fun ContentDrawScope.getDrawScale( layoutImpl: SceneTransitionLayoutImpl, element: Element, - transition: TransitionState.Transition?, + transition: ContentState.Transition<*>?, stateInContent: Element.State, ): Scale { val scale = @@ -1031,7 +1033,7 @@ private fun ContentDrawScope.getDrawScale( * @param layoutImpl the [SceneTransitionLayoutImpl] associated to [element]. * @param currentContentState the content state of the content for which we are computing the value. * Note that during interruptions, this could be the state of a content that is neither - * [transition.toScene] nor [transition.fromScene]. + * [transition.toContent] nor [transition.fromContent]. * @param element the element being animated. * @param contentValue the value being animated. * @param transformation the transformation associated to the value being animated. @@ -1046,7 +1048,7 @@ private inline fun <T> computeValue( layoutImpl: SceneTransitionLayoutImpl, currentContentState: Element.State, element: Element, - transition: TransitionState.Transition?, + transition: ContentState.Transition<*>?, contentValue: (Element.State) -> T, transformation: (ElementTransformations) -> PropertyTransformation<T>?, currentValue: () -> T, @@ -1061,11 +1063,11 @@ private inline fun <T> computeValue( return currentValue() } - val fromScene = transition.fromScene - val toScene = transition.toScene + val fromContent = transition.fromContent + val toContent = transition.toContent - val fromState = element.stateByContent[fromScene] - val toState = element.stateByContent[toScene] + val fromState = element.stateByContent[fromContent] + val toState = element.stateByContent[toContent] if (fromState == null && toState == null) { // TODO(b/311600838): Throw an exception instead once layers of disposed elements are not @@ -1073,19 +1075,20 @@ private inline fun <T> computeValue( return contentValue(currentContentState) } - val currentScene = currentContentState.content - if (transition is TransitionState.HasOverscrollProperties) { + val currentContent = currentContentState.content + if (transition is ContentState.HasOverscrollProperties) { val overscroll = transition.currentOverscrollSpec - if (overscroll?.scene == currentScene) { + if (overscroll?.scene == currentContent) { val elementSpec = - overscroll.transformationSpec.transformations(element.key, currentScene) + overscroll.transformationSpec.transformations(element.key, currentContent) val propertySpec = transformation(elementSpec) ?: return currentValue() - val overscrollState = checkNotNull(if (currentScene == toScene) toState else fromState) + val overscrollState = + checkNotNull(if (currentContent == toContent) toState else fromState) val idleValue = contentValue(overscrollState) val targetValue = propertySpec.transform( layoutImpl, - currentScene, + currentContent, element, overscrollState, transition, @@ -1101,8 +1104,8 @@ private inline fun <T> computeValue( // TODO(b/290184746): Make sure that we don't overflow transformations associated to a // range. val directionSign = if (transition.isUpOrLeft) -1 else 1 - val isToScene = overscroll.scene == transition.toScene - val linearProgress = transition.progress.let { if (isToScene) it - 1f else it } + val isToContent = overscroll.scene == transition.toContent + val linearProgress = transition.progress.let { if (isToContent) it - 1f else it } val progress = directionSign * overscroll.progressConverter(linearProgress) val rangeProgress = propertySpec.range?.progress(progress) ?: progress @@ -1111,7 +1114,8 @@ private inline fun <T> computeValue( } } - // The element is shared: interpolate between the value in fromScene and the value in toScene. + // The element is shared: interpolate between the value in fromContent and the value in + // toContent. // TODO(b/290184746): Support non linear shared paths as well as a way to make sure that shared // elements follow the finger direction. val isSharedElement = fromState != null && toState != null @@ -1134,15 +1138,15 @@ private inline fun <T> computeValue( val contentState = checkNotNull( when { - isSharedElement && currentScene == fromScene -> fromState + isSharedElement && currentContent == fromContent -> fromState isSharedElement -> toState else -> fromState ?: toState } ) - // The scene for which we compute the transformation. Note that this is not necessarily - // [currentScene] because [currentScene] could be a different scene than the transition - // fromScene or toScene during interruptions. + // The content for which we compute the transformation. Note that this is not necessarily + // [currentContent] because [currentContent] could be a different content than the transition + // fromContent or toContent during interruptions. val content = contentState.content val transformation = @@ -1156,7 +1160,7 @@ private inline fun <T> computeValue( val isInPreviewStage = transition.isInPreviewStage val idleValue = contentValue(contentState) - val isEntering = content == toScene + val isEntering = content == toContent val previewTargetValue = previewTransformation.transform( layoutImpl, @@ -1262,7 +1266,7 @@ private inline fun <T> computeValue( val rangeProgress = transformation.range?.progress(progress) ?: progress // Interpolate between the value at rest and the value before entering/after leaving. - val isEntering = content == toScene + val isEntering = content == toContent return if (isEntering) { lerp(targetValue, idleValue, rangeProgress) } else { diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/InterruptionHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/InterruptionHandler.kt index 54c64fd721ec..bf70ca9b6744 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/InterruptionHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/InterruptionHandler.kt @@ -16,6 +16,8 @@ package com.android.compose.animation.scene +import com.android.compose.animation.scene.content.state.TransitionState + /** * A handler to specify how a transition should be interrupted. * diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt index a9edf0afc66f..3cd5553bc963 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt @@ -64,15 +64,15 @@ class SceneKey( } /** Key for an element. */ -class ElementKey( +open class ElementKey( debugName: String, identity: Any = Object(), /** - * The [ElementScenePicker] to use when deciding in which scene we should draw shared Elements + * The [ElementContentPicker] to use when deciding in which scene we should draw shared Elements * or compose MovableElements. */ - val scenePicker: ElementScenePicker = DefaultElementScenePicker, + open val contentPicker: ElementContentPicker = DefaultElementContentPicker, ) : Key(debugName, identity), ElementMatcher { @VisibleForTesting // TODO(b/240432457): Make internal once PlatformComposeSceneTransitionLayoutTestsUtils can @@ -99,6 +99,32 @@ class ElementKey( } } +/** Key for a movable element. */ +class MovableElementKey( + debugName: String, + + /** + * The [StaticElementContentPicker] to use when deciding in which scene we should draw shared + * Elements or compose MovableElements. + * + * @see MovableElementContentPicker + */ + override val contentPicker: StaticElementContentPicker, + identity: Any = Object(), +) : ElementKey(debugName, identity, contentPicker) { + constructor( + debugName: String, + + /** The exhaustive list of contents (scenes or overlays) that can contain this element. */ + contents: Set<ContentKey>, + identity: Any = Object(), + ) : this(debugName, MovableElementContentPicker(contents), identity) + + override fun toString(): String { + return "MovableElementKey(debugName=$debugName)" + } +} + /** Key for a shared value of an element. */ class ValueKey(debugName: String, identity: Any = Object()) : Key(debugName, identity) { override fun toString(): String { diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt index e556f6f4ff05..abecdd771f7a 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt @@ -28,6 +28,7 @@ import androidx.compose.ui.layout.Layout import androidx.compose.ui.unit.IntSize import androidx.compose.ui.util.fastLastOrNull import com.android.compose.animation.scene.content.Content +import com.android.compose.animation.scene.content.state.TransitionState @Composable internal fun Element( @@ -53,7 +54,7 @@ internal fun Element( internal fun MovableElement( layoutImpl: SceneTransitionLayoutImpl, sceneOrOverlay: Content, - key: ElementKey, + key: MovableElementKey, modifier: Modifier, content: @Composable ElementScope<MovableElementContentScope>.() -> Unit, ) { @@ -111,7 +112,7 @@ private class ElementScopeImpl( private class MovableElementScopeImpl( private val layoutImpl: SceneTransitionLayoutImpl, - private val element: ElementKey, + private val element: MovableElementKey, private val content: Content, private val baseContentScope: BaseContentScope, private val boxScope: BoxScope, @@ -169,7 +170,7 @@ private class MovableElementScopeImpl( private fun shouldComposeMovableElement( layoutImpl: SceneTransitionLayoutImpl, content: ContentKey, - element: ElementKey, + element: MovableElementKey, ): Boolean { val transitions = layoutImpl.state.currentTransitions if (transitions.isEmpty()) { @@ -181,14 +182,10 @@ private fun shouldComposeMovableElement( // The current transition for this element is the last transition in which either fromScene or // toScene contains the element. + val contents = element.contentPicker.contents val transition = transitions.fastLastOrNull { transition -> - element.scenePicker.sceneDuringTransition( - element = element, - transition = transition, - fromSceneZIndex = layoutImpl.scenes.getValue(transition.fromScene).zIndex, - toSceneZIndex = layoutImpl.scenes.getValue(transition.toScene).zIndex, - ) != null + transition.fromScene in contents || transition.toScene in contents } ?: return false // Always compose movable elements in the scene picked by their scene picker. diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt index f1b2249fc29e..ae5344fd7922 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt @@ -17,6 +17,7 @@ package com.android.compose.animation.scene import androidx.compose.runtime.snapshotFlow +import com.android.compose.animation.scene.content.state.TransitionState import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOf diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt index fd6762b97f79..fe16ef75118b 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt @@ -25,6 +25,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import com.android.compose.animation.scene.content.state.TransitionState import kotlin.coroutines.cancellation.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt index 3401af827d4c..65a73676d398 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt @@ -189,7 +189,7 @@ interface BaseContentScope : ElementStateScope { */ @Composable fun MovableElement( - key: ElementKey, + key: MovableElementKey, modifier: Modifier, // TODO(b/317026105): As discussed in http://shortn/_gJVdltF8Si, remove the @Composable diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt index 01ce2111b8f2..a6c6a80941fc 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt @@ -18,10 +18,6 @@ package com.android.compose.animation.scene import android.util.Log import androidx.annotation.VisibleForTesting -import androidx.compose.animation.core.Animatable -import androidx.compose.animation.core.AnimationVector1D -import androidx.compose.animation.core.spring -import androidx.compose.foundation.gestures.Orientation import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -29,12 +25,12 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.util.fastAll import androidx.compose.ui.util.fastFilter import androidx.compose.ui.util.fastForEach +import com.android.compose.animation.scene.content.state.ContentState +import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.animation.scene.transition.link.LinkedTransition import com.android.compose.animation.scene.transition.link.StateLink import kotlin.math.absoluteValue import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch /** * The state of a [SceneTransitionLayout]. @@ -146,221 +142,6 @@ fun MutableSceneTransitionLayoutState( ) } -@Stable -sealed interface TransitionState { - /** - * The current effective scene. If a new transition was triggered, it would start from this - * scene. - * - * For instance, when swiping from scene A to scene B, the [currentScene] is A when the swipe - * gesture starts, but then if the user flings their finger and commits the transition to scene - * B, then [currentScene] becomes scene B even if the transition is not finished yet and is - * still animating to settle to scene B. - */ - val currentScene: SceneKey - - /** No transition/animation is currently running. */ - data class Idle(override val currentScene: SceneKey) : TransitionState - - /** There is a transition animating between two scenes. */ - abstract class Transition( - /** The scene this transition is starting from. Can't be the same as toScene */ - val fromScene: SceneKey, - - /** The scene this transition is going to. Can't be the same as fromScene */ - val toScene: SceneKey, - - /** The transition that `this` transition is replacing, if any. */ - internal val replacedTransition: Transition? = null, - ) : TransitionState { - /** - * The key of this transition. This should usually be null, but it can be specified to use a - * specific set of transformations associated to this transition. - */ - open val key: TransitionKey? = null - - /** - * The progress of the transition. This is usually in the `[0; 1]` range, but it can also be - * less than `0` or greater than `1` when using transitions with a spring AnimationSpec or - * when flinging quickly during a swipe gesture. - */ - abstract val progress: Float - - /** The current velocity of [progress], in progress units. */ - abstract val progressVelocity: Float - - /** - * The progress of the preview transition. This is usually in the `[0; 1]` range, but it can - * also be less than `0` or greater than `1` when using transitions with a spring - * AnimationSpec or when flinging quickly during a swipe gesture. - */ - open val previewProgress: Float = 0f - - /** The current velocity of [previewProgress], in progress units. */ - open val previewProgressVelocity: Float = 0f - - /** Whether the transition is currently in the preview stage */ - open val isInPreviewStage: Boolean = false - - /** Whether the transition was triggered by user input rather than being programmatic. */ - abstract val isInitiatedByUserInput: Boolean - - /** Whether user input is currently driving the transition. */ - abstract val isUserInputOngoing: Boolean - - /** - * The current [TransformationSpecImpl] and [OverscrollSpecImpl] associated to this - * transition. - * - * Important: These will be set exactly once, when this transition is - * [started][MutableSceneTransitionLayoutStateImpl.startTransition]. - */ - internal var transformationSpec: TransformationSpecImpl = TransformationSpec.Empty - internal var previewTransformationSpec: TransformationSpecImpl? = null - private var fromOverscrollSpec: OverscrollSpecImpl? = null - private var toOverscrollSpec: OverscrollSpecImpl? = null - - /** The current [OverscrollSpecImpl], if this transition is currently overscrolling. */ - internal val currentOverscrollSpec: OverscrollSpecImpl? - get() { - if (this !is HasOverscrollProperties) return null - val progress = progress - val bouncingScene = bouncingScene - return when { - progress < 0f || bouncingScene == fromScene -> fromOverscrollSpec - progress > 1f || bouncingScene == toScene -> toOverscrollSpec - else -> null - } - } - - /** Returns if the [progress] value of this transition can go beyond range `[0; 1]` */ - internal fun isWithinProgressRange(progress: Float): Boolean { - // If the properties are missing we assume that every [Transition] can overscroll - if (this !is HasOverscrollProperties) return true - // [OverscrollSpec] for the current scene, even if it hasn't started overscrolling yet. - val specForCurrentScene = - when { - progress <= 0f -> fromOverscrollSpec - progress >= 1f -> toOverscrollSpec - else -> null - } ?: return true - - return specForCurrentScene.transformationSpec.transformations.isNotEmpty() - } - - /** - * An animatable that animates from 1f to 0f. This will be used to nicely animate the sudden - * jump of values when this transitions interrupts another one. - */ - private var interruptionDecay: Animatable<Float, AnimationVector1D>? = null - - init { - check(fromScene != toScene) - check( - replacedTransition == null || - (replacedTransition.fromScene == fromScene && - replacedTransition.toScene == toScene) - ) - } - - /** - * Force this transition to finish and animate to [currentScene], so that this transition - * progress will settle to either 0% (if [currentScene] == [fromScene]) or 100% (if - * [currentScene] == [toScene]) in a finite amount of time. - * - * @return the [Job] that animates the progress to [currentScene]. It can be used to wait - * until the animation is complete or cancel it to snap to [currentScene]. Calling - * [finish] multiple times will return the same [Job]. - */ - abstract fun finish(): Job - - /** - * Whether we are transitioning. If [from] or [to] is empty, we will also check that they - * match the scenes we are animating from and/or to. - */ - fun isTransitioning(from: SceneKey? = null, to: SceneKey? = null): Boolean { - return (from == null || fromScene == from) && (to == null || toScene == to) - } - - /** Whether we are transitioning from [scene] to [other], or from [other] to [scene]. */ - fun isTransitioningBetween(scene: SceneKey, other: SceneKey): Boolean { - return isTransitioning(from = scene, to = other) || - isTransitioning(from = other, to = scene) - } - - internal fun updateOverscrollSpecs( - fromSpec: OverscrollSpecImpl?, - toSpec: OverscrollSpecImpl?, - ) { - fromOverscrollSpec = fromSpec - toOverscrollSpec = toSpec - } - - internal open fun interruptionProgress( - layoutImpl: SceneTransitionLayoutImpl, - ): Float { - if (!layoutImpl.state.enableInterruptions) { - return 0f - } - - if (replacedTransition != null) { - return replacedTransition.interruptionProgress(layoutImpl) - } - - fun create(): Animatable<Float, AnimationVector1D> { - val animatable = Animatable(1f, visibilityThreshold = ProgressVisibilityThreshold) - layoutImpl.coroutineScope.launch { - val swipeSpec = layoutImpl.state.transitions.defaultSwipeSpec - val progressSpec = - spring( - stiffness = swipeSpec.stiffness, - dampingRatio = swipeSpec.dampingRatio, - visibilityThreshold = ProgressVisibilityThreshold, - ) - animatable.animateTo(0f, progressSpec) - } - - return animatable - } - - val animatable = interruptionDecay ?: create().also { interruptionDecay = it } - return animatable.value - } - } - - interface HasOverscrollProperties { - /** - * The position of the [Transition.toScene]. - * - * Used to understand the direction of the overscroll. - */ - val isUpOrLeft: Boolean - - /** - * The relative orientation between [Transition.fromScene] and [Transition.toScene]. - * - * Used to understand the orientation of the overscroll. - */ - val orientation: Orientation - - /** - * Scope which can be used in the Overscroll DSL to define a transformation based on the - * distance between [Transition.fromScene] and [Transition.toScene]. - */ - val overscrollScope: OverscrollScope - - /** - * The scene around which the transition is currently bouncing. When not `null`, this - * transition is currently oscillating around this scene and will soon settle to that scene. - */ - val bouncingScene: SceneKey? - - companion object { - const val DistanceUnspecified = 0f - } - } -} - /** A [MutableSceneTransitionLayoutState] that holds the value for the current scene. */ internal class MutableSceneTransitionLayoutStateImpl( initialScene: SceneKey, @@ -459,7 +240,7 @@ internal class MutableSceneTransitionLayoutStateImpl( // Compute the [TransformationSpec] when the transition starts. val fromScene = transition.fromScene val toScene = transition.toScene - val orientation = (transition as? TransitionState.HasOverscrollProperties)?.orientation + val orientation = (transition as? ContentState.HasOverscrollProperties)?.orientation // Update the transition specs. transition.transformationSpec = diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt index cfa4c70c8239..3ded1de0f3e1 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt @@ -157,16 +157,16 @@ interface TransitionSpec { val key: TransitionKey? /** - * The scene we are transitioning from. If `null`, this spec can be used to animate from any - * scene. + * The content we are transitioning from. If `null`, this spec can be used to animate from any + * content. */ - val from: SceneKey? + val from: ContentKey? /** - * The scene we are transitioning to. If `null`, this spec can be used to animate from any - * scene. + * The content we are transitioning to. If `null`, this spec can be used to animate from any + * content. */ - val to: SceneKey? + val to: ContentKey? /** * Return a reversed version of this [TransitionSpec] for a transition going from [to] to @@ -231,8 +231,8 @@ interface TransformationSpec { internal class TransitionSpecImpl( override val key: TransitionKey?, - override val from: SceneKey?, - override val to: SceneKey?, + override val from: ContentKey?, + override val to: ContentKey?, private val previewTransformationSpec: (() -> TransformationSpecImpl)? = null, private val reversePreviewTransformationSpec: (() -> TransformationSpecImpl)? = null, private val transformationSpec: () -> TransformationSpecImpl diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt index ecef0f49f206..38b83835ad29 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt @@ -23,7 +23,7 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import com.android.compose.animation.scene.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified +import com.android.compose.animation.scene.content.state.ContentState /** Define the [transitions][SceneTransitions] to be used with a [SceneTransitionLayout]. */ fun transitions(builder: SceneTransitionsBuilder.() -> Unit): SceneTransitions { @@ -47,9 +47,9 @@ interface SceneTransitionsBuilder { var interruptionHandler: InterruptionHandler /** - * Define the default animation to be played when transitioning [to] the specified scene, from - * any scene. For the animation specification to apply only when transitioning between two - * specific scenes, use [from] instead. + * Define the default animation to be played when transitioning [to] the specified content, from + * any content. For the animation specification to apply only when transitioning between two + * specific contents, use [from] instead. * * If [key] is not `null`, then this transition will only be used if the same key is specified * when triggering the transition. @@ -61,7 +61,7 @@ interface SceneTransitionsBuilder { * @see from */ fun to( - to: SceneKey, + to: ContentKey, key: TransitionKey? = null, preview: (TransitionBuilder.() -> Unit)? = null, reversePreview: (TransitionBuilder.() -> Unit)? = null, @@ -69,12 +69,12 @@ interface SceneTransitionsBuilder { ): TransitionSpec /** - * Define the animation to be played when transitioning [from] the specified scene. For the - * animation specification to apply only when transitioning between two specific scenes, pass - * the destination scene via the [to] argument. + * Define the animation to be played when transitioning [from] the specified content. For the + * animation specification to apply only when transitioning between two specific contents, pass + * the destination content via the [to] argument. * - * When looking up which transition should be used when animating from scene A to scene B, we - * pick the single transition with the given [key] and matching one of these predicates (in + * When looking up which transition should be used when animating from content A to content B, + * we pick the single transition with the given [key] and matching one of these predicates (in * order of importance): * 1. from == A && to == B * 2. to == A && from == B, which is then treated in reverse. @@ -86,8 +86,8 @@ interface SceneTransitionsBuilder { * reversible with the reverse animation having a preview as well, define a [reversePreview]. */ fun from( - from: SceneKey, - to: SceneKey? = null, + from: ContentKey, + to: ContentKey? = null, key: TransitionKey? = null, preview: (TransitionBuilder.() -> Unit)? = null, reversePreview: (TransitionBuilder.() -> Unit)? = null, @@ -233,99 +233,171 @@ interface OverscrollScope : Density { /** * An interface to decide where we should draw shared Elements or compose MovableElements. * - * @see DefaultElementScenePicker - * @see HighestZIndexScenePicker - * @see LowestZIndexScenePicker - * @see MovableElementScenePicker + * @see DefaultElementContentPicker + * @see HighestZIndexContentPicker + * @see LowestZIndexContentPicker + * @see MovableElementContentPicker */ -interface ElementScenePicker { +interface ElementContentPicker { /** - * Return the scene in which [element] should be drawn (when using `Modifier.element(key)`) or + * Return the content in which [element] should be drawn (when using `Modifier.element(key)`) or * composed (when using `MovableElement(key)`) during the given [transition]. If this element - * should not be drawn or composed in neither [transition.fromScene] nor [transition.toScene], - * return `null`. + * should not be drawn or composed in neither [transition.fromContent] nor + * [transition.toContent], return `null`. * - * Important: For [MovableElements][ContentScope.MovableElement], this scene picker will + * Important: For [MovableElements][ContentScope.MovableElement], this content picker will * *always* be used during transitions to decide whether we should compose that element in a - * given scene or not. Therefore, you should make sure that the returned [SceneKey] contains the - * movable element, otherwise that element will not be composed in any scene during the + * given content or not. Therefore, you should make sure that the returned [ContentKey] contains + * the movable element, otherwise that element will not be composed in any scene during the * transition. */ - fun sceneDuringTransition( + fun contentDuringTransition( element: ElementKey, - transition: TransitionState.Transition, - fromSceneZIndex: Float, - toSceneZIndex: Float, - ): SceneKey? + transition: ContentState.Transition<*>, + fromContentZIndex: Float, + toContentZIndex: Float, + ): ContentKey /** - * Return [transition.fromScene] if it is in [scenes] and [transition.toScene] is not, or return - * [transition.toScene] if it is in [scenes] and [transition.fromScene] is not. If neither - * [transition.fromScene] and [transition.toScene] are in [scenes], return `null`. If both - * [transition.fromScene] and [transition.toScene] are in [scenes], throw an exception. + * Return [transition.fromContent] if it is in [contents] and [transition.toContent] is not, or + * return [transition.toContent] if it is in [contents] and [transition.fromContent] is not. If + * neither [transition.toContent] and [transition.fromContent] are in [contents] or if both + * [transition.fromContent] and [transition.toContent] are in [contents], throw an exception. * - * This function can be useful when computing the scene in which a movable element should be + * This function can be useful when computing the content in which a movable element should be * composed. */ - fun pickSingleSceneIn( - scenes: Set<SceneKey>, - transition: TransitionState.Transition, + fun pickSingleContentIn( + contents: Set<ContentKey>, + transition: ContentState.Transition<*>, element: ElementKey, - ): SceneKey? { - val fromScene = transition.fromScene - val toScene = transition.toScene - val fromSceneInScenes = scenes.contains(fromScene) - val toSceneInScenes = scenes.contains(toScene) + ): ContentKey { + val fromContent = transition.fromContent + val toContent = transition.toContent + val fromContentInContents = contents.contains(fromContent) + val toContentInContents = contents.contains(toContent) + + if (fromContentInContents && toContentInContents) { + error( + "Element $element can be in both $fromContent and $toContent. You should add a " + + "special case for this transition before calling pickSingleSceneIn()." + ) + } - return when { - fromSceneInScenes && toSceneInScenes -> { - error( - "Element $element can be in both $fromScene and $toScene. You should add a " + - "special case for this transition before calling pickSingleSceneIn()." - ) - } - fromSceneInScenes -> fromScene - toSceneInScenes -> toScene - else -> null + if (!fromContentInContents && !toContentInContents) { + error( + "Element $element can be neither in $fromContent and $toContent. This either " + + "means that you should add one of them in the scenes set passed to " + + "pickSingleSceneIn(), or there is an internal error and this element was " + + "composed when it shouldn't be." + ) + } + + return if (fromContentInContents) { + fromContent + } else { + toContent } } } -/** An [ElementScenePicker] that draws/composes elements in the scene with the highest z-order. */ -object HighestZIndexScenePicker : ElementScenePicker { - override fun sceneDuringTransition( +/** + * An element picker on which we can query the set of contents (scenes or overlays) that contain the + * element. This is needed by [MovableElement], that needs to know at composition time on which of + * the candidate contents an element should be composed. + */ +interface StaticElementContentPicker : ElementContentPicker { + /** The exhaustive lists of contents that contain this element. */ + val contents: Set<ContentKey> +} + +/** + * An [ElementContentPicker] that draws/composes elements in the content with the highest z-order. + */ +object HighestZIndexContentPicker : ElementContentPicker { + override fun contentDuringTransition( element: ElementKey, - transition: TransitionState.Transition, - fromSceneZIndex: Float, - toSceneZIndex: Float - ): SceneKey { - return if (fromSceneZIndex > toSceneZIndex) { - transition.fromScene + transition: ContentState.Transition<*>, + fromContentZIndex: Float, + toContentZIndex: Float + ): ContentKey { + return if (fromContentZIndex > toContentZIndex) { + transition.fromContent } else { - transition.toScene + transition.toContent + } + } + + /** + * Return a [StaticElementContentPicker] that behaves like [HighestZIndexContentPicker] and can + * be used by [MovableElement]. + */ + operator fun invoke(contents: Set<ContentKey>): StaticElementContentPicker { + return object : StaticElementContentPicker { + override val contents: Set<ContentKey> = contents + + override fun contentDuringTransition( + element: ElementKey, + transition: ContentState.Transition<*>, + fromContentZIndex: Float, + toContentZIndex: Float + ): ContentKey { + return HighestZIndexContentPicker.contentDuringTransition( + element, + transition, + fromContentZIndex, + toContentZIndex, + ) + } } } } -/** An [ElementScenePicker] that draws/composes elements in the scene with the lowest z-order. */ -object LowestZIndexScenePicker : ElementScenePicker { - override fun sceneDuringTransition( +/** + * An [ElementContentPicker] that draws/composes elements in the content with the lowest z-order. + */ +object LowestZIndexContentPicker : ElementContentPicker { + override fun contentDuringTransition( element: ElementKey, - transition: TransitionState.Transition, - fromSceneZIndex: Float, - toSceneZIndex: Float - ): SceneKey { - return if (fromSceneZIndex < toSceneZIndex) { - transition.fromScene + transition: ContentState.Transition<*>, + fromContentZIndex: Float, + toContentZIndex: Float + ): ContentKey { + return if (fromContentZIndex < toContentZIndex) { + transition.fromContent } else { - transition.toScene + transition.toContent + } + } + + /** + * Return a [StaticElementContentPicker] that behaves like [LowestZIndexContentPicker] and can + * be used by [MovableElement]. + */ + operator fun invoke(contents: Set<ContentKey>): StaticElementContentPicker { + return object : StaticElementContentPicker { + override val contents: Set<ContentKey> = contents + + override fun contentDuringTransition( + element: ElementKey, + transition: ContentState.Transition<*>, + fromContentZIndex: Float, + toContentZIndex: Float + ): ContentKey { + return LowestZIndexContentPicker.contentDuringTransition( + element, + transition, + fromContentZIndex, + toContentZIndex, + ) + } } } } /** - * An [ElementScenePicker] that draws/composes elements in the scene we are transitioning to, iff - * that scene is in [scenes]. + * An [ElementContentPicker] that draws/composes elements in the content we are transitioning to, + * iff that content is in [contents]. * * This picker can be useful for movable elements whose content size depends on its content (because * it wraps it) in at least one scene. That way, the target size of the MovableElement will be @@ -337,23 +409,30 @@ object LowestZIndexScenePicker : ElementScenePicker { * is not the same as when going from scene B to scene A, so it's not usable in situations where * z-ordering during the transition matters. */ -class MovableElementScenePicker(private val scenes: Set<SceneKey>) : ElementScenePicker { - override fun sceneDuringTransition( +class MovableElementContentPicker( + override val contents: Set<ContentKey>, +) : StaticElementContentPicker { + override fun contentDuringTransition( element: ElementKey, - transition: TransitionState.Transition, - fromSceneZIndex: Float, - toSceneZIndex: Float, - ): SceneKey? { + transition: ContentState.Transition<*>, + fromContentZIndex: Float, + toContentZIndex: Float, + ): ContentKey { return when { - scenes.contains(transition.toScene) -> transition.toScene - scenes.contains(transition.fromScene) -> transition.fromScene - else -> null + transition.toContent in contents -> transition.toContent + else -> { + check(transition.fromContent in contents) { + "Neither ${transition.fromContent} nor ${transition.toContent} are in " + + "contents. This transition should not have been used for this element." + } + transition.fromContent + } } } } -/** The default [ElementScenePicker]. */ -val DefaultElementScenePicker = HighestZIndexScenePicker +/** The default [ElementContentPicker]. */ +val DefaultElementContentPicker = HighestZIndexContentPicker @TransitionDsl interface PropertyTransformationBuilder { diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt index a0759c73f76e..6515cb8f68ca 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt @@ -60,7 +60,7 @@ private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder { val transitionOverscrollSpecs = mutableListOf<OverscrollSpecImpl>() override fun to( - to: SceneKey, + to: ContentKey, key: TransitionKey?, preview: (TransitionBuilder.() -> Unit)?, reversePreview: (TransitionBuilder.() -> Unit)?, @@ -70,8 +70,8 @@ private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder { } override fun from( - from: SceneKey, - to: SceneKey?, + from: ContentKey, + to: ContentKey?, key: TransitionKey?, preview: (TransitionBuilder.() -> Unit)?, reversePreview: (TransitionBuilder.() -> Unit)?, @@ -120,8 +120,8 @@ private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder { } private fun transition( - from: SceneKey?, - to: SceneKey?, + from: ContentKey?, + to: ContentKey?, key: TransitionKey?, preview: (TransitionBuilder.() -> Unit)?, reversePreview: (TransitionBuilder.() -> Unit)?, diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt index 492d2115cfdc..6f608cbc1d7f 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt @@ -39,6 +39,7 @@ import com.android.compose.animation.scene.ElementScope import com.android.compose.animation.scene.ElementStateScope import com.android.compose.animation.scene.MovableElement import com.android.compose.animation.scene.MovableElementContentScope +import com.android.compose.animation.scene.MovableElementKey import com.android.compose.animation.scene.NestedScrollBehavior import com.android.compose.animation.scene.SceneTransitionLayoutImpl import com.android.compose.animation.scene.SceneTransitionLayoutState @@ -130,7 +131,7 @@ internal class ContentScopeImpl( @Composable override fun MovableElement( - key: ElementKey, + key: MovableElementKey, modifier: Modifier, content: @Composable (ElementScope<MovableElementContentScope>.() -> Unit) ) { diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/ContentState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/ContentState.kt new file mode 100644 index 000000000000..add393441794 --- /dev/null +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/ContentState.kt @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2024 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.compose.animation.scene.content.state + +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.AnimationVector1D +import androidx.compose.animation.core.spring +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.runtime.Stable +import com.android.compose.animation.scene.ContentKey +import com.android.compose.animation.scene.OverscrollScope +import com.android.compose.animation.scene.OverscrollSpecImpl +import com.android.compose.animation.scene.ProgressVisibilityThreshold +import com.android.compose.animation.scene.SceneTransitionLayoutImpl +import com.android.compose.animation.scene.TransformationSpec +import com.android.compose.animation.scene.TransformationSpecImpl +import com.android.compose.animation.scene.TransitionKey +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch + +/** The state associated to one or more contents. */ +@Stable +sealed interface ContentState<out T : ContentKey> { + /** The [content] is idle, it does not animate. */ + sealed class Idle<T : ContentKey>(val content: T) : ContentState<T> + + /** The content is transitioning with another content. */ + sealed class Transition<out T : ContentKey>( + val fromContent: T, + val toContent: T, + internal val replacedTransition: Transition<T>?, + ) : ContentState<T> { + /** + * The key of this transition. This should usually be null, but it can be specified to use a + * specific set of transformations associated to this transition. + */ + open val key: TransitionKey? = null + + /** + * The progress of the transition. This is usually in the `[0; 1]` range, but it can also be + * less than `0` or greater than `1` when using transitions with a spring AnimationSpec or + * when flinging quickly during a swipe gesture. + */ + abstract val progress: Float + + /** The current velocity of [progress], in progress units. */ + abstract val progressVelocity: Float + + /** Whether the transition was triggered by user input rather than being programmatic. */ + abstract val isInitiatedByUserInput: Boolean + + /** Whether user input is currently driving the transition. */ + abstract val isUserInputOngoing: Boolean + + /** + * The progress of the preview transition. This is usually in the `[0; 1]` range, but it can + * also be less than `0` or greater than `1` when using transitions with a spring + * AnimationSpec or when flinging quickly during a swipe gesture. + */ + internal open val previewProgress: Float = 0f + + /** The current velocity of [previewProgress], in progress units. */ + internal open val previewProgressVelocity: Float = 0f + + /** Whether the transition is currently in the preview stage */ + internal open val isInPreviewStage: Boolean = false + + /** + * The current [TransformationSpecImpl] and [OverscrollSpecImpl] associated to this + * transition. + * + * Important: These will be set exactly once, when this transition is + * [started][MutableSceneTransitionLayoutStateImpl.startTransition]. + */ + internal var transformationSpec: TransformationSpecImpl = TransformationSpec.Empty + internal var previewTransformationSpec: TransformationSpecImpl? = null + private var fromOverscrollSpec: OverscrollSpecImpl? = null + private var toOverscrollSpec: OverscrollSpecImpl? = null + + /** The current [OverscrollSpecImpl], if this transition is currently overscrolling. */ + internal val currentOverscrollSpec: OverscrollSpecImpl? + get() { + if (this !is HasOverscrollProperties) return null + val progress = progress + val bouncingContent = bouncingContent + return when { + progress < 0f || bouncingContent == fromContent -> fromOverscrollSpec + progress > 1f || bouncingContent == toContent -> toOverscrollSpec + else -> null + } + } + + /** + * An animatable that animates from 1f to 0f. This will be used to nicely animate the sudden + * jump of values when this transitions interrupts another one. + */ + private var interruptionDecay: Animatable<Float, AnimationVector1D>? = null + + init { + check(fromContent != toContent) + check( + replacedTransition == null || + (replacedTransition.fromContent == fromContent && + replacedTransition.toContent == toContent) + ) + } + + /** + * Force this transition to finish and animate to an [Idle] state. + * + * Important: Once this is called, the effective state of the transition should remain + * unchanged. For instance, in the case of a [TransitionState.Transition], its + * [currentScene][TransitionState.Transition.currentScene] should never change once [finish] + * is called. + * + * @return the [Job] that animates to the idle state. It can be used to wait until the + * animation is complete or cancel it to snap the animation. Calling [finish] multiple + * times will return the same [Job]. + */ + abstract fun finish(): Job + + /** + * Whether we are transitioning. If [from] or [to] is empty, we will also check that they + * match the contents we are animating from and/or to. + */ + fun isTransitioning(from: ContentKey? = null, to: ContentKey? = null): Boolean { + return (from == null || fromContent == from) && (to == null || toContent == to) + } + + /** Whether we are transitioning from [content] to [other], or from [other] to [content]. */ + fun isTransitioningBetween(content: ContentKey, other: ContentKey): Boolean { + return isTransitioning(from = content, to = other) || + isTransitioning(from = other, to = content) + } + + internal fun updateOverscrollSpecs( + fromSpec: OverscrollSpecImpl?, + toSpec: OverscrollSpecImpl?, + ) { + fromOverscrollSpec = fromSpec + toOverscrollSpec = toSpec + } + + /** Returns if the [progress] value of this transition can go beyond range `[0; 1]` */ + internal fun isWithinProgressRange(progress: Float): Boolean { + // If the properties are missing we assume that every [Transition] can overscroll + if (this !is HasOverscrollProperties) return true + // [OverscrollSpec] for the current scene, even if it hasn't started overscrolling yet. + val specForCurrentScene = + when { + progress <= 0f -> fromOverscrollSpec + progress >= 1f -> toOverscrollSpec + else -> null + } ?: return true + + return specForCurrentScene.transformationSpec.transformations.isNotEmpty() + } + + internal open fun interruptionProgress( + layoutImpl: SceneTransitionLayoutImpl, + ): Float { + if (!layoutImpl.state.enableInterruptions) { + return 0f + } + + if (replacedTransition != null) { + return replacedTransition.interruptionProgress(layoutImpl) + } + + fun create(): Animatable<Float, AnimationVector1D> { + val animatable = Animatable(1f, visibilityThreshold = ProgressVisibilityThreshold) + layoutImpl.coroutineScope.launch { + val swipeSpec = layoutImpl.state.transitions.defaultSwipeSpec + val progressSpec = + spring( + stiffness = swipeSpec.stiffness, + dampingRatio = swipeSpec.dampingRatio, + visibilityThreshold = ProgressVisibilityThreshold, + ) + animatable.animateTo(0f, progressSpec) + } + + return animatable + } + + val animatable = interruptionDecay ?: create().also { interruptionDecay = it } + return animatable.value + } + } + + interface HasOverscrollProperties { + /** + * The position of the [Transition.toContent]. + * + * Used to understand the direction of the overscroll. + */ + val isUpOrLeft: Boolean + + /** + * The relative orientation between [Transition.fromContent] and [Transition.toContent]. + * + * Used to understand the orientation of the overscroll. + */ + val orientation: Orientation + + /** + * Scope which can be used in the Overscroll DSL to define a transformation based on the + * distance between [Transition.fromContent] and [Transition.toContent]. + */ + val overscrollScope: OverscrollScope + + /** + * The content (scene or overlay) around which the transition is currently bouncing. When + * not `null`, this transition is currently oscillating around this content and will soon + * settle to that content. + */ + val bouncingContent: ContentKey? + + companion object { + const val DistanceUnspecified = 0f + } + } +} diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt new file mode 100644 index 000000000000..77de22ceeec4 --- /dev/null +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2024 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.compose.animation.scene.content.state + +import androidx.compose.runtime.Stable +import com.android.compose.animation.scene.SceneKey + +/** The state associated to one or more scenes. */ +// TODO(b/353679003): Rename to SceneState. +@Stable +sealed interface TransitionState : ContentState<SceneKey> { + /** + * The current effective scene. If a new transition was triggered, it would start from this + * scene. + * + * For instance, when swiping from scene A to scene B, the [currentScene] is A when the swipe + * gesture starts, but then if the user flings their finger and commits the transition to scene + * B, then [currentScene] becomes scene B even if the transition is not finished yet and is + * still animating to settle to scene B. + */ + val currentScene: SceneKey + + /** The scene [currentScene] is idle. */ + data class Idle( + override val currentScene: SceneKey, + ) : TransitionState, ContentState.Idle<SceneKey>(currentScene) + + /** There is a transition animating between [fromScene] and [toScene]. */ + abstract class Transition( + /** The scene this transition is starting from. Can't be the same as toScene */ + val fromScene: SceneKey, + + /** The scene this transition is going to. Can't be the same as fromScene */ + val toScene: SceneKey, + + /** The transition that `this` transition is replacing, if any. */ + replacedTransition: Transition? = null, + ) : TransitionState, ContentState.Transition<SceneKey>(fromScene, toScene, replacedTransition) +} diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt index 65d4d2da4dd1..538ce799c3eb 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt @@ -21,9 +21,8 @@ import com.android.compose.animation.scene.ContentKey import com.android.compose.animation.scene.Element import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.ElementMatcher -import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.SceneTransitionLayoutImpl -import com.android.compose.animation.scene.TransitionState +import com.android.compose.animation.scene.content.state.ContentState /** Anchor the size of an element to the size of another element. */ internal class AnchoredSize( @@ -36,19 +35,19 @@ internal class AnchoredSize( layoutImpl: SceneTransitionLayoutImpl, content: ContentKey, element: Element, - sceneState: Element.State, - transition: TransitionState.Transition, + stateInContent: Element.State, + transition: ContentState.Transition<*>, value: IntSize, ): IntSize { - fun anchorSizeIn(scene: SceneKey): IntSize { + fun anchorSizeIn(content: ContentKey): IntSize { val size = - layoutImpl.elements[anchor]?.stateByContent?.get(scene)?.targetSize?.takeIf { + layoutImpl.elements[anchor]?.stateByContent?.get(content)?.targetSize?.takeIf { it != Element.SizeUnspecified } ?: throwMissingAnchorException( transformation = "AnchoredSize", anchor = anchor, - scene = scene, + content = content, ) return IntSize( @@ -60,10 +59,10 @@ internal class AnchoredSize( // This simple implementation assumes that the size of [element] is the same as the size of // the [anchor] in [scene], so simply transform to the size of the anchor in the other // scene. - return if (content == transition.fromScene) { - anchorSizeIn(transition.toScene) + return if (content == transition.fromContent) { + anchorSizeIn(transition.toContent) } else { - anchorSizeIn(transition.fromScene) + anchorSizeIn(transition.fromContent) } } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt index 8d7e1c971acf..258f54122711 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt @@ -22,9 +22,8 @@ import com.android.compose.animation.scene.ContentKey import com.android.compose.animation.scene.Element import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.ElementMatcher -import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.SceneTransitionLayoutImpl -import com.android.compose.animation.scene.TransitionState +import com.android.compose.animation.scene.content.state.ContentState /** Anchor the translation of an element to another element. */ internal class AnchoredTranslate( @@ -35,33 +34,33 @@ internal class AnchoredTranslate( layoutImpl: SceneTransitionLayoutImpl, content: ContentKey, element: Element, - sceneState: Element.State, - transition: TransitionState.Transition, + stateInContent: Element.State, + transition: ContentState.Transition<*>, value: Offset, ): Offset { - fun throwException(scene: SceneKey?): Nothing { + fun throwException(content: ContentKey?): Nothing { throwMissingAnchorException( transformation = "AnchoredTranslate", anchor = anchor, - scene = scene, + content = content, ) } - val anchor = layoutImpl.elements[anchor] ?: throwException(scene = null) - fun anchorOffsetIn(scene: SceneKey): Offset? { - return anchor.stateByContent[scene]?.targetOffset?.takeIf { it.isSpecified } + val anchor = layoutImpl.elements[anchor] ?: throwException(content = null) + fun anchorOffsetIn(content: ContentKey): Offset? { + return anchor.stateByContent[content]?.targetOffset?.takeIf { it.isSpecified } } // [element] will move the same amount as [anchor] does. // TODO(b/290184746): Also support anchors that are not shared but translated because of // other transformations, like an edge translation. val anchorFromOffset = - anchorOffsetIn(transition.fromScene) ?: throwException(transition.fromScene) + anchorOffsetIn(transition.fromContent) ?: throwException(transition.fromContent) val anchorToOffset = - anchorOffsetIn(transition.toScene) ?: throwException(transition.toScene) + anchorOffsetIn(transition.toContent) ?: throwException(transition.toContent) val offset = anchorToOffset - anchorFromOffset - return if (content == transition.toScene) { + return if (content == transition.toContent) { Offset( value.x - offset.x, value.y - offset.y, @@ -78,11 +77,11 @@ internal class AnchoredTranslate( internal fun throwMissingAnchorException( transformation: String, anchor: ElementKey, - scene: SceneKey?, + content: ContentKey?, ): Nothing { error( """ - Anchor ${anchor.debugName} does not have a target state in scene ${scene?.debugName}. + Anchor ${anchor.debugName} does not have a target state in content ${content?.debugName}. This either means that it was not composed at all during the transition or that it was composed too late, for instance during layout/subcomposition. To avoid flickers in $transformation, you should make sure that the composition and layout of anchor is *not* diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt index f010c3b1d3b4..be8dac21a5cf 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt @@ -22,7 +22,7 @@ import com.android.compose.animation.scene.Element import com.android.compose.animation.scene.ElementMatcher import com.android.compose.animation.scene.Scale import com.android.compose.animation.scene.SceneTransitionLayoutImpl -import com.android.compose.animation.scene.TransitionState +import com.android.compose.animation.scene.content.state.ContentState /** * Scales the draw size of an element. Note this will only scale the draw inside of an element, @@ -39,8 +39,8 @@ internal class DrawScale( layoutImpl: SceneTransitionLayoutImpl, content: ContentKey, element: Element, - sceneState: Element.State, - transition: TransitionState.Transition, + stateInContent: Element.State, + transition: ContentState.Transition<*>, value: Scale, ): Scale { return Scale(scaleX, scaleY, pivot) diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt index dfce997ba190..d72e43abe8f4 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt @@ -22,7 +22,7 @@ import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.Element import com.android.compose.animation.scene.ElementMatcher import com.android.compose.animation.scene.SceneTransitionLayoutImpl -import com.android.compose.animation.scene.TransitionState +import com.android.compose.animation.scene.content.state.ContentState /** Translate an element from an edge of the layout. */ internal class EdgeTranslate( @@ -34,12 +34,12 @@ internal class EdgeTranslate( layoutImpl: SceneTransitionLayoutImpl, content: ContentKey, element: Element, - sceneState: Element.State, - transition: TransitionState.Transition, + stateInContent: Element.State, + transition: ContentState.Transition<*>, value: Offset ): Offset { val sceneSize = layoutImpl.content(content).targetSize - val elementSize = sceneState.targetSize + val elementSize = stateInContent.targetSize if (elementSize == Element.SizeUnspecified) { return value } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt index c1bb017143a9..92ae30f8e3db 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt @@ -20,7 +20,7 @@ import com.android.compose.animation.scene.ContentKey import com.android.compose.animation.scene.Element import com.android.compose.animation.scene.ElementMatcher import com.android.compose.animation.scene.SceneTransitionLayoutImpl -import com.android.compose.animation.scene.TransitionState +import com.android.compose.animation.scene.content.state.ContentState /** Fade an element in or out. */ internal class Fade( @@ -30,8 +30,8 @@ internal class Fade( layoutImpl: SceneTransitionLayoutImpl, content: ContentKey, element: Element, - sceneState: Element.State, - transition: TransitionState.Transition, + stateInContent: Element.State, + transition: ContentState.Transition<*>, value: Float ): Float { // Return the alpha value of [element] either when it starts fading in or when it finished diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt index 5adbf7eb2614..e8515dce5fec 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt @@ -21,7 +21,7 @@ import com.android.compose.animation.scene.ContentKey import com.android.compose.animation.scene.Element import com.android.compose.animation.scene.ElementMatcher import com.android.compose.animation.scene.SceneTransitionLayoutImpl -import com.android.compose.animation.scene.TransitionState +import com.android.compose.animation.scene.content.state.ContentState import kotlin.math.roundToInt /** @@ -37,8 +37,8 @@ internal class ScaleSize( layoutImpl: SceneTransitionLayoutImpl, content: ContentKey, element: Element, - sceneState: Element.State, - transition: TransitionState.Transition, + stateInContent: Element.State, + transition: ContentState.Transition<*>, value: IntSize, ): IntSize { return IntSize( diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt index 24b71944b6d0..77ec89161d43 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt @@ -23,7 +23,7 @@ import com.android.compose.animation.scene.ContentKey import com.android.compose.animation.scene.Element import com.android.compose.animation.scene.ElementMatcher import com.android.compose.animation.scene.SceneTransitionLayoutImpl -import com.android.compose.animation.scene.TransitionState +import com.android.compose.animation.scene.content.state.ContentState /** A transformation applied to one or more elements during a transition. */ sealed interface Transformation { @@ -63,8 +63,8 @@ internal sealed interface PropertyTransformation<T> : Transformation { layoutImpl: SceneTransitionLayoutImpl, content: ContentKey, element: Element, - sceneState: Element.State, - transition: TransitionState.Transition, + stateInContent: Element.State, + transition: ContentState.Transition<*>, value: T, ): T } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt index 123756ae4211..fab4ced1ff44 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt @@ -24,7 +24,7 @@ import com.android.compose.animation.scene.Element import com.android.compose.animation.scene.ElementMatcher import com.android.compose.animation.scene.OverscrollScope import com.android.compose.animation.scene.SceneTransitionLayoutImpl -import com.android.compose.animation.scene.TransitionState +import com.android.compose.animation.scene.content.state.ContentState internal class Translate( override val matcher: ElementMatcher, @@ -35,8 +35,8 @@ internal class Translate( layoutImpl: SceneTransitionLayoutImpl, content: ContentKey, element: Element, - sceneState: Element.State, - transition: TransitionState.Transition, + stateInContent: Element.State, + transition: ContentState.Transition<*>, value: Offset, ): Offset { return with(layoutImpl.density) { @@ -57,14 +57,14 @@ internal class OverscrollTranslate( layoutImpl: SceneTransitionLayoutImpl, content: ContentKey, element: Element, - sceneState: Element.State, - transition: TransitionState.Transition, + stateInContent: Element.State, + transition: ContentState.Transition<*>, value: Offset, ): Offset { // As this object is created by OverscrollBuilderImpl and we retrieve the current // OverscrollSpec only when the transition implements HasOverscrollProperties, we can assume // that this method was invoked after performing this check. - val overscrollProperties = transition as TransitionState.HasOverscrollProperties + val overscrollProperties = transition as ContentState.HasOverscrollProperties return Offset( x = value.x + overscrollProperties.overscrollScope.x(), diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt index ed9888560f05..23bcf109eb98 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt @@ -18,7 +18,7 @@ package com.android.compose.animation.scene.transition.link import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.TransitionKey -import com.android.compose.animation.scene.TransitionState +import com.android.compose.animation.scene.content.state.TransitionState import kotlinx.coroutines.Job /** A linked transition which is driven by a [originalTransition]. */ diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt index 2018d6e37b57..c0c40dd23897 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt @@ -20,7 +20,7 @@ import com.android.compose.animation.scene.MutableSceneTransitionLayoutStateImpl import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.SceneTransitionLayoutState import com.android.compose.animation.scene.TransitionKey -import com.android.compose.animation.scene.TransitionState +import com.android.compose.animation.scene.content.state.TransitionState /** A link between a source (implicit) and [target] `SceneTransitionLayoutState`. */ class StateLink(target: SceneTransitionLayoutState, val transitionLinks: List<TransitionLink>) { diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt index 87162987bb00..01895c91a399 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt @@ -88,11 +88,11 @@ class AnimatedSharedAsStateTest { @Composable private fun ContentScope.MovableFoo( + key: MovableElementKey, targetValues: Values, onCurrentValueChanged: (Values) -> Unit, ) { - val key = TestElements.Foo - MovableElement(key = key, Modifier) { + MovableElement(key, Modifier) { val int by animateElementIntAsState(targetValues.int, key = TestValues.Value1) val float by animateElementFloatAsState(targetValues.float, key = TestValues.Value2) val dp by animateElementDpAsState(targetValues.dp, key = TestValues.Value3) @@ -183,15 +183,22 @@ class AnimatedSharedAsStateTest { var lastValueInFrom = fromValues var lastValueInTo = toValues + val key = MovableElementKey("Foo", contents = setOf(SceneA, SceneB)) + rule.testTransition( fromSceneContent = { MovableFoo( + key = key, targetValues = fromValues, - onCurrentValueChanged = { lastValueInFrom = it } + onCurrentValueChanged = { lastValueInFrom = it }, ) }, toSceneContent = { - MovableFoo(targetValues = toValues, onCurrentValueChanged = { lastValueInTo = it }) + MovableFoo( + key = key, + targetValues = toValues, + onCurrentValueChanged = { lastValueInTo = it }, + ) }, transition = { // The transition lasts 64ms = 4 frames. diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt index be89b18a02c3..dc5b2f78180f 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt @@ -35,7 +35,8 @@ import com.android.compose.animation.scene.NestedScrollBehavior.EdgeWithPreview import com.android.compose.animation.scene.TestScenes.SceneA import com.android.compose.animation.scene.TestScenes.SceneB import com.android.compose.animation.scene.TestScenes.SceneC -import com.android.compose.animation.scene.TransitionState.Transition +import com.android.compose.animation.scene.content.state.TransitionState +import com.android.compose.animation.scene.content.state.TransitionState.Transition import com.android.compose.animation.scene.subjects.assertThat import com.android.compose.test.MonotonicClockTestScope import com.android.compose.test.runMonotonicClockTest diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementScenePickerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementContentPickerTest.kt index 3b022e8adc72..96e521bced29 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementScenePickerTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementContentPickerTest.kt @@ -31,12 +31,12 @@ import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) -class ElementScenePickerTest { +class ElementContentPickerTest { @get:Rule val rule = createComposeRule() @Test fun highestZIndexPicker() { - val key = ElementKey("TestElement", scenePicker = HighestZIndexScenePicker) + val key = ElementKey("TestElement", contentPicker = HighestZIndexContentPicker) rule.testTransition( fromSceneContent = { Box(Modifier.element(key).size(10.dp)) }, toSceneContent = { Box(Modifier.element(key).size(10.dp)) }, @@ -62,7 +62,7 @@ class ElementScenePickerTest { @Test fun lowestZIndexPicker() { - val key = ElementKey("TestElement", scenePicker = LowestZIndexScenePicker) + val key = ElementKey("TestElement", contentPicker = LowestZIndexContentPicker) rule.testTransition( fromSceneContent = { Box(Modifier.element(key).size(10.dp)) }, toSceneContent = { Box(Modifier.element(key).size(10.dp)) }, diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt index 63df7a93375c..75f44ff9cfe0 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt @@ -1723,9 +1723,11 @@ class ElementTest { val fooInA = "fooInA" val fooInB = "fooInB" + val key = MovableElementKey("Foo", contents = setOf(SceneA, SceneB)) + @Composable fun ContentScope.MovableFoo(text: String, modifier: Modifier = Modifier) { - MovableElement(TestElements.Foo, modifier) { content { Text(text) } } + MovableElement(key, modifier) { content { Text(text) } } } rule.setContent { diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt index 3552d3d0bc1b..ca72181e4fcf 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt @@ -22,6 +22,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.compose.animation.scene.TestScenes.SceneA import com.android.compose.animation.scene.TestScenes.SceneB import com.android.compose.animation.scene.TestScenes.SceneC +import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.animation.scene.subjects.assertThat import com.android.compose.test.runMonotonicClockTest import com.google.common.truth.Correspondence diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementScenePickerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementContentPickerTest.kt index 6745fbe064ff..e1d09453ee64 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementScenePickerTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementContentPickerTest.kt @@ -18,20 +18,22 @@ package com.android.compose.animation.scene import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat +import org.junit.Assert.assertThrows import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) -class MovableElementScenePickerTest { +class MovableElementContentPickerTest { @Test fun toSceneInScenes() { - val picker = MovableElementScenePicker(scenes = setOf(TestScenes.SceneA, TestScenes.SceneB)) + val picker = + MovableElementContentPicker(contents = setOf(TestScenes.SceneA, TestScenes.SceneB)) assertThat( - picker.sceneDuringTransition( + picker.contentDuringTransition( TestElements.Foo, transition(from = TestScenes.SceneA, to = TestScenes.SceneB), - fromSceneZIndex = 0f, - toSceneZIndex = 1f, + fromContentZIndex = 0f, + toContentZIndex = 1f, ) ) .isEqualTo(TestScenes.SceneB) @@ -39,13 +41,13 @@ class MovableElementScenePickerTest { @Test fun fromSceneInScenes() { - val picker = MovableElementScenePicker(scenes = setOf(TestScenes.SceneA)) + val picker = MovableElementContentPicker(contents = setOf(TestScenes.SceneA)) assertThat( - picker.sceneDuringTransition( + picker.contentDuringTransition( TestElements.Foo, transition(from = TestScenes.SceneA, to = TestScenes.SceneB), - fromSceneZIndex = 0f, - toSceneZIndex = 1f, + fromContentZIndex = 0f, + toContentZIndex = 1f, ) ) .isEqualTo(TestScenes.SceneA) @@ -53,15 +55,14 @@ class MovableElementScenePickerTest { @Test fun noneInScenes() { - val picker = MovableElementScenePicker(scenes = emptySet()) - assertThat( - picker.sceneDuringTransition( - TestElements.Foo, - transition(from = TestScenes.SceneA, to = TestScenes.SceneB), - fromSceneZIndex = 0f, - toSceneZIndex = 1f, - ) + val picker = MovableElementContentPicker(contents = emptySet()) + assertThrows(IllegalStateException::class.java) { + picker.contentDuringTransition( + TestElements.Foo, + transition(from = TestScenes.SceneA, to = TestScenes.SceneB), + fromContentZIndex = 0f, + toContentZIndex = 1f, ) - .isEqualTo(null) + } } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt index 821cc2927443..520e7599f56a 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt @@ -43,6 +43,10 @@ import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.compose.ui.unit.dp import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.compose.animation.scene.TestScenes.SceneA +import com.android.compose.animation.scene.TestScenes.SceneB +import com.android.compose.animation.scene.content.state.ContentState +import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.animation.scene.subjects.assertThat import com.android.compose.test.assertSizeIsEqualTo import com.google.common.truth.Truth.assertThat @@ -62,7 +66,7 @@ class MovableElementTest { } @Composable - private fun ContentScope.MovableCounter(key: ElementKey, modifier: Modifier) { + private fun ContentScope.MovableCounter(key: MovableElementKey, modifier: Modifier) { MovableElement(key, modifier) { content { Counter() } } } @@ -74,8 +78,8 @@ class MovableElementTest { }, toSceneContent = { Box(Modifier.element(TestElements.Foo).size(100.dp)) { Counter() } }, transition = { spec = tween(durationMillis = 16 * 4, easing = LinearEasing) }, - fromScene = TestScenes.SceneA, - toScene = TestScenes.SceneB, + fromScene = SceneA, + toScene = SceneB, ) { before { // Click 3 times on the counter. @@ -103,7 +107,7 @@ class MovableElementTest { rule .onNode( hasText("count: 3") and - hasParent(isElement(TestElements.Foo, scene = TestScenes.SceneA)) + hasParent(isElement(TestElements.Foo, scene = SceneA)) ) .assertExists() .assertIsNotDisplayed() @@ -111,7 +115,7 @@ class MovableElementTest { rule .onNode( hasText("count: 0") and - hasParent(isElement(TestElements.Foo, scene = TestScenes.SceneB)) + hasParent(isElement(TestElements.Foo, scene = SceneB)) ) .assertIsDisplayed() .assertSizeIsEqualTo(75.dp, 75.dp) @@ -148,27 +152,30 @@ class MovableElementTest { @Test fun movableElementIsMovedAndComposedOnlyOnce() { val key = - ElementKey( + MovableElementKey( "Foo", - scenePicker = - object : ElementScenePicker { - override fun sceneDuringTransition( + contentPicker = + object : StaticElementContentPicker { + override val contents: Set<ContentKey> = setOf(SceneA, SceneB) + + override fun contentDuringTransition( element: ElementKey, - transition: TransitionState.Transition, - fromSceneZIndex: Float, - toSceneZIndex: Float - ): SceneKey { - assertThat(transition).hasFromScene(TestScenes.SceneA) - assertThat(transition).hasToScene(TestScenes.SceneB) - assertThat(fromSceneZIndex).isEqualTo(0) - assertThat(toSceneZIndex).isEqualTo(1) + transition: ContentState.Transition<*>, + fromContentZIndex: Float, + toContentZIndex: Float + ): ContentKey { + transition as TransitionState.Transition + assertThat(transition).hasFromScene(SceneA) + assertThat(transition).hasToScene(SceneB) + assertThat(fromContentZIndex).isEqualTo(0) + assertThat(toContentZIndex).isEqualTo(1) // Compose Foo in Scene A if progress < 0.65f, otherwise compose it // in Scene B. return if (transition.progress < 0.65f) { - TestScenes.SceneA + SceneA } else { - TestScenes.SceneB + SceneB } } } @@ -178,8 +185,8 @@ class MovableElementTest { fromSceneContent = { MovableCounter(key, Modifier.size(50.dp)) }, toSceneContent = { MovableCounter(key, Modifier.size(100.dp)) }, transition = { spec = tween(durationMillis = 16 * 4, easing = LinearEasing) }, - fromScene = TestScenes.SceneA, - toScene = TestScenes.SceneB, + fromScene = SceneA, + toScene = SceneB, ) { before { // Click 3 times on the counter. @@ -207,7 +214,7 @@ class MovableElementTest { rule .onNode( hasText("count: 3") and - hasParent(isElement(TestElements.Foo, scene = TestScenes.SceneA)) + hasParent(isElement(TestElements.Foo, scene = SceneA)) ) .assertIsDisplayed() .assertSizeIsEqualTo(75.dp, 75.dp) @@ -228,7 +235,7 @@ class MovableElementTest { rule .onNode( hasText("count: 3") and - hasParent(isElement(TestElements.Foo, scene = TestScenes.SceneB)) + hasParent(isElement(TestElements.Foo, scene = SceneB)) ) .assertIsDisplayed() @@ -263,17 +270,19 @@ class MovableElementTest { @Test fun movableElementContentIsRecomposedIfContentParametersChange() { + val key = MovableElementKey("Foo", contents = setOf(SceneA, SceneB)) + @Composable fun ContentScope.MovableFoo(text: String, modifier: Modifier = Modifier) { - MovableElement(TestElements.Foo, modifier) { content { Text(text) } } + MovableElement(key, modifier) { content { Text(text) } } } rule.testTransition( fromSceneContent = { MovableFoo(text = "fromScene") }, toSceneContent = { MovableFoo(text = "toScene") }, transition = { spec = tween(durationMillis = 16 * 4, easing = LinearEasing) }, - fromScene = TestScenes.SceneA, - toScene = TestScenes.SceneB, + fromScene = SceneA, + toScene = SceneB, ) { // Before the transition, only fromScene is composed. before { @@ -314,9 +323,10 @@ class MovableElementTest { @Test fun movableElementScopeExtendsBoxScope() { + val key = MovableElementKey("Foo", contents = setOf(SceneA)) rule.setContent { TestContentScope { - MovableElement(TestElements.Foo, Modifier.size(200.dp)) { + MovableElement(key, Modifier.size(200.dp)) { content { Box(Modifier.testTag("bottomEnd").align(Alignment.BottomEnd)) Box(Modifier.testTag("matchParentSize").matchParentSize()) diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt index 52cceecadbf9..6b417ee5a468 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt @@ -25,6 +25,7 @@ import com.android.compose.animation.scene.TestScenes.SceneA import com.android.compose.animation.scene.TestScenes.SceneB import com.android.compose.animation.scene.TestScenes.SceneC import com.android.compose.animation.scene.TestScenes.SceneD +import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.animation.scene.subjects.assertThat import com.android.compose.animation.scene.transition.link.StateLink import com.android.compose.test.runMonotonicClockTest diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt index 66d4059adfda..91bd7e1800cd 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt @@ -17,6 +17,8 @@ package com.android.compose.animation.scene import androidx.compose.foundation.gestures.Orientation +import com.android.compose.animation.scene.content.state.ContentState +import com.android.compose.animation.scene.content.state.TransitionState import kotlinx.coroutines.Job import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex @@ -37,14 +39,14 @@ fun transition( isInitiatedByUserInput: Boolean = false, isUserInputOngoing: Boolean = false, isUpOrLeft: Boolean = false, - bouncingScene: SceneKey? = null, + bouncingContent: ContentKey? = null, orientation: Orientation = Orientation.Horizontal, onFinish: ((TransitionState.Transition) -> Job)? = null, replacedTransition: TransitionState.Transition? = null, ): TransitionState.Transition { return object : TransitionState.Transition(from, to, replacedTransition), - TransitionState.HasOverscrollProperties { + ContentState.HasOverscrollProperties { override val currentScene: SceneKey get() = current() @@ -66,7 +68,7 @@ fun transition( override val isInitiatedByUserInput: Boolean = isInitiatedByUserInput override val isUserInputOngoing: Boolean = isUserInputOngoing override val isUpOrLeft: Boolean = isUpOrLeft - override val bouncingScene: SceneKey? = bouncingScene + override val bouncingContent: ContentKey? = bouncingContent override val orientation: Orientation = orientation override val overscrollScope: OverscrollScope = object : OverscrollScope { diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt index f4e0073843ce..68240b5337fe 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt @@ -59,7 +59,7 @@ class TransitionDslTest { assertThat(transitions.transitionSpecs) .comparingElementsUsing( - Correspondence.transforming<TransitionSpecImpl, Pair<SceneKey?, SceneKey?>>( + Correspondence.transforming<TransitionSpecImpl, Pair<ContentKey?, ContentKey?>>( { it?.from to it?.to }, "has (from, to) equal to" ) diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt index e997a75a7a38..a12ab7888a15 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt @@ -18,7 +18,8 @@ package com.android.compose.animation.scene.subjects import com.android.compose.animation.scene.OverscrollSpec import com.android.compose.animation.scene.SceneKey -import com.android.compose.animation.scene.TransitionState +import com.android.compose.animation.scene.content.state.ContentState +import com.android.compose.animation.scene.content.state.TransitionState import com.google.common.truth.Fact.simpleFact import com.google.common.truth.FailureMetadata import com.google.common.truth.Subject @@ -132,12 +133,12 @@ private constructor( } fun hasBouncingScene(scene: SceneKey) { - if (actual !is TransitionState.HasOverscrollProperties) { - failWithActual(simpleFact("expected to be TransitionState.HasOverscrollProperties")) + if (actual !is ContentState.HasOverscrollProperties) { + failWithActual(simpleFact("expected to be ContentState.HasOverscrollProperties")) } - check("bouncingScene") - .that((actual as TransitionState.HasOverscrollProperties).bouncingScene) + check("bouncingContent") + .that((actual as ContentState.HasOverscrollProperties).bouncingContent) .isEqualTo(scene) } |