• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2025 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(ExperimentalAnimatableApi::class)
18 
19 package com.android.mechanics.demo.presentation
20 
21 import androidx.compose.animation.core.ExperimentalAnimatableApi
22 import androidx.compose.animation.core.tween
23 import androidx.compose.foundation.background
24 import androidx.compose.foundation.layout.Arrangement
25 import androidx.compose.foundation.layout.Column
26 import androidx.compose.foundation.layout.ColumnScope
27 import androidx.compose.foundation.layout.Row
28 import androidx.compose.foundation.layout.Spacer
29 import androidx.compose.foundation.layout.fillMaxWidth
30 import androidx.compose.foundation.layout.height
31 import androidx.compose.foundation.layout.padding
32 import androidx.compose.foundation.rememberScrollState
33 import androidx.compose.foundation.verticalScroll
34 import androidx.compose.material.icons.Icons
35 import androidx.compose.material.icons.filled.AllInclusive
36 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
37 import androidx.compose.material3.Icon
38 import androidx.compose.material3.LocalContentColor
39 import androidx.compose.material3.MaterialTheme
40 import androidx.compose.material3.MaterialTheme.typography
41 import androidx.compose.material3.Text
42 import androidx.compose.runtime.Composable
43 import androidx.compose.runtime.CompositionLocalProvider
44 import androidx.compose.runtime.getValue
45 import androidx.compose.runtime.mutableStateOf
46 import androidx.compose.runtime.remember
47 import androidx.compose.runtime.setValue
48 import androidx.compose.ui.Modifier
49 import androidx.compose.ui.unit.Dp
50 import androidx.compose.ui.unit.dp
51 import com.android.compose.animation.scene.ElementKey
52 import com.android.compose.animation.scene.transitions
53 import com.android.compose.modifiers.thenIf
54 import com.android.mechanics.demo.staging.behavior.magneticDetach
55 import com.android.mechanics.demo.staging.behavior.reveal.FadeContentRevealSpec
56 import com.android.mechanics.demo.staging.behavior.reveal.fadeReveal
57 import com.android.mechanics.demo.staging.behavior.reveal.rememberFadeContentRevealSpec
58 import com.android.mechanics.demo.staging.behavior.reveal.revealContainer
59 import com.android.mechanics.demo.tuneable.Demo
60 import com.android.mechanics.demo.tuneable.DpSlider
61 import com.android.mechanics.demo.tuneable.GuaranteeSection
62 import com.android.mechanics.demo.tuneable.LabelledCheckbox
63 import com.android.mechanics.demo.tuneable.Section
64 import com.android.mechanics.demo.tuneable.SpringParameterSection
65 import com.android.mechanics.demo.util.ExpandableCard
66 import com.android.mechanics.demo.util.Scenes
67 import com.android.mechanics.demo.util.Transitions.ExpandedCollapsedDistance
68 
69 object PracticalDemoDetach : Demo<PracticalDemoDetach.Config> {
70     object Elements {
71         val ExpandableContent = ElementKey("ExpandableContent")
72     }
73 
74     data class Config(val fadeSpec: FadeContentRevealSpec, val showItemBackground: Boolean)
75 
76     @Composable
77     override fun DemoUi(config: Config, modifier: Modifier) {
78         val colors = MaterialTheme.colorScheme
79         ExpandableCard(
80             transitions =
81                 transitions {
82                     from(Scenes.Expanded, Scenes.Collapsed) {
83                         spec = tween(500)
84                         distance = ExpandedCollapsedDistance
85 
86                         magneticDetach(Elements.ExpandableContent)
87                     }
88                 },
89             modifier = modifier.fillMaxWidth(),
90             header = { Text(text = "Contents", style = typography.titleMedium) },
91         ) { isExpanded ->
92             Column(
93                 verticalArrangement = Arrangement.spacedBy(8.dp),
94                 modifier =
95                     Modifier.fillMaxWidth()
96                         .element(Elements.ExpandableContent)
97                         .revealContainer(this@ExpandableCard)
98                         .verticalScroll(rememberScrollState())
99                         .padding(start = 16.dp, end = 16.dp, bottom = 16.dp),
100             ) {
101                 if (isExpanded) {
102                     Spacer(modifier = Modifier.height(24.dp))
103                     repeat(10) {
104                         Row(
105                             horizontalArrangement = Arrangement.spacedBy(8.dp),
106                             modifier =
107                                 Modifier.noResizeDuringTransitions()
108                                     .fadeReveal(spec = config.fadeSpec, debug = true)
109                                     .fillMaxWidth()
110                                     .thenIf(config.showItemBackground) {
111                                         Modifier.background(colors.primary)
112                                     },
113                         ) {
114                             CompositionLocalProvider(
115                                 LocalContentColor provides
116                                     if (config.showItemBackground) colors.onPrimary
117                                     else colors.onSurface
118                             ) {
119                                 Icon(Icons.Default.AllInclusive, null)
120                                 Text(text = "Item ${it + 1}", modifier = Modifier.height(20.dp))
121                             }
122                         }
123                     }
124                 }
125             }
126         }
127     }
128 
129     @OptIn(ExperimentalMaterial3ExpressiveApi::class)
130     @Composable
131     override fun rememberDefaultConfig(): Config {
132         val fadeRevealSpec = rememberFadeContentRevealSpec(showDelta = 8.dp, hideDelta = 16.dp)
133         return remember(fadeRevealSpec) { Config(fadeRevealSpec, showItemBackground = false) }
134     }
135 
136     override var visualizationInputRange by mutableStateOf(0f..1000f)
137     override val collapsedGraphHeight: Dp = 20.dp
138 
139     @Composable
140     override fun ColumnScope.ConfigUi(config: Config, onConfigChanged: (Config) -> Unit) {
141         LabelledCheckbox(
142             "Show item background",
143             config.showItemBackground,
144             onCheckedChange = { onConfigChanged(config.copy(showItemBackground = it)) },
145             modifier = Modifier.fillMaxWidth(),
146         )
147 
148         Section(
149             "Show",
150             summary = { "" },
151             value = config.fadeSpec,
152             onValueChanged = { onConfigChanged(config.copy(fadeSpec = it)) },
153             sectionKey = "show spec",
154             modifier = Modifier.fillMaxWidth(),
155         ) { spec, onSpecChanged ->
156             SpringParameterSection(
157                 " Spring",
158                 spec.showSpring,
159                 onValueChanged = { onSpecChanged(spec.copy(showSpring = it)) },
160                 "showspring",
161                 modifier = Modifier.fillMaxWidth(),
162             )
163             GuaranteeSection(
164                 " Guarantee",
165                 spec.showGuarantee,
166                 { onSpecChanged(spec.copy(showGuarantee = it)) },
167                 "showguarantee",
168                 modifier = Modifier.fillMaxWidth(),
169             )
170 
171             Text(text = "Delta", modifier = Modifier.padding(start = 8.dp))
172             DpSlider(spec.showDelta, { onSpecChanged(spec.copy(showDelta = it)) }, 0.dp..24.dp)
173         }
174 
175         Section(
176             "Hide",
177             summary = { "" },
178             value = config.fadeSpec,
179             onValueChanged = { onConfigChanged(config.copy(fadeSpec = it)) },
180             sectionKey = "show spec",
181             modifier = Modifier.fillMaxWidth(),
182         ) { spec, onSpecChanged ->
183             SpringParameterSection(
184                 "Spring",
185                 spec.hideSpring,
186                 onValueChanged = { onSpecChanged(spec.copy(hideSpring = it)) },
187                 "hidespring",
188                 modifier = Modifier.fillMaxWidth(),
189             )
190             GuaranteeSection(
191                 "Guarantee",
192                 spec.hideGuarantee,
193                 { onSpecChanged(spec.copy(hideGuarantee = it)) },
194                 "hideguarantee",
195                 modifier = Modifier.fillMaxWidth(),
196             )
197 
198             Text(text = "Delta", modifier = Modifier.padding(start = 8.dp))
199             DpSlider(spec.hideDelta, { onSpecChanged(spec.copy(hideDelta = it)) }, 0.dp..24.dp)
200         }
201     }
202 
203     override val identifier: String = "PracticalDemoDetach"
204 }
205