summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig10
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt1
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt47
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt9
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Scene.kt1
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt34
6 files changed, 89 insertions, 13 deletions
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index cb1f1af8b5a6..a90081738062 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -2093,3 +2093,13 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "always_compose_qs_ui_fragment"
+ namespace: "systemui"
+ description: "Have QQS and QS scenes in the Compose fragment always composed, not just when it should be visible."
+ bug: "389985793"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+} \ No newline at end of file
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 404f1b217026..22688d310b44 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
@@ -102,6 +102,7 @@ interface SceneTransitionLayoutScope<out CS : ContentScope> {
key: SceneKey,
userActions: Map<UserAction, UserActionResult> = emptyMap(),
effectFactory: OverscrollFactory? = null,
+ alwaysCompose: Boolean = false,
content: @Composable CS.() -> Unit,
)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index e3c4eb0f8bea..4da83c3a6fc9 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -207,6 +207,9 @@ internal class SceneTransitionLayoutImpl(
private val nestedScrollDispatcher = NestedScrollDispatcher()
private val nestedScrollConnection = object : NestedScrollConnection {}
+ // TODO(b/399825091): Remove this.
+ private var scenesToAlwaysCompose: MutableList<Scene>? = null
+
init {
updateContents(builder, layoutDirection, defaultEffectFactory)
@@ -312,6 +315,7 @@ internal class SceneTransitionLayoutImpl(
key: SceneKey,
userActions: Map<UserAction, UserActionResult>,
effectFactory: OverscrollFactory?,
+ alwaysCompose: Boolean,
content: @Composable InternalContentScope.() -> Unit,
) {
require(!overlaysDefined) { "all scenes must be defined before overlays" }
@@ -324,6 +328,10 @@ internal class SceneTransitionLayoutImpl(
Content.calculateGlobalZIndex(parentZIndex, ++zIndex, ancestors.size)
val factory = effectFactory ?: defaultEffectFactory
if (scene != null) {
+ check(alwaysCompose == scene.alwaysCompose) {
+ "scene.alwaysCompose can not change"
+ }
+
// Update an existing scene.
scene.content = content
scene.userActions = resolvedUserActions
@@ -332,7 +340,7 @@ internal class SceneTransitionLayoutImpl(
scene.maybeUpdateEffects(factory)
} else {
// New scene.
- scenes[key] =
+ val scene =
Scene(
key,
this@SceneTransitionLayoutImpl,
@@ -341,7 +349,16 @@ internal class SceneTransitionLayoutImpl(
zIndex.toFloat(),
globalZIndex,
factory,
+ alwaysCompose,
)
+
+ scenes[key] = scene
+
+ if (alwaysCompose) {
+ (scenesToAlwaysCompose
+ ?: mutableListOf<Scene>().also { scenesToAlwaysCompose = it })
+ .add(scene)
+ }
}
}
@@ -470,22 +487,24 @@ internal class SceneTransitionLayoutImpl(
@Composable
private fun Scenes() {
- scenesToCompose().fastForEach { scene -> key(scene.key) { scene.Content() } }
+ scenesToCompose().fastForEach { (scene, isInvisible) ->
+ key(scene.key) { scene.Content(isInvisible = isInvisible) }
+ }
}
- private fun scenesToCompose(): List<Scene> {
+ private fun scenesToCompose(): List<SceneToCompose> {
val transitions = state.currentTransitions
- return if (transitions.isEmpty()) {
- listOf(scene(state.transitionState.currentScene))
- } else {
- buildList {
- val visited = mutableSetOf<SceneKey>()
- fun maybeAdd(sceneKey: SceneKey) {
- if (visited.add(sceneKey)) {
- add(scene(sceneKey))
- }
+ return buildList {
+ val visited = mutableSetOf<SceneKey>()
+ fun maybeAdd(sceneKey: SceneKey, isInvisible: Boolean = false) {
+ if (visited.add(sceneKey)) {
+ add(SceneToCompose(scene(sceneKey), isInvisible))
}
+ }
+ if (transitions.isEmpty()) {
+ maybeAdd(state.transitionState.currentScene)
+ } else {
// Compose the new scene we are going to first.
transitions.fastForEachReversed { transition ->
when (transition) {
@@ -504,9 +523,13 @@ internal class SceneTransitionLayoutImpl(
// Make sure that the current scene is always composed.
maybeAdd(transitions.last().currentScene)
}
+
+ scenesToAlwaysCompose?.fastForEach { maybeAdd(it.key, isInvisible = true) }
}
}
+ private data class SceneToCompose(val scene: Scene, val isInvisible: Boolean)
+
@Composable
private fun BoxScope.Overlays() {
val overlaysOrderedByZIndex = overlaysToComposeOrderedByZIndex()
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 149a9e7c4705..72ee75ad2d47 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
@@ -34,6 +34,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.LookaheadScope
import androidx.compose.ui.layout.approachLayout
+import androidx.compose.ui.layout.layout
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.zIndex
@@ -154,11 +155,12 @@ internal sealed class Content(
@SuppressLint("NotConstructor")
@Composable
- fun Content(modifier: Modifier = Modifier) {
+ fun Content(modifier: Modifier = Modifier, isInvisible: Boolean = false) {
// If this content has a custom factory, provide it to the content so that the factory is
// automatically used when calling rememberOverscrollEffect().
Box(
modifier
+ .thenIf(isInvisible) { InvisibleModifier }
.zIndex(zIndex)
.approachLayout(
isMeasurementApproachInProgress = { layoutImpl.state.isTransitioning() }
@@ -305,3 +307,8 @@ internal class ContentScopeImpl(
)
}
}
+
+private val InvisibleModifier =
+ Modifier.layout { measurable, constraints ->
+ measurable.measure(constraints).run { layout(width, height) {} }
+ }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Scene.kt
index 7f57798fb1b3..38acd4be80ae 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Scene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Scene.kt
@@ -35,6 +35,7 @@ internal class Scene(
zIndex: Float,
globalZIndex: Long,
effectFactory: OverscrollFactory,
+ val alwaysCompose: Boolean,
) : Content(key, layoutImpl, content, actions, zIndex, globalZIndex, effectFactory) {
override fun toString(): String {
return "Scene(key=$key)"
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
index fa7661b6d102..6538d4340cf3 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
@@ -45,6 +45,7 @@ import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.SemanticsNodeInteraction
import androidx.compose.ui.test.assertHeightIsEqualTo
import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.assertPositionInRootIsEqualTo
import androidx.compose.ui.test.assertWidthIsEqualTo
import androidx.compose.ui.test.junit4.createComposeRule
@@ -64,10 +65,13 @@ import com.android.compose.animation.scene.TestScenes.SceneB
import com.android.compose.animation.scene.TestScenes.SceneC
import com.android.compose.animation.scene.subjects.assertThat
import com.android.compose.test.assertSizeIsEqualTo
+import com.android.compose.test.setContentAndCreateMainScope
import com.android.compose.test.subjects.DpOffsetSubject
import com.android.compose.test.subjects.assertThat
+import com.android.compose.test.transition
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
import org.junit.Assert.assertThrows
import org.junit.Rule
import org.junit.Test
@@ -582,4 +586,34 @@ class SceneTransitionLayoutTest {
assertThat((state2 as MutableSceneTransitionLayoutStateImpl).motionScheme)
.isEqualTo(motionScheme2)
}
+
+ @Test
+ fun alwaysCompose() {
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
+ val scope =
+ rule.setContentAndCreateMainScope {
+ SceneTransitionLayoutForTesting(state) {
+ scene(SceneA) { Box(Modifier.element(TestElements.Foo).size(20.dp)) }
+ scene(SceneB, alwaysCompose = true) {
+ Box(Modifier.element(TestElements.Bar).size(40.dp))
+ }
+ }
+ }
+
+ // Idle(A): Foo is displayed and Bar exists given that SceneB is always composed but it is
+ // not displayed.
+ rule.onNode(isElement(TestElements.Foo)).assertIsDisplayed().assertSizeIsEqualTo(20.dp)
+ rule.onNode(isElement(TestElements.Bar)).assertExists().assertIsNotDisplayed()
+
+ // Transition(A => B): Foo and Bar are both displayed
+ val aToB = transition(SceneA, SceneB)
+ scope.launch { state.startTransition(aToB) }
+ rule.onNode(isElement(TestElements.Foo)).assertIsDisplayed().assertSizeIsEqualTo(20.dp)
+ rule.onNode(isElement(TestElements.Bar)).assertIsDisplayed().assertSizeIsEqualTo(40.dp)
+
+ // Idle(B): Foo does not exist and Bar is displayed.
+ aToB.finish()
+ rule.onNode(isElement(TestElements.Foo)).assertDoesNotExist()
+ rule.onNode(isElement(TestElements.Bar)).assertIsDisplayed().assertSizeIsEqualTo(40.dp)
+ }
}