Skip to content

Commit cc10db0

Browse files
committed
feat: add hue and saturation color panel to bill playground
Signed-off-by: Brandon McAnsh <[email protected]>
1 parent 092067c commit cc10db0

File tree

13 files changed

+900
-133
lines changed

13 files changed

+900
-133
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
2+
android:width="9dp"
3+
android:height="15dp"
4+
android:viewportWidth="9"
5+
android:viewportHeight="15">
6+
<path
7+
android:pathData="M8.45,7.346C8.45,7.49 8.423,7.623 8.367,7.745C8.312,7.866 8.229,7.983 8.118,8.093L1.652,14.418C1.469,14.607 1.242,14.701 0.971,14.701C0.794,14.701 0.631,14.656 0.481,14.568C0.332,14.485 0.213,14.369 0.125,14.219C0.042,14.075 0,13.915 0,13.738C0,13.472 0.1,13.237 0.299,13.032L6.126,7.346L0.299,1.66C0.1,1.455 0,1.223 0,0.963C0,0.78 0.042,0.617 0.125,0.473C0.213,0.329 0.332,0.216 0.481,0.133C0.631,0.044 0.794,0 0.971,0C1.242,0 1.469,0.091 1.652,0.274L8.118,6.599C8.229,6.71 8.312,6.826 8.367,6.948C8.423,7.069 8.45,7.202 8.45,7.346Z"
8+
android:fillColor="#ffffff"/>
9+
</vector>

apps/flipcash/features/bill-customization/src/main/kotlin/com/flipcash/app/bill/customization/BillCustomizationScaffold.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ fun BillPlaygroundScaffold(content: @Composable () -> Unit) {
154154
modifier = Modifier.fillMaxWidth(),
155155
) {
156156
BillPlayground(
157+
mode = state.mode,
157158
selectedColors = state.selectedColors,
158159
selectedSlot = state.selectedSlot,
159160
maxSlots = state.maxSlots,

apps/flipcash/features/bill-customization/src/main/kotlin/com/flipcash/app/bill/customization/components/BillPlayground.kt

Lines changed: 155 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
package com.flipcash.app.bill.customization.components
22

3+
import androidx.compose.animation.AnimatedContent
34
import androidx.compose.animation.ExperimentalSharedTransitionApi
45
import androidx.compose.animation.animateBounds
56
import androidx.compose.animation.animateColorAsState
7+
import androidx.compose.animation.core.Spring
8+
import androidx.compose.animation.core.VisibilityThreshold
69
import 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
715
import androidx.compose.foundation.background
816
import androidx.compose.foundation.border
917
import androidx.compose.foundation.layout.Arrangement
@@ -29,9 +37,11 @@ import androidx.compose.material.IconButton
2937
import androidx.compose.runtime.Composable
3038
import androidx.compose.runtime.getValue
3139
import androidx.compose.runtime.remember
40+
import androidx.compose.runtime.rememberUpdatedState
3241
import androidx.compose.ui.Alignment
3342
import androidx.compose.ui.Modifier
3443
import androidx.compose.ui.composed
44+
import androidx.compose.ui.draw.clip
3545
import androidx.compose.ui.draw.drawWithContent
3646
import androidx.compose.ui.geometry.CornerRadius
3747
import androidx.compose.ui.geometry.Offset
@@ -44,10 +54,12 @@ import androidx.compose.ui.platform.LocalDensity
4454
import androidx.compose.ui.res.painterResource
4555
import androidx.compose.ui.tooling.preview.Preview
4656
import androidx.compose.ui.unit.Dp
57+
import androidx.compose.ui.unit.IntOffset
4758
import androidx.compose.ui.unit.dp
4859
import androidx.compose.ui.util.fastForEachIndexed
4960
import androidx.lifecycle.compose.collectAsStateWithLifecycle
5061
import com.flipcash.app.bill.customization.Event
62+
import com.flipcash.app.bill.customization.PlaygroundMode
5163
import com.flipcash.app.bill.customization.internal.InternalBillPlaygroundController
5264
import com.flipcash.app.theme.FlipcashDesignSystem
5365
import com.flipcash.features.bill.playground.R
@@ -64,6 +76,7 @@ import com.getcode.ui.utils.hexToColor
6476
@OptIn(ExperimentalSharedTransitionApi::class)
6577
@Composable
6678
internal 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

Comments
 (0)