1 /*
<lambda>null2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 @file:OptIn(ExperimentalComposeUiApi::class)
18 
19 package com.android.systemui.scene.ui.composable
20 
21 import androidx.compose.foundation.layout.Box
22 import androidx.compose.foundation.layout.fillMaxSize
23 import androidx.compose.material3.Text
24 import androidx.compose.runtime.Composable
25 import androidx.compose.runtime.DisposableEffect
26 import androidx.compose.runtime.getValue
27 import androidx.compose.runtime.remember
28 import androidx.compose.runtime.rememberCoroutineScope
29 import androidx.compose.ui.Alignment
30 import androidx.compose.ui.ExperimentalComposeUiApi
31 import androidx.compose.ui.Modifier
32 import androidx.compose.ui.graphics.Color
33 import androidx.lifecycle.compose.collectAsStateWithLifecycle
34 import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
35 import com.android.compose.animation.scene.SceneKey
36 import com.android.compose.animation.scene.SceneTransitionLayout
37 import com.android.compose.animation.scene.observableTransitionState
38 import com.android.systemui.ribbon.ui.composable.BottomRightCornerRibbon
39 import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
40 import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
41 
42 /**
43  * Renders a container of a collection of "scenes" that the user can switch between using certain
44  * user actions (for instance, swiping up and down) or that can be switched automatically based on
45  * application business logic in response to certain events (for example, the device unlocking).
46  *
47  * It's possible for the application to host several such scene containers, the configuration system
48  * allows configuring each container with its own set of scenes. Scenes can be present in multiple
49  * containers.
50  *
51  * @param viewModel The UI state holder for this container.
52  * @param sceneByKey Mapping of [ComposableScene] by [SceneKey], ordered by z-order such that the
53  *   last scene is rendered on top of all other scenes. It's critical that this map contains exactly
54  *   and only the scenes on this container. In other words: (a) there should be no scene in this map
55  *   that is not in the configuration for this container and (b) all scenes in the configuration
56  *   must have entries in this map.
57  * @param modifier A modifier.
58  */
59 @OptIn(ExperimentalComposeUiApi::class)
60 @Composable
61 fun SceneContainer(
62     viewModel: SceneContainerViewModel,
63     sceneByKey: Map<SceneKey, ComposableScene>,
64     dataSourceDelegator: SceneDataSourceDelegator,
65     modifier: Modifier = Modifier,
66 ) {
67     val coroutineScope = rememberCoroutineScope()
68     val currentSceneKey: SceneKey by viewModel.currentScene.collectAsStateWithLifecycle()
69     val currentDestinations by
70         viewModel.currentDestinationScenes(coroutineScope).collectAsStateWithLifecycle()
71     val state: MutableSceneTransitionLayoutState = remember {
72         MutableSceneTransitionLayoutState(
73             initialScene = currentSceneKey,
74             canChangeScene = { toScene -> viewModel.canChangeScene(toScene) },
75             transitions = SceneContainerTransitions,
76             enableInterruptions = false,
77         )
78     }
79 
80     DisposableEffect(state) {
81         val dataSource = SceneTransitionLayoutDataSource(state, coroutineScope)
82         dataSourceDelegator.setDelegate(dataSource)
83         onDispose { dataSourceDelegator.setDelegate(null) }
84     }
85 
86     DisposableEffect(viewModel, state) {
87         viewModel.setTransitionState(state.observableTransitionState())
88         onDispose { viewModel.setTransitionState(null) }
89     }
90 
91     Box(
92         modifier = Modifier.fillMaxSize(),
93     ) {
94         SceneTransitionLayout(state = state, modifier = modifier.fillMaxSize()) {
95             sceneByKey.forEach { (sceneKey, composableScene) ->
96                 scene(
97                     key = sceneKey,
98                     userActions =
99                         if (sceneKey == currentSceneKey) {
100                             currentDestinations
101                         } else {
102                             viewModel.resolveSceneFamilies(composableScene.destinationScenes.value)
103                         },
104                 ) {
105                     with(composableScene) {
106                         this@scene.Content(
107                             modifier = Modifier.element(sceneKey.rootElementKey).fillMaxSize(),
108                         )
109                     }
110                 }
111             }
112         }
113 
114         BottomRightCornerRibbon(
115             content = {
116                 Text(
117                     text = "flexi\uD83E\uDD43",
118                     color = Color.White,
119                 )
120             },
121             modifier = Modifier.align(Alignment.BottomEnd),
122         )
123     }
124 }
125