1 /*
2  * Copyright (C) 2024 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 package com.android.compose.animation.scene.demo
18 
19 import androidx.compose.animation.core.tween
20 import androidx.compose.foundation.background
21 import androidx.compose.foundation.border
22 import androidx.compose.foundation.clickable
23 import androidx.compose.foundation.layout.Box
24 import androidx.compose.foundation.layout.Column
25 import androidx.compose.foundation.layout.fillMaxSize
26 import androidx.compose.foundation.layout.padding
27 import androidx.compose.foundation.layout.size
28 import androidx.compose.foundation.shape.CircleShape
29 import androidx.compose.runtime.Composable
30 import androidx.compose.runtime.remember
31 import androidx.compose.runtime.rememberCoroutineScope
32 import androidx.compose.ui.Alignment
33 import androidx.compose.ui.Modifier
34 import androidx.compose.ui.graphics.Color
35 import androidx.compose.ui.unit.dp
36 import com.android.compose.animation.scene.ContentScope
37 import com.android.compose.animation.scene.ElementKey
38 import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
39 import com.android.compose.animation.scene.SceneKey
40 import com.android.compose.animation.scene.SceneScope
41 import com.android.compose.animation.scene.SceneTransitionLayout
42 import com.android.compose.animation.scene.transitions
43 
44 object ParentSTL {
45     object Scenes {
46         val Left = SceneKey("Left")
47         val Right = SceneKey("Right")
48     }
49 }
50 
51 object ChildSTL {
52     object Scenes {
53         val Top = SceneKey("Red")
54         val Bottom = SceneKey("Red")
55     }
56 }
57 
58 object Elements {
59     val Shared = ElementKey("Shared")
60 }
61 
62 @Composable
NestedSharedElementDemonull63 fun NestedSharedElementDemo(modifier: Modifier = Modifier) {
64     Column(modifier) {
65         val state = remember {
66             MutableSceneTransitionLayoutState(
67                 ParentSTL.Scenes.Left,
68                 transitions {
69                     from(ParentSTL.Scenes.Left, to = ParentSTL.Scenes.Right) { spec = tween(500) }
70                 },
71             )
72         }
73         val childState = remember {
74             MutableSceneTransitionLayoutState(
75                 ChildSTL.Scenes.Top,
76                 transitions {
77                     from(ChildSTL.Scenes.Top, to = ChildSTL.Scenes.Bottom) { spec = tween(500) }
78                 },
79             )
80         }
81         val scope = rememberCoroutineScope()
82         SceneTransitionLayout(
83             state,
84             Modifier.padding(16.dp)
85                 .border(3.dp, Color.Blue)
86                 .clickable {
87                     val targetScene =
88                         when (state.currentScene) {
89                             ParentSTL.Scenes.Left -> ParentSTL.Scenes.Right
90                             else -> ParentSTL.Scenes.Left
91                         }
92                     state.setTargetScene(targetScene, scope)
93                 }
94                 .padding(16.dp),
95         ) {
96             scene(ParentSTL.Scenes.Right) {
97                 Box(Modifier.fillMaxSize()) {
98                     SharedElement(Modifier.size(30.dp).align(Alignment.TopEnd))
99                 }
100             }
101 
102             scene(ParentSTL.Scenes.Left) {
103                 Box(Modifier.fillMaxSize()) {
104                     ChildSTL(
105                         childState,
106                         Modifier.align(Alignment.Center).fillMaxSize(fraction = 0.5f),
107                     )
108                 }
109             }
110         }
111     }
112 }
113 
114 @Composable
ContentScopenull115 private fun ContentScope.ChildSTL(
116     state: MutableSceneTransitionLayoutState,
117     modifier: Modifier = Modifier,
118 ) {
119     val scope = rememberCoroutineScope()
120     NestedSceneTransitionLayout(
121         state,
122         modifier.border(3.dp, Color.Red).clickable {
123             val targetScene =
124                 when (state.currentScene) {
125                     ChildSTL.Scenes.Top -> ChildSTL.Scenes.Bottom
126                     else -> ChildSTL.Scenes.Top
127                 }
128             state.setTargetScene(targetScene, scope)
129         },
130     ) {
131         scene(ChildSTL.Scenes.Top) {
132             Box(Modifier.fillMaxSize()) { SharedElement(Modifier.size(100.dp)) }
133         }
134         scene(ChildSTL.Scenes.Bottom) {
135             Box(Modifier.fillMaxSize()) {
136                 SharedElement(Modifier.align(Alignment.BottomStart).size(60.dp))
137             }
138         }
139     }
140 }
141 
142 @Composable
SharedElementnull143 private fun SceneScope.SharedElement(modifier: Modifier = Modifier) {
144     Box(modifier.element(Elements.Shared).background(Color.Green, CircleShape))
145 }
146