11package com.flipcash.app.bill.customization.components
22
3+ import androidx.compose.animation.AnimatedContent
34import androidx.compose.animation.ExperimentalSharedTransitionApi
45import androidx.compose.animation.animateBounds
56import androidx.compose.animation.animateColorAsState
7+ import androidx.compose.animation.core.Spring
8+ import androidx.compose.animation.core.VisibilityThreshold
69import androidx.compose.animation.core.animateFloatAsState
10+ import androidx.compose.animation.core.spring
11+ import androidx.compose.animation.fadeOut
12+ import androidx.compose.animation.slideInHorizontally
13+ import androidx.compose.animation.slideOutHorizontally
14+ import androidx.compose.animation.togetherWith
715import androidx.compose.foundation.background
816import androidx.compose.foundation.border
917import androidx.compose.foundation.layout.Arrangement
@@ -29,9 +37,11 @@ import androidx.compose.material.IconButton
2937import androidx.compose.runtime.Composable
3038import androidx.compose.runtime.getValue
3139import androidx.compose.runtime.remember
40+ import androidx.compose.runtime.rememberUpdatedState
3241import androidx.compose.ui.Alignment
3342import androidx.compose.ui.Modifier
3443import androidx.compose.ui.composed
44+ import androidx.compose.ui.draw.clip
3545import androidx.compose.ui.draw.drawWithContent
3646import androidx.compose.ui.geometry.CornerRadius
3747import androidx.compose.ui.geometry.Offset
@@ -44,10 +54,12 @@ import androidx.compose.ui.platform.LocalDensity
4454import androidx.compose.ui.res.painterResource
4555import androidx.compose.ui.tooling.preview.Preview
4656import androidx.compose.ui.unit.Dp
57+ import androidx.compose.ui.unit.IntOffset
4758import androidx.compose.ui.unit.dp
4859import androidx.compose.ui.util.fastForEachIndexed
4960import androidx.lifecycle.compose.collectAsStateWithLifecycle
5061import com.flipcash.app.bill.customization.Event
62+ import com.flipcash.app.bill.customization.PlaygroundMode
5163import com.flipcash.app.bill.customization.internal.InternalBillPlaygroundController
5264import com.flipcash.app.theme.FlipcashDesignSystem
5365import com.flipcash.features.bill.playground.R
@@ -64,6 +76,7 @@ import com.getcode.ui.utils.hexToColor
6476@OptIn(ExperimentalSharedTransitionApi ::class )
6577@Composable
6678internal fun BillPlayground (
79+ mode : PlaygroundMode ,
6780 selectedSlot : Int ,
6881 maxSlots : Int ,
6982 colorOptions : List <BillBackground >,
@@ -159,96 +172,162 @@ internal fun BillPlayground(
159172 }
160173 }
161174
162- // color bar
163- LazyHorizontalGrid (
164- modifier = Modifier
165- .fillMaxWidth()
166- .height(112 .dp),
167- rows = GridCells .Fixed (2 ),
168- horizontalArrangement = Arrangement .spacedBy(CodeTheme .dimens.grid.x2),
169- verticalArrangement = Arrangement .spacedBy(CodeTheme .dimens.grid.x2),
170- contentPadding = PaddingValues (horizontal = CodeTheme .dimens.grid.x5)
171- ) {
172- item(
173- span = { GridItemSpan (maxLineSpan) }
174- ) {
175- // hue control trigger
176- Box (
177- modifier = Modifier
178- .width(50 .dp) // Set a fixed width matching other items
179- .fillMaxHeight() // Fill the available height of the grid
180- .rainbowBackground()
181- .padding(CodeTheme .dimens.thickBorder)
182- .background(
183- color = Color .Black .copy(0.50f ),
184- shape = CodeTheme .shapes.small
175+ val selectedSlotColor by rememberUpdatedState(selectedColors[selectedSlot])
176+
177+ // color options
178+ AnimatedContent (
179+ targetState = mode,
180+ transitionSpec = {
181+ if (targetState == PlaygroundMode .ColorPanel ) {
182+ // ColorPanel is entering, slide in from the left
183+ slideInHorizontally(
184+ spring(
185+ dampingRatio = 0.9f ,
186+ stiffness = Spring .StiffnessMediumLow ,
187+ visibilityThreshold = IntOffset .VisibilityThreshold ,
185188 )
186- .padding(CodeTheme .dimens.grid.x3),
187- contentAlignment = Alignment .Center
188- ) {
189- Icon (
190- painter = painterResource(R .drawable.ic_color_tune_hsl),
191- contentDescription = " Color manipulation" ,
192- tint = Color .White
193- )
189+ ) { width -> - width } togetherWith
190+ slideOutHorizontally { width -> width } + fadeOut()
191+ } else { // targetState == PlaygroundMode.Presets
192+ // ColorPanel is exiting, slide out to the left
193+ slideInHorizontally(
194+ spring(
195+ dampingRatio = 0.9f ,
196+ stiffness = Spring .StiffnessMediumLow ,
197+ visibilityThreshold = IntOffset .VisibilityThreshold ,
198+ ),
199+ ) { width -> width } togetherWith
200+ slideOutHorizontally { width -> - width } + fadeOut()
194201 }
195202 }
196-
197- items(colorOptions.size) { index ->
198- val numRows = 2
199- val itemsPerRow = (colorOptions.size + numRows - 1 ) / numRows
200- val col = index / numRows
201- val row = index % numRows
202- val newIndex = if (row == 0 ) {
203- col
204- } else {
205- itemsPerRow + col
203+ ) { mode ->
204+ when (mode) {
205+ PlaygroundMode .ColorPanel -> {
206+ ColorPanel (
207+ selectedColor = selectedSlotColor,
208+ modifier = Modifier
209+ .fillMaxWidth()
210+ .height(CodeTheme .dimens.grid.x14 * 2 )
211+ .padding(horizontal = CodeTheme .dimens.grid.x5)
212+ .padding(vertical = CodeTheme .dimens.grid.x3),
213+ onChange = { dispatchEvent(Event .ChangeColor (it)) },
214+ onClose = {
215+ dispatchEvent(Event .CloseHueControls )
216+ }
217+ )
206218 }
207- if (newIndex < colorOptions.size) {
208- val option = colorOptions[newIndex]
209- Box (
219+ PlaygroundMode .Presets -> {
220+ LazyHorizontalGrid (
210221 modifier = Modifier
211- .size(50 .dp)
212- .presenceBorder()
213- .addIf(option is BillBackground .Solid ) {
214- Modifier .background(
215- color = hexToColor((option as BillBackground .Solid ).colorHex),
216- shape = CodeTheme .shapes.small
217- )
218- }
219- .addIf(option is BillBackground .Gradient ) {
220- val colors =
221- (option as BillBackground .Gradient ).colors.map { hexToColor(it) }
222- Modifier .background(
223- brush = Brush .verticalGradient(
224- colors = colors,
225- ),
226- shape = CodeTheme .shapes.small
222+ .fillMaxWidth()
223+ .height(CodeTheme .dimens.grid.x14 * 2 )
224+ .padding(vertical = CodeTheme .dimens.grid.x3),
225+ rows = GridCells .Fixed (2 ),
226+ horizontalArrangement = Arrangement .spacedBy(CodeTheme .dimens.grid.x2),
227+ verticalArrangement = Arrangement .spacedBy(CodeTheme .dimens.grid.x2),
228+ contentPadding = PaddingValues (horizontal = CodeTheme .dimens.grid.x5)
229+ ) {
230+ item(
231+ span = { GridItemSpan (maxLineSpan) }
232+ ) {
233+ Box (
234+ modifier = Modifier
235+ .width(CodeTheme .dimens.grid.x10)
236+ .fillMaxHeight()
237+ .rainbowBackground()
238+ .padding(CodeTheme .dimens.thickBorder)
239+ .background(
240+ color = Color .Black .copy(0.50f ),
241+ shape = CodeTheme .shapes.small
242+ )
243+ .clip(CodeTheme .shapes.small)
244+ .rememberedClickable {
245+ dispatchEvent(Event .OpenHueControls )
246+ }
247+ .padding(CodeTheme .dimens.grid.x3),
248+ contentAlignment = Alignment .Center
249+ ) {
250+ Icon (
251+ painter = painterResource(R .drawable.ic_color_tune_hsl),
252+ contentDescription = " Color manipulation" ,
253+ tint = Color .White
227254 )
228255 }
229- .rememberedClickable {
230- when (option) {
231- is BillBackground .Gradient -> dispatchEvent(
232- Event .LoadBackground (
233- option
234- )
235- )
256+ }
236257
237- is BillBackground .Solid -> dispatchEvent(
238- Event .ChangeColor (
239- hexToColor(option.colorHex)
240- )
241- )
242- }
258+ items(colorOptions.size) { index ->
259+ ColorOptionItem (colorOptions, index) { event ->
260+ dispatchEvent(event)
243261 }
244- )
262+ }
263+ }
245264 }
246265 }
247266 }
248267 }
249268}
250269
251- private fun Modifier.presenceBorder (
270+ @Composable
271+ private fun ColorOptionItem (
272+ colorOptions : List <BillBackground >,
273+ index : Int ,
274+ dispatchEvent : (Event ) -> Unit
275+ ) {
276+ val numRows = 2
277+ val itemsPerRow = (colorOptions.size + numRows - 1 ) / numRows
278+ val col = index / numRows
279+ val row = index % numRows
280+ val newIndex = if (row == 0 ) {
281+ col
282+ } else {
283+ itemsPerRow + col
284+ }
285+ if (newIndex < colorOptions.size) {
286+ val option = colorOptions[newIndex]
287+ Box (
288+ modifier = Modifier
289+ .width(CodeTheme .dimens.grid.x10)
290+ .presenceBorder()
291+ .addIf(option is BillBackground .Solid ) {
292+ Modifier .background(
293+ color = hexToColor((option as BillBackground .Solid ).colorHex),
294+ shape = CodeTheme .shapes.small
295+ )
296+ }
297+ .addIf(option is BillBackground .Gradient ) {
298+ val colors =
299+ (option as BillBackground .Gradient ).colors.map {
300+ hexToColor(
301+ it
302+ )
303+ }
304+ Modifier .background(
305+ brush = Brush .verticalGradient(
306+ colors = colors,
307+ ),
308+ shape = CodeTheme .shapes.small
309+ )
310+ }
311+ .rememberedClickable {
312+ when (option) {
313+ is BillBackground .Gradient -> dispatchEvent(
314+ Event .LoadBackground (
315+ option
316+ )
317+ )
318+
319+ is BillBackground .Solid -> dispatchEvent(
320+ Event .ChangeColor (
321+ hexToColor(option.colorHex)
322+ )
323+ )
324+ }
325+ }
326+ )
327+ }
328+ }
329+
330+ internal fun Modifier.presenceBorder (
252331 width : Dp = 2.dp,
253332 color : Color = Color .White .copy(0.30f)
254333): Modifier = this .composed {
@@ -342,6 +421,7 @@ private fun PreviewCustomizationControls() {
342421 .presenceBorder(),
343422 )
344423 BillPlayground (
424+ mode = state.mode,
345425 selectedSlot = state.selectedSlot,
346426 maxSlots = state.maxSlots,
347427 selectedColors = state.selectedColors,
0 commit comments