Skip to content

Commit 98f451f

Browse files
committed
feat: update animations, transitions and add polish to bill creator
Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent 0e93121 commit 98f451f

10 files changed

Lines changed: 319 additions & 157 deletions

File tree

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

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import androidx.compose.ui.Alignment
3232
import androidx.compose.ui.Modifier
3333
import androidx.compose.ui.composed
3434
import androidx.compose.ui.graphics.Color
35+
import androidx.compose.ui.graphics.Shape
3536
import androidx.compose.ui.platform.LocalContext
3637
import androidx.compose.ui.tooling.preview.Preview
3738
import androidx.compose.ui.unit.Dp
@@ -102,7 +103,7 @@ internal fun BillPlayground(
102103
visibilityThreshold = IntOffset.VisibilityThreshold,
103104
)
104105
) { width -> -width } togetherWith
105-
slideOutHorizontally { width -> width } + fadeOut()
106+
slideOutHorizontally { width -> width } + fadeOut()
106107
} else { // targetState == PlaygroundMode.Presets
107108
// ColorPanel is exiting, slide out to the left
108109
slideInHorizontally(
@@ -112,14 +113,14 @@ internal fun BillPlayground(
112113
visibilityThreshold = IntOffset.VisibilityThreshold,
113114
),
114115
) { width -> width } togetherWith
115-
slideOutHorizontally { width -> -width } + fadeOut()
116+
slideOutHorizontally { width -> -width } + fadeOut()
116117
}
117118
}
118119
) { mode ->
119120
when (mode) {
120121
PlaygroundMode.ColorPanel -> {
121122
ColorPanel(
122-
selectedColor = selectedSlotStore.color,
123+
hsv = selectedSlotStore.hsv,
123124
modifier = Modifier
124125
.fillMaxWidth()
125126
.height(CodeTheme.dimens.grid.x14 * 2)
@@ -138,6 +139,7 @@ internal fun BillPlayground(
138139
}
139140
)
140141
}
142+
141143
PlaygroundMode.Presets -> {
142144
LazyHorizontalGrid(
143145
modifier = Modifier
@@ -169,16 +171,16 @@ internal fun BillPlayground(
169171
}
170172
}
171173

174+
@Composable
172175
internal fun Modifier.presenceBorder(
173176
width: Dp = 2.dp,
174-
color: Color = Color.White.copy(0.30f)
175-
): Modifier = this.composed {
176-
Modifier.border(
177-
width = width,
178-
color = color,
179-
shape = CodeTheme.shapes.small
180-
)
181-
}
177+
color: Color = Color.White.copy(0.30f),
178+
shape: Shape = CodeTheme.shapes.small
179+
): Modifier = this.border(
180+
width = width,
181+
color = color,
182+
shape = shape
183+
)
182184

183185
private val usdToCadRate = Rate(fx = 1.37161, currency = CurrencyCode.CAD)
184186
private val cadToUsdRate = Rate(fx = 0.72894, currency = CurrencyCode.USD)

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import com.getcode.theme.CodeTheme
1212
import com.getcode.ui.core.addIf
1313
import com.getcode.ui.core.rememberedClickable
1414
import com.getcode.ui.utils.hexToColor
15+
import com.getcode.ui.utils.hsv
1516

1617
@Composable
1718
internal fun ColorOptionItem(
@@ -64,7 +65,7 @@ internal fun ColorOptionItem(
6465

6566
is BillBackground.Solid -> dispatchEvent(
6667
Event.CommitColorChange(
67-
hexToColor(option.colorHex),
68+
hexToColor(option.colorHex).hsv,
6869
)
6970
)
7071
}

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

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,15 @@ import com.flipcash.app.theme.FlipcashDesignSystem
2929
import com.flipcash.features.bill.playground.R
3030
import com.getcode.theme.CodeTheme
3131
import com.getcode.ui.core.rememberedClickable
32+
import com.getcode.ui.utils.Hsv
3233
import com.getcode.ui.utils.color
34+
import com.getcode.ui.utils.hsv
3335

3436
@Composable
3537
internal fun ColorPanel(
36-
selectedColor: Color,
38+
hsv: Hsv,
3739
modifier: Modifier = Modifier,
38-
onChange: (Color, isDragging: Boolean) -> Unit,
40+
onChange: (Hsv, isDragging: Boolean) -> Unit,
3941
onClose: () -> Unit,
4042
) {
4143
Row(
@@ -47,18 +49,18 @@ internal fun ColorPanel(
4749
modifier = Modifier
4850
.weight(1f)
4951
.fillMaxHeight(),
50-
selectedColor = selectedColor
52+
hsv = hsv,
5153
) { hsv, isDragging ->
52-
onChange(hsv.color, isDragging)
54+
onChange(hsv, isDragging)
5355
}
5456

5557
HueScroller(
5658
modifier = Modifier
5759
.weight(1f)
5860
.fillMaxHeight(),
59-
color = selectedColor,
61+
hsv = hsv,
6062
) { hsv, isDragging ->
61-
onChange(hsv.color, isDragging)
63+
onChange(hsv, isDragging)
6264
}
6365

6466
Box(
@@ -88,16 +90,16 @@ internal fun ColorPanel(
8890
@Preview
8991
private fun ColorPanelPreview() {
9092
FlipcashDesignSystem {
91-
val color = CodeTheme.colors.cashBillColor
92-
var selectedColor by remember { mutableStateOf(color) }
93+
val hsv = CodeTheme.colors.cashBillColor.hsv
94+
var selectedHsv by remember { mutableStateOf(hsv) }
9395

9496
Column {
9597
Box(
9698
modifier = Modifier
9799
.fillMaxWidth()
98100
.fillMaxHeight(0.5f)
99101
.padding(20.dp)
100-
.background(selectedColor)
102+
.background(Color.hsv(selectedHsv.h, selectedHsv.s, selectedHsv.v))
101103
)
102104
Spacer(Modifier.weight(1f))
103105

@@ -107,9 +109,9 @@ private fun ColorPanelPreview() {
107109
.padding(vertical = CodeTheme.dimens.grid.x2)
108110
.padding(horizontal = CodeTheme.dimens.inset)
109111
.height(114.dp),
110-
selectedColor = selectedColor,
111-
onChange = { color, isDragging ->
112-
selectedColor = color
112+
hsv = selectedHsv,
113+
onChange = { hsv, isDragging ->
114+
selectedHsv = hsv
113115
},
114116
onClose = {}
115117
)

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

Lines changed: 84 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,25 @@
11
package com.flipcash.app.bill.customization.components
22

3+
import androidx.compose.animation.AnimatedContent
4+
import androidx.compose.animation.EnterTransition
5+
import androidx.compose.animation.ExitTransition
36
import androidx.compose.animation.ExperimentalSharedTransitionApi
4-
import androidx.compose.animation.animateBounds
57
import androidx.compose.animation.animateColorAsState
8+
import androidx.compose.animation.core.ExperimentalAnimatableApi
9+
import androidx.compose.animation.core.animateDpAsState
610
import androidx.compose.animation.core.animateFloatAsState
11+
import androidx.compose.animation.core.spring
12+
import androidx.compose.animation.togetherWith
713
import androidx.compose.foundation.background
814
import androidx.compose.foundation.layout.Arrangement
915
import androidx.compose.foundation.layout.Box
16+
import androidx.compose.foundation.layout.BoxWithConstraints
1017
import androidx.compose.foundation.layout.Row
1118
import androidx.compose.foundation.layout.fillMaxHeight
1219
import androidx.compose.foundation.layout.fillMaxWidth
1320
import androidx.compose.foundation.layout.height
14-
import androidx.compose.foundation.layout.padding
1521
import androidx.compose.foundation.layout.size
22+
import androidx.compose.foundation.layout.width
1623
import androidx.compose.material.ContentAlpha
1724
import androidx.compose.material.Icon
1825
import androidx.compose.material.IconButton
@@ -21,17 +28,16 @@ import androidx.compose.runtime.getValue
2128
import androidx.compose.ui.Alignment
2229
import androidx.compose.ui.Modifier
2330
import androidx.compose.ui.graphics.Color
24-
import androidx.compose.ui.layout.LookaheadScope
2531
import androidx.compose.ui.res.painterResource
32+
import androidx.compose.ui.unit.Dp
2633
import androidx.compose.ui.unit.dp
27-
import androidx.compose.ui.util.fastForEachIndexed
2834
import com.flipcash.app.bill.customization.ColorStore
2935
import com.flipcash.app.bill.customization.Event
3036
import com.flipcash.features.bill.playground.R
3137
import com.getcode.theme.CodeTheme
3238
import com.getcode.ui.core.rememberedClickable
3339

34-
@OptIn(ExperimentalSharedTransitionApi::class)
40+
@OptIn(ExperimentalSharedTransitionApi::class, ExperimentalAnimatableApi::class)
3541
@Composable
3642
internal fun ColorSlots(
3743
selectedSlot: Int,
@@ -64,33 +70,48 @@ internal fun ColorSlots(
6470
}
6571

6672
// slots
67-
LookaheadScope {
73+
BoxWithConstraints(
74+
modifier = Modifier
75+
.weight(1f)
76+
.height(CodeTheme.dimens.grid.x10)
77+
) {
6878
Row(
6979
modifier = Modifier
70-
.weight(1f)
80+
.fillMaxWidth()
7181
.height(CodeTheme.dimens.grid.x10),
72-
horizontalArrangement = Arrangement.spacedBy(CodeTheme.dimens.grid.x1),
82+
horizontalArrangement = Arrangement.spacedBy(
83+
CodeTheme.dimens.grid.x1,
84+
Alignment.End
85+
),
7386
verticalAlignment = Alignment.CenterVertically,
7487
) {
75-
selectedColors.fastForEachIndexed { slot, store ->
76-
val borderColor by animateColorAsState(
77-
if (selectedSlot == slot) Color.White else Color.White.copy(0.30f)
78-
)
88+
val slotSize = this@BoxWithConstraints.maxWidth / selectedColors.count()
89+
repeat(maxSlots) { slot ->
90+
val store = selectedColors.getOrNull(slot)
91+
92+
AnimatedContent(
93+
targetState = store,
94+
transitionSpec = { EnterTransition.None togetherWith ExitTransition.None }
95+
) { s ->
96+
val animatedWidth by animateDpAsState(
97+
if (s != null) slotSize else 0.dp,
98+
animationSpec = SlotTransitionAnimationSpec
99+
)
79100

80-
Box(
81-
modifier = Modifier
82-
.fillMaxHeight()
83-
.weight(1f)
84-
.animateBounds(this@LookaheadScope)
85-
.presenceBorder(3.dp, borderColor)
86-
.background(color = store.color, shape = CodeTheme.shapes.small)
87-
.rememberedClickable {
88-
dispatchEvent(Event.SelectSlot(slot))
89-
}
90-
)
101+
ColorSlot(
102+
modifier = Modifier
103+
.fillMaxHeight()
104+
.width(animatedWidth),
105+
store = s,
106+
selected = selectedSlot == slot,
107+
) {
108+
dispatchEvent(Event.SelectSlot(slot))
109+
}
110+
}
91111
}
92112
}
93113
}
114+
94115
// add slot
95116
IconButton(
96117
enabled = selectedColors.count() < maxSlots,
@@ -109,4 +130,43 @@ internal fun ColorSlots(
109130
)
110131
}
111132
}
112-
}
133+
}
134+
135+
@Composable
136+
private fun ColorSlot(
137+
store: ColorStore?,
138+
selected: Boolean,
139+
modifier: Modifier = Modifier,
140+
onClick: () -> Unit,
141+
) {
142+
143+
val borderColor by animateColorAsState(
144+
if (selected) Color.White else Color.White.copy(0.22f),
145+
animationSpec = SelectedSlotAnimationSpec,
146+
label = "slot border color"
147+
)
148+
149+
val borderWidth by animateDpAsState(
150+
if (selected) 3.dp else CodeTheme.dimens.border,
151+
animationSpec = SlotTransitionAnimationSpec,
152+
label = "slot border width"
153+
)
154+
155+
Box(
156+
modifier = modifier
157+
.fillMaxHeight()
158+
.presenceBorder(borderWidth, borderColor)
159+
.background(color = store?.color ?: Color.Transparent, shape = CodeTheme.shapes.small)
160+
.rememberedClickable(onClick = onClick)
161+
)
162+
}
163+
164+
private val SlotTransitionAnimationSpec = spring<Dp>(
165+
dampingRatio = 0.9f,
166+
stiffness = 450f,
167+
)
168+
169+
private val SelectedSlotAnimationSpec = spring<Color>(
170+
dampingRatio = 0.9f,
171+
stiffness = 450f,
172+
)

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,14 @@ internal fun ControlPanelItem(
3434

3535
Box(
3636
modifier = modifier
37-
.measuredIntSize { componentSize = it }
3837
.border(
3938
border = BorderStroke(
4039
width = CodeTheme.dimens.border,
4140
color = Color.White.copy(0.48f)
4241
),
4342
shape = shape
4443
)
45-
.clip(shape)
44+
.measuredIntSize { componentSize = it }
4645
) {
4746
content()
4847
}

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

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ package com.flipcash.app.bill.customization.components
33
import androidx.compose.foundation.background
44
import androidx.compose.foundation.layout.Box
55
import androidx.compose.foundation.layout.fillMaxHeight
6+
import androidx.compose.foundation.layout.height
67
import androidx.compose.foundation.layout.padding
78
import androidx.compose.foundation.layout.width
9+
import androidx.compose.foundation.shape.RoundedCornerShape
810
import androidx.compose.material.Icon
911
import androidx.compose.runtime.Composable
1012
import androidx.compose.ui.Alignment
@@ -19,8 +21,12 @@ import androidx.compose.ui.graphics.Color
1921
import androidx.compose.ui.graphics.SolidColor
2022
import androidx.compose.ui.platform.LocalDensity
2123
import androidx.compose.ui.res.painterResource
24+
import androidx.compose.ui.tooling.preview.Preview
25+
import androidx.compose.ui.unit.dp
26+
import com.flipcash.app.theme.FlipcashDesignSystem
2227
import com.flipcash.features.bill.playground.R
2328
import com.getcode.theme.CodeTheme
29+
import com.getcode.theme.extraSmall
2430
import com.getcode.ui.core.rememberedClickable
2531

2632
@Composable
@@ -33,13 +39,13 @@ internal fun HueControlButton(
3339
.width(CodeTheme.dimens.grid.x10)
3440
.fillMaxHeight()
3541
.rainbowBackground()
42+
.clip(CodeTheme.shapes.small)
43+
.rememberedClickable { onClick() }
3644
.padding(CodeTheme.dimens.thickBorder)
3745
.background(
3846
color = Color.Black.copy(0.50f),
39-
shape = CodeTheme.shapes.small
47+
shape = CodeTheme.shapes.extraSmall
4048
)
41-
.clip(CodeTheme.shapes.small)
42-
.rememberedClickable { onClick() }
4349
.padding(CodeTheme.dimens.grid.x3),
4450
contentAlignment = Alignment.Center
4551
) {
@@ -92,4 +98,14 @@ private fun Modifier.rainbowBackground(): Modifier = composed {
9298
)
9399
drawContent()
94100
}
101+
}
102+
103+
@Composable
104+
@Preview
105+
private fun HueControl_Preview() {
106+
FlipcashDesignSystem {
107+
Box(modifier = Modifier.height(114.dp)) {
108+
HueControlButton { }
109+
}
110+
}
95111
}

0 commit comments

Comments
 (0)