@@ -2,11 +2,8 @@ package com.flipcash.app.bill.customization.components
22
33import androidx.compose.animation.AnimatedContent
44import androidx.compose.animation.ExperimentalSharedTransitionApi
5- import androidx.compose.animation.animateBounds
6- import androidx.compose.animation.animateColorAsState
75import androidx.compose.animation.core.Spring
86import androidx.compose.animation.core.VisibilityThreshold
9- import androidx.compose.animation.core.animateFloatAsState
107import androidx.compose.animation.core.spring
118import androidx.compose.animation.fadeOut
129import androidx.compose.animation.slideInHorizontally
@@ -18,61 +15,40 @@ import androidx.compose.foundation.layout.Arrangement
1815import androidx.compose.foundation.layout.Box
1916import androidx.compose.foundation.layout.Column
2017import androidx.compose.foundation.layout.PaddingValues
21- import androidx.compose.foundation.layout.Row
2218import androidx.compose.foundation.layout.aspectRatio
23- import androidx.compose.foundation.layout.fillMaxHeight
2419import androidx.compose.foundation.layout.fillMaxSize
2520import androidx.compose.foundation.layout.fillMaxWidth
2621import androidx.compose.foundation.layout.height
2722import androidx.compose.foundation.layout.navigationBarsPadding
2823import androidx.compose.foundation.layout.padding
29- import androidx.compose.foundation.layout.size
30- import androidx.compose.foundation.layout.width
3124import androidx.compose.foundation.lazy.grid.GridCells
3225import androidx.compose.foundation.lazy.grid.GridItemSpan
3326import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
34- import androidx.compose.material.ContentAlpha
35- import androidx.compose.material.Icon
36- import androidx.compose.material.IconButton
3727import androidx.compose.runtime.Composable
3828import androidx.compose.runtime.getValue
3929import androidx.compose.runtime.remember
4030import androidx.compose.runtime.rememberUpdatedState
4131import androidx.compose.ui.Alignment
4232import androidx.compose.ui.Modifier
4333import androidx.compose.ui.composed
44- import androidx.compose.ui.draw.clip
45- import androidx.compose.ui.draw.drawWithContent
46- import androidx.compose.ui.geometry.CornerRadius
47- import androidx.compose.ui.geometry.Offset
48- import androidx.compose.ui.graphics.Brush
4934import androidx.compose.ui.graphics.Color
50- import androidx.compose.ui.graphics.SolidColor
51- import androidx.compose.ui.layout.LookaheadScope
5235import androidx.compose.ui.platform.LocalContext
53- import androidx.compose.ui.platform.LocalDensity
54- import androidx.compose.ui.res.painterResource
5536import androidx.compose.ui.tooling.preview.Preview
5637import androidx.compose.ui.unit.Dp
5738import androidx.compose.ui.unit.IntOffset
5839import androidx.compose.ui.unit.dp
59- import androidx.compose.ui.util.fastForEachIndexed
6040import androidx.lifecycle.compose.collectAsStateWithLifecycle
61- import com.flipcash.app.bill.customization.ColorChange
41+ import com.flipcash.app.bill.customization.ColorStore
6242import com.flipcash.app.bill.customization.Event
6343import com.flipcash.app.bill.customization.PlaygroundMode
6444import com.flipcash.app.bill.customization.internal.InternalBillPlaygroundController
6545import com.flipcash.app.theme.FlipcashDesignSystem
66- import com.flipcash.features.bill.playground.R
6746import com.getcode.opencode.compose.ExchangeStub
6847import com.getcode.opencode.model.financial.BillBackground
6948import com.getcode.opencode.model.financial.CurrencyCode
7049import com.getcode.opencode.model.financial.Rate
7150import com.getcode.theme.CodeTheme
7251import com.getcode.ui.components.Pill
73- import com.getcode.ui.core.addIf
74- import com.getcode.ui.core.rememberedClickable
75- import com.getcode.ui.utils.hexToColor
7652
7753@OptIn(ExperimentalSharedTransitionApi ::class )
7854@Composable
@@ -81,7 +57,7 @@ internal fun BillPlayground(
8157 selectedSlot : Int ,
8258 maxSlots : Int ,
8359 colorOptions : List <BillBackground >,
84- selectedColors : List <Color >,
60+ selectedColors : List <ColorStore >,
8561 dispatchEvent : (Event ) -> Unit ,
8662) {
8763 Column (
@@ -101,79 +77,17 @@ internal fun BillPlayground(
10177 )
10278
10379 // color selections
104- Row (
80+ ColorSlots (
10581 modifier = Modifier
10682 .fillMaxWidth()
10783 .padding(horizontal = CodeTheme .dimens.grid.x3),
108- horizontalArrangement = Arrangement .spacedBy(CodeTheme .dimens.grid.x1),
109- verticalAlignment = Alignment .CenterVertically
110- ) {
111- // remove slot
112- IconButton (
113- enabled = selectedColors.count() > 1 ,
114- onClick = {
115- dispatchEvent(Event .RemoveSlot )
116- }
117- ) {
118- val alpha by animateFloatAsState(
119- if (selectedColors.count() > 1 ) 1f else ContentAlpha .disabled
120- )
121- Icon (
122- modifier = Modifier .size(CodeTheme .dimens.staticGrid.x4),
123- painter = painterResource(R .drawable.ic_minus),
124- contentDescription = " Remove Color Slot" ,
125- tint = Color .White .copy(alpha),
126- )
127- }
128-
129- // slots
130- LookaheadScope {
131- Row (
132- modifier = Modifier
133- .weight(1f )
134- .height(CodeTheme .dimens.grid.x10),
135- horizontalArrangement = Arrangement .spacedBy(CodeTheme .dimens.grid.x1),
136- verticalAlignment = Alignment .CenterVertically ,
137- ) {
138- selectedColors.fastForEachIndexed { slot, color ->
139- val borderColor by animateColorAsState(
140- if (selectedSlot == slot) Color .White else Color .White .copy(0.30f )
141- )
142-
143- Box (
144- modifier = Modifier
145- .fillMaxHeight()
146- .weight(1f )
147- .animateBounds(this @LookaheadScope)
148- .presenceBorder(3 .dp, borderColor)
149- .background(color = color, shape = CodeTheme .shapes.small)
150- .rememberedClickable {
151- dispatchEvent(Event .SelectSlot (slot))
152- }
153- )
154- }
155- }
156- }
157- // add slot
158- IconButton (
159- enabled = selectedColors.count() < maxSlots,
160- onClick = {
161- dispatchEvent(Event .AddSlot )
162- }
163- ) {
164- val alpha by animateFloatAsState(
165- if (selectedColors.count() < maxSlots) 1f else ContentAlpha .disabled
166- )
167- Icon (
168- modifier = Modifier .size(CodeTheme .dimens.staticGrid.x4),
169- painter = painterResource(R .drawable.ic_plus),
170- contentDescription = " Add Color Slot" ,
171- tint = Color .White .copy(alpha),
172- )
173- }
174- }
84+ selectedSlot = selectedSlot,
85+ maxSlots = maxSlots,
86+ selectedColors = selectedColors,
87+ dispatchEvent = dispatchEvent
88+ )
17589
176- val selectedSlotColor by rememberUpdatedState(selectedColors[selectedSlot])
90+ val selectedSlotStore by rememberUpdatedState(selectedColors[selectedSlot])
17791
17892 // color options
17993 AnimatedContent (
@@ -205,13 +119,20 @@ internal fun BillPlayground(
205119 when (mode) {
206120 PlaygroundMode .ColorPanel -> {
207121 ColorPanel (
208- selectedColor = selectedSlotColor ,
122+ selectedColor = selectedSlotStore.color ,
209123 modifier = Modifier
210124 .fillMaxWidth()
211125 .height(CodeTheme .dimens.grid.x14 * 2 )
212126 .padding(horizontal = CodeTheme .dimens.grid.x5)
213127 .padding(vertical = CodeTheme .dimens.grid.x3),
214- onChange = { dispatchEvent(Event .ChangeColor (it, ColorChange .Custom )) },
128+ onChange = { color, isDragging ->
129+ if (isDragging) {
130+ dispatchEvent(Event .PreviewColorChange (color))
131+ } else {
132+ dispatchEvent(Event .CommitColorChange (color))
133+
134+ }
135+ },
215136 onClose = {
216137 dispatchEvent(Event .CloseHueControls )
217138 }
@@ -231,28 +152,8 @@ internal fun BillPlayground(
231152 item(
232153 span = { GridItemSpan (maxLineSpan) }
233154 ) {
234- Box (
235- modifier = Modifier
236- .width(CodeTheme .dimens.grid.x10)
237- .fillMaxHeight()
238- .rainbowBackground()
239- .padding(CodeTheme .dimens.thickBorder)
240- .background(
241- color = Color .Black .copy(0.50f ),
242- shape = CodeTheme .shapes.small
243- )
244- .clip(CodeTheme .shapes.small)
245- .rememberedClickable {
246- dispatchEvent(Event .OpenHueControls )
247- }
248- .padding(CodeTheme .dimens.grid.x3),
249- contentAlignment = Alignment .Center
250- ) {
251- Icon (
252- painter = painterResource(R .drawable.ic_color_tune_hsl),
253- contentDescription = " Color manipulation" ,
254- tint = Color .White
255- )
155+ HueControlButton {
156+ dispatchEvent(Event .OpenHueControls )
256157 }
257158 }
258159
@@ -268,67 +169,6 @@ internal fun BillPlayground(
268169 }
269170}
270171
271- @Composable
272- private fun ColorOptionItem (
273- colorOptions : List <BillBackground >,
274- index : Int ,
275- dispatchEvent : (Event ) -> Unit
276- ) {
277- val numRows = 2
278- val itemsPerRow = (colorOptions.size + numRows - 1 ) / numRows
279- val col = index / numRows
280- val row = index % numRows
281- val newIndex = if (row == 0 ) {
282- col
283- } else {
284- itemsPerRow + col
285- }
286- if (newIndex < colorOptions.size) {
287- val option = colorOptions[newIndex]
288- Box (
289- modifier = Modifier
290- .width(CodeTheme .dimens.grid.x10)
291- .presenceBorder()
292- .addIf(option is BillBackground .Solid ) {
293- Modifier .background(
294- color = hexToColor((option as BillBackground .Solid ).colorHex),
295- shape = CodeTheme .shapes.small
296- )
297- }
298- .addIf(option is BillBackground .Gradient ) {
299- val colors =
300- (option as BillBackground .Gradient ).colors.map {
301- hexToColor(
302- it
303- )
304- }
305- Modifier .background(
306- brush = Brush .verticalGradient(
307- colors = colors,
308- ),
309- shape = CodeTheme .shapes.small
310- )
311- }
312- .rememberedClickable {
313- when (option) {
314- is BillBackground .Gradient -> dispatchEvent(
315- Event .LoadBackground (
316- option
317- )
318- )
319-
320- is BillBackground .Solid -> dispatchEvent(
321- Event .ChangeColor (
322- hexToColor(option.colorHex),
323- ColorChange .Preset
324- )
325- )
326- }
327- }
328- )
329- }
330- }
331-
332172internal fun Modifier.presenceBorder (
333173 width : Dp = 2.dp,
334174 color : Color = Color .White .copy(0.30f)
@@ -340,49 +180,6 @@ internal fun Modifier.presenceBorder(
340180 )
341181}
342182
343- private fun Modifier.rainbowBackground (): Modifier = composed {
344- val small = CodeTheme .shapes.small
345- val thickBorder = CodeTheme .dimens.thickBorder
346- val density = LocalDensity .current
347- this .drawWithContent {
348- drawRoundRect(
349- brush = Brush .sweepGradient(
350- colorStops = arrayOf( // Starts at 3 o'clock
351- 0f to Color (0xFFBB3DFF ), // Purple starts at 3 o'clock (right)
352- 0.16f to Color (0xFFFF3D3D ), // Dark red
353- 0.22f to Color (0xFFFF7070 ), // Light red
354- 0.38f to Color (0xFFFFC23D ), // Yellow
355- 0.58f to Color (0xFF54FF3D ), // Green
356- 0.81f to Color (0xFF3DEFFF ), // Cyan
357- 0.92f to Color (0xFF3DA8FF ), // Blue
358- 1f to Color (0xFFBB3DFF ) // Loops back to purple
359- ),
360- center = center
361- ),
362- cornerRadius = CornerRadius (
363- small.topStart.toPx(size, density),
364- small.topEnd.toPx(size, density)
365- ),
366- )
367- drawRoundRect(
368- brush = SolidColor (Color .Black .copy(0.50f )),
369- cornerRadius = CornerRadius (
370- small.topStart.toPx(size, density),
371- small.topEnd.toPx(size, density)
372- ),
373- topLeft = Offset (
374- x = thickBorder.toPx(),
375- y = thickBorder.toPx()
376- ),
377- size = size.copy(
378- width = size.width - thickBorder.toPx() * 2 ,
379- height = size.height - thickBorder.toPx() * 2
380- )
381- )
382- drawContent()
383- }
384- }
385-
386183private val usdToCadRate = Rate (fx = 1.37161 , currency = CurrencyCode .CAD )
387184private val cadToUsdRate = Rate (fx = 0.72894 , currency = CurrencyCode .USD )
388185private val rates = mapOf (
0 commit comments