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