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

Commit e3475620 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "[flexiglass] Add SceneTransitionLayout (STL) overlays to the framework." into main

parents c51b1ad0 fa9866d1
Loading
Loading
Loading
Loading
+37 −0
Original line number Diff line number Diff line
/*
 * 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.systemui.scene.ui.composable

import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.android.compose.animation.scene.ContentScope
import com.android.compose.animation.scene.OverlayKey
import com.android.systemui.lifecycle.Activatable

/**
 * Defines interface for classes that can describe an "overlay".
 *
 * In the scene framework, there can be multiple overlays in a single scene "container". The
 * container takes care of rendering any current overlays and allowing overlays to be shown, hidden,
 * or replaced based on a user action.
 */
interface Overlay : Activatable {
    /** Uniquely-identifying key for this overlay. The key must be unique within its container. */
    val key: OverlayKey

    @Composable fun ContentScope.Content(modifier: Modifier)
}
+26 −6
Original line number Diff line number Diff line
@@ -31,7 +31,9 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
import com.android.compose.animation.scene.OverlayKey
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayout
import com.android.compose.animation.scene.UserAction
@@ -57,12 +59,18 @@ import kotlinx.coroutines.flow.collectLatest
 *   and only the scenes on this container. In other words: (a) there should be no scene in this map
 *   that is not in the configuration for this container and (b) all scenes in the configuration
 *   must have entries in this map.
 * @param overlayByKey Mapping of [Overlay] by [OverlayKey], ordered by z-order such that the last
 *   overlay is rendered on top of all other overlays. It's critical that this map contains exactly
 *   and only the overlays on this container. In other words: (a) there should be no overlay in this
 *   map that is not in the configuration for this container and (b) all overlays in the
 *   configuration must have entries in this map.
 * @param modifier A modifier.
 */
@Composable
fun SceneContainer(
    viewModel: SceneContainerViewModel,
    sceneByKey: Map<SceneKey, ComposableScene>,
    overlayByKey: Map<OverlayKey, Overlay>,
    initialSceneKey: SceneKey,
    dataSourceDelegator: SceneDataSourceDelegator,
    modifier: Modifier = Modifier,
@@ -89,16 +97,19 @@ fun SceneContainer(
        onDispose { viewModel.setTransitionState(null) }
    }

    val userActionsBySceneKey: MutableMap<SceneKey, Map<UserAction, UserActionResult>> = remember {
    val userActionsByContentKey: MutableMap<ContentKey, Map<UserAction, UserActionResult>> =
        remember {
            mutableStateMapOf()
        }
    // TODO(b/359173565): Add overlay user actions when the API is final.
    LaunchedEffect(currentSceneKey) {
        try {
            sceneByKey[currentSceneKey]?.destinationScenes?.collectLatest { userActions ->
                userActionsBySceneKey[currentSceneKey] = viewModel.resolveSceneFamilies(userActions)
                userActionsByContentKey[currentSceneKey] =
                    viewModel.resolveSceneFamilies(userActions)
            }
        } finally {
            userActionsBySceneKey[currentSceneKey] = emptyMap()
            userActionsByContentKey[currentSceneKey] = emptyMap()
        }
    }

@@ -115,7 +126,7 @@ fun SceneContainer(
            sceneByKey.forEach { (sceneKey, composableScene) ->
                scene(
                    key = sceneKey,
                    userActions = userActionsBySceneKey.getOrDefault(sceneKey, emptyMap())
                    userActions = userActionsByContentKey.getOrDefault(sceneKey, emptyMap())
                ) {
                    // Activate the scene.
                    LaunchedEffect(composableScene) { composableScene.activate() }
@@ -128,6 +139,15 @@ fun SceneContainer(
                    }
                }
            }
            overlayByKey.forEach { (overlayKey, composableOverlay) ->
                overlay(
                    key = overlayKey,
                    userActions = userActionsByContentKey.getOrDefault(overlayKey, emptyMap())
                ) {
                    // Render the overlay.
                    with(composableOverlay) { this@overlay.Content(Modifier) }
                }
            }
        }

        BottomRightCornerRibbon(
+35 −0
Original line number Diff line number Diff line
@@ -18,7 +18,9 @@

package com.android.systemui.scene.ui.composable

import androidx.compose.runtime.snapshotFlow
import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
import com.android.compose.animation.scene.OverlayKey
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.TransitionKey
import com.android.compose.animation.scene.observableTransitionState
@@ -52,6 +54,14 @@ class SceneTransitionLayoutDataSource(
                initialValue = state.transitionState.currentScene,
            )

    override val currentOverlays: StateFlow<Set<OverlayKey>> =
        snapshotFlow { state.currentOverlays }
            .stateIn(
                scope = coroutineScope,
                started = SharingStarted.WhileSubscribed(),
                initialValue = emptySet(),
            )

    override fun changeScene(
        toScene: SceneKey,
        transitionKey: TransitionKey?,
@@ -68,4 +78,29 @@ class SceneTransitionLayoutDataSource(
            scene = toScene,
        )
    }

    override fun showOverlay(overlay: OverlayKey, transitionKey: TransitionKey?) {
        state.showOverlay(
            overlay = overlay,
            animationScope = coroutineScope,
            transitionKey = transitionKey,
        )
    }

    override fun hideOverlay(overlay: OverlayKey, transitionKey: TransitionKey?) {
        state.hideOverlay(
            overlay = overlay,
            animationScope = coroutineScope,
            transitionKey = transitionKey,
        )
    }

    override fun replaceOverlay(from: OverlayKey, to: OverlayKey, transitionKey: TransitionKey?) {
        state.replaceOverlay(
            from = from,
            to = to,
            animationScope = coroutineScope,
            transitionKey = transitionKey,
        )
    }
}
+15 −12
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.overlayKeys
import com.android.systemui.scene.sceneContainerConfig
import com.android.systemui.scene.sceneKeys
import com.android.systemui.scene.shared.model.Scenes
@@ -46,19 +47,9 @@ class SceneContainerRepositoryTest : SysuiTestCase() {
    private val testScope = kosmos.testScope

    @Test
    fun allSceneKeys() {
    fun allContentKeys() {
        val underTest = kosmos.sceneContainerRepository
        assertThat(underTest.allSceneKeys())
            .isEqualTo(
                listOf(
                    Scenes.QuickSettings,
                    Scenes.Shade,
                    Scenes.Lockscreen,
                    Scenes.Bouncer,
                    Scenes.Gone,
                    Scenes.Communal,
                )
            )
        assertThat(underTest.allContentKeys).isEqualTo(kosmos.sceneKeys + kosmos.overlayKeys)
    }

    @Test
@@ -75,6 +66,18 @@ class SceneContainerRepositoryTest : SysuiTestCase() {
            assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
        }

    // TODO(b/356596436): Add tests for showing, hiding, and replacing overlays after we've defined
    //  them.
    @Test
    fun currentOverlays() =
        testScope.runTest {
            val underTest = kosmos.sceneContainerRepository
            val currentOverlays by collectLastValue(underTest.currentOverlays)
            assertThat(currentOverlays).isEmpty()

            // TODO(b/356596436): When we have a first overlay, add it here and assert contains.
        }

    @Test(expected = IllegalStateException::class)
    fun changeScene_noSuchSceneInContainer_throws() {
        kosmos.sceneKeys = listOf(Scenes.QuickSettings, Scenes.Lockscreen)
+5 −2
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import com.android.systemui.scene.data.repository.Transition
import com.android.systemui.scene.data.repository.sceneContainerRepository
import com.android.systemui.scene.data.repository.setSceneTransition
import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver
import com.android.systemui.scene.overlayKeys
import com.android.systemui.scene.sceneContainerConfig
import com.android.systemui.scene.sceneKeys
import com.android.systemui.scene.shared.model.SceneFamilies
@@ -72,9 +73,11 @@ class SceneInteractorTest : SysuiTestCase() {
        kosmos.keyguardEnabledInteractor
    }

    // TODO(b/356596436): Add tests for showing, hiding, and replacing overlays after we've defined
    //  them.
    @Test
    fun allSceneKeys() {
        assertThat(underTest.allSceneKeys()).isEqualTo(kosmos.sceneKeys)
    fun allContentKeys() {
        assertThat(underTest.allContentKeys).isEqualTo(kosmos.sceneKeys + kosmos.overlayKeys)
    }

    @Test
Loading