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