Skip to content

Commit e9cdcb3

Browse files
committed
feat(bill/playground): split controller into multiple feature controllers
Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent 87ac049 commit e9cdcb3

6 files changed

Lines changed: 276 additions & 160 deletions

File tree

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

Lines changed: 12 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@ import androidx.compose.material.DismissState
2424
import androidx.compose.material.DismissValue
2525
import androidx.compose.material.ExperimentalMaterialApi
2626
import androidx.compose.material.Text
27-
import androidx.compose.material.icons.Icons
28-
import androidx.compose.material.icons.filled.CopyAll
2927
import androidx.compose.runtime.Composable
3028
import androidx.compose.runtime.derivedStateOf
3129
import androidx.compose.runtime.getValue
@@ -44,55 +42,43 @@ import com.flipcash.app.bill.customization.components.BillPlayground
4442
import com.flipcash.app.bills.AnimatedBill
4543
import com.flipcash.app.core.bill.Bill
4644
import com.flipcash.features.bill.playground.R
47-
import com.getcode.opencode.model.financial.BillBackground
4845
import com.getcode.opencode.model.financial.TokenBillCustomizations
4946
import com.getcode.theme.CodeTheme
5047
import com.getcode.ui.components.AppBarDefaults
5148
import com.getcode.ui.core.measured
5249
import com.getcode.ui.core.rememberedClickable
53-
import com.getcode.ui.core.unboundedClickable
5450
import com.getcode.ui.utils.AnimationUtils
55-
import com.getcode.ui.utils.toAGColor
5651

5752
@OptIn(ExperimentalMaterialApi::class)
5853
@Composable
5954
fun BillPlaygroundScaffold(content: @Composable () -> Unit) {
6055
val controller = LocalBillPlaygroundController.current
6156

62-
val state by controller.state.collectAsStateWithLifecycle()
57+
val playgroundState by controller.state.collectAsStateWithLifecycle()
6358

6459
// bill dismiss state, restarted for every bill
65-
val billDismissState = remember(state.bill) {
60+
val billDismissState = remember(playgroundState.bill) {
6661
DismissState(
6762
initialValue = DismissValue.Default,
6863
confirmStateChange = { false }
6964
)
7065
}
7166

7267
val customizationsOptions by remember(
73-
state.selectedColors,
74-
state.bill?.token?.billCustomizations
68+
playgroundState.backgroundState.selectedColors,
69+
playgroundState.bill?.token?.billCustomizations
7570
) {
7671
derivedStateOf {
77-
val background = if (state.selectedColors.count() > 1) {
78-
BillBackground.Gradient.from(
79-
state.selectedColors.map { it.color.toAGColor() }
80-
)
81-
} else {
82-
BillBackground.Solid.from(state.selectedColors.first().color.toAGColor())
83-
}
84-
85-
8672
return@derivedStateOf TokenBillCustomizations(
87-
background = background,
73+
background = playgroundState.background,
8874
icon = null,
8975
)
9076
}
9177
}
9278

93-
val augmentedBill by remember(state.bill, customizationsOptions) {
79+
val augmentedBill by remember(playgroundState.bill, customizationsOptions) {
9480
derivedStateOf {
95-
val bill = state.bill ?: return@derivedStateOf null
81+
val bill = playgroundState.bill ?: return@derivedStateOf null
9682
if (bill !is Bill.Cash) return@derivedStateOf null
9783
bill.copy(
9884
token = bill.token.copy(
@@ -110,7 +96,7 @@ fun BillPlaygroundScaffold(content: @Composable () -> Unit) {
11096
mutableStateOf(0.dp)
11197
}
11298

113-
BackHandler(state.isCustomizing) {
99+
BackHandler(playgroundState.isCustomizing) {
114100
controller.cancel()
115101
}
116102

@@ -119,7 +105,7 @@ fun BillPlaygroundScaffold(content: @Composable () -> Unit) {
119105
AnimatedBill(
120106
modifier = Modifier.fillMaxSize(),
121107
dismissState = billDismissState,
122-
dismissed = !state.isCustomizing,
108+
dismissed = !playgroundState.isCustomizing,
123109
contentPadding = PaddingValues(
124110
top = topBarHeight,
125111
bottom = playgroundHeight,
@@ -136,7 +122,7 @@ fun BillPlaygroundScaffold(content: @Composable () -> Unit) {
136122
modifier = Modifier
137123
.fillMaxWidth()
138124
.measured { topBarHeight = it.height },
139-
visible = state.isCustomizing,
125+
visible = playgroundState.isCustomizing,
140126
enter = fadeIn(),
141127
exit = fadeOut(),
142128
) {
@@ -156,7 +142,7 @@ fun BillPlaygroundScaffold(content: @Composable () -> Unit) {
156142
.fillMaxWidth()
157143
.align(Alignment.BottomCenter)
158144
.measured { playgroundHeight = it.height },
159-
visible = state.isCustomizing,
145+
visible = playgroundState.isCustomizing,
160146
enter = slideInVertically { it },
161147
exit = slideOutVertically { it },
162148
label = "bill customization",
@@ -165,11 +151,7 @@ fun BillPlaygroundScaffold(content: @Composable () -> Unit) {
165151
modifier = Modifier.fillMaxWidth(),
166152
) {
167153
BillPlayground(
168-
mode = state.mode,
169-
selectedColors = state.selectedColors,
170-
selectedSlot = state.selectedSlot,
171-
maxSlots = state.maxSlots,
172-
colorOptions = state.colorOptions,
154+
state = playgroundState,
173155
) { event ->
174156
controller.dispatchEvent(event)
175157
}

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

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ import androidx.compose.runtime.remember
3131
import androidx.compose.runtime.rememberUpdatedState
3232
import androidx.compose.ui.Alignment
3333
import androidx.compose.ui.Modifier
34-
import androidx.compose.ui.composed
3534
import androidx.compose.ui.graphics.Color
3635
import androidx.compose.ui.graphics.Shape
3736
import androidx.compose.ui.platform.LocalContext
@@ -40,26 +39,18 @@ import androidx.compose.ui.unit.Dp
4039
import androidx.compose.ui.unit.IntOffset
4140
import androidx.compose.ui.unit.dp
4241
import androidx.lifecycle.compose.collectAsStateWithLifecycle
43-
import com.flipcash.app.bill.customization.ColorStore
4442
import com.flipcash.app.bill.customization.Event
4543
import com.flipcash.app.bill.customization.PlaygroundMode
44+
import com.flipcash.app.bill.customization.PlaygroundState
4645
import com.flipcash.app.bill.customization.internal.InternalBillPlaygroundController
4746
import com.flipcash.app.theme.FlipcashDesignSystem
48-
import com.getcode.opencode.compose.ExchangeStub
49-
import com.getcode.opencode.model.financial.BillBackground
50-
import com.getcode.opencode.model.financial.CurrencyCode
51-
import com.getcode.opencode.model.financial.Rate
5247
import com.getcode.theme.CodeTheme
5348
import com.getcode.ui.components.Pill
5449

5550
@OptIn(ExperimentalSharedTransitionApi::class)
5651
@Composable
5752
internal fun BillPlayground(
58-
mode: PlaygroundMode,
59-
selectedSlot: Int,
60-
maxSlots: Int,
61-
colorOptions: List<BillBackground>,
62-
selectedColors: List<ColorStore>,
53+
state: PlaygroundState,
6354
dispatchEvent: (Event) -> Unit,
6455
) {
6556
Column(
@@ -83,17 +74,17 @@ internal fun BillPlayground(
8374
modifier = Modifier
8475
.fillMaxWidth()
8576
.padding(horizontal = CodeTheme.dimens.grid.x3),
86-
selectedSlot = selectedSlot,
87-
maxSlots = maxSlots,
88-
selectedColors = selectedColors,
77+
selectedSlot = state.backgroundState.selectedSlot,
78+
maxSlots = state.backgroundState.maxSlots,
79+
selectedColors = state.backgroundState.selectedColors,
8980
dispatchEvent = dispatchEvent
9081
)
9182

92-
val selectedSlotStore by rememberUpdatedState(selectedColors[selectedSlot])
83+
val selectedSlotStore by rememberUpdatedState(state.backgroundState.selectedColors[state.backgroundState.selectedSlot])
9384

9485
// color options
9586
AnimatedContent(
96-
targetState = mode,
87+
targetState = state.backgroundState.mode,
9788
transitionSpec = {
9889
if (targetState == PlaygroundMode.ColorPanel) {
9990
// ColorPanel is entering, slide in from the left
@@ -160,8 +151,8 @@ internal fun BillPlayground(
160151
}
161152
}
162153

163-
items(colorOptions.size) { index ->
164-
ColorOptionItem(colorOptions, index) { event ->
154+
items(state.backgroundState.colorOptions.size) { index ->
155+
ColorOptionItem(state.backgroundState.colorOptions, index) { event ->
165156
dispatchEvent(event)
166157
}
167158
}
@@ -205,17 +196,13 @@ private fun PreviewCustomizationControls() {
205196
.aspectRatio(0.555f)
206197
.weight(1f)
207198
.background(
208-
brush = state.brush,
199+
brush = state.backgroundBrush,
209200
shape = CodeTheme.shapes.large,
210201
)
211202
.presenceBorder(),
212203
)
213204
BillPlayground(
214-
mode = state.mode,
215-
selectedSlot = state.selectedSlot,
216-
maxSlots = state.maxSlots,
217-
selectedColors = state.selectedColors,
218-
colorOptions = state.colorOptions,
205+
state = state,
219206
) { controller.dispatchEvent(it) }
220207
}
221208
}

apps/flipcash/shared/bill-customization/src/main/kotlin/com/flipcash/app/bill/customization/BillPlaygroundController.kt

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,31 @@ package com.flipcash.app.bill.customization
33
import androidx.compose.runtime.staticCompositionLocalOf
44
import androidx.compose.ui.graphics.Brush
55
import androidx.compose.ui.graphics.Color
6+
import com.flipcash.app.bill.customization.internal.ColorState
67
import com.flipcash.app.core.bill.Bill
78
import com.getcode.opencode.model.financial.BillBackground
89
import com.getcode.opencode.model.financial.Token
910
import com.getcode.ui.utils.Hsv
1011
import com.getcode.ui.utils.color
1112
import com.getcode.ui.utils.hexToColor
1213
import com.getcode.ui.utils.hsv
14+
import com.getcode.ui.utils.toAGColor
1315
import kotlinx.coroutines.flow.MutableStateFlow
1416
import kotlinx.coroutines.flow.StateFlow
1517
import java.util.UUID
1618

19+
interface RestorableController<T> {
20+
fun getCurrentCleanState(): T // no transient preview values
21+
fun restore(state: T) // idempotent, full replace
22+
}
23+
24+
enum class PlaygroundFeature {
25+
Background,
26+
;
27+
}
28+
1729
interface BillPlaygroundController {
18-
val state: StateFlow<State>
30+
val state: StateFlow<PlaygroundState>
1931
val canUndo: Boolean
2032
val canCopy: Boolean
2133
fun customizeFor(token: Token)
@@ -43,30 +55,41 @@ data class ColorStore(
4355
get() = hsv.color
4456
}
4557

46-
data class State(
58+
data class PlaygroundState(
4759
val bill: Bill? = null,
48-
val presetsOpen: Boolean = false,
49-
val mode: PlaygroundMode = PlaygroundMode.Presets,
50-
val selectedSlot: Int = 0,
51-
val maxSlots: Int = MaxGradientColors,
52-
val selectedColors: List<ColorStore> = buildGradient(),
53-
val colorOptions: List<BillBackground.Solid> = PresetColorOptions,
54-
val gradientOptions: List<BillBackground.Gradient> = PresetGradients,
60+
val selectedFeature: PlaygroundFeature = PlaygroundFeature.Background,
61+
val backgroundState: ColorState = ColorState(),
5562
) {
5663
val isCustomizing: Boolean
5764
get() = bill != null
5865

59-
val brush: Brush
66+
val background: BillBackground
67+
get() {
68+
return with(backgroundState) {
69+
if (selectedColors.count() > 1) {
70+
BillBackground.Gradient.from(
71+
selectedColors.map { it.color.toAGColor() }
72+
)
73+
} else {
74+
BillBackground.Solid.from(selectedColors.first().color.toAGColor())
75+
}
76+
}
77+
}
78+
79+
val backgroundBrush: Brush
6080
get() {
61-
if (selectedColors.size == 1) return Brush.verticalGradient(listOf(selectedColors.first().color, selectedColors.first().color))
62-
val colorStops = selectedColors.mapIndexed { index, store -> index.toFloat() / (selectedColors.size - 1) to store.color }
63-
return Brush.verticalGradient(
64-
colorStops = colorStops.toTypedArray()
65-
)
81+
with (backgroundState) {
82+
if (selectedColors.size == 1) return Brush.verticalGradient(listOf(selectedColors.first().color, selectedColors.first().color))
83+
val colorStops = selectedColors.mapIndexed { index, store -> index.toFloat() / (selectedColors.size - 1) to store.color }
84+
return Brush.verticalGradient(
85+
colorStops = colorStops.toTypedArray()
86+
)
87+
}
6688
}
6789
}
6890

6991
sealed interface Event {
92+
data class SelectFeature(val feature: PlaygroundFeature): Event
7093
data object AddSlot: Event
7194
data object RemoveSlot: Event
7295
data class SelectSlot(val slot: Int): Event
@@ -118,7 +141,7 @@ internal fun buildGradient(): List<ColorStore> {
118141
}
119142

120143
internal object StubPlaygroundController : BillPlaygroundController {
121-
override val state: StateFlow<State> = MutableStateFlow(State())
144+
override val state: StateFlow<PlaygroundState> = MutableStateFlow(PlaygroundState())
122145
override val canUndo: Boolean = false
123146
override val canCopy: Boolean = false
124147
override fun customizeFor(token: Token) = Unit

apps/flipcash/shared/bill-customization/src/main/kotlin/com/flipcash/app/bill/customization/inject/PlaygroundModule.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.flipcash.app.bill.customization.inject
22

33
import android.content.ClipboardManager
44
import com.flipcash.app.bill.customization.BillPlaygroundController
5+
import com.flipcash.app.bill.customization.internal.BackgroundController
56
import com.flipcash.app.bill.customization.internal.InternalBillPlaygroundController
67
import dagger.Module
78
import dagger.Provides
@@ -12,9 +13,13 @@ import javax.inject.Singleton
1213
@Module
1314
@InstallIn(SingletonComponent::class)
1415
object PlaygroundModule {
16+
1517
@Provides
1618
@Singleton
1719
fun providesPlaygroundController(
1820
clipboardManager: ClipboardManager,
19-
): BillPlaygroundController = InternalBillPlaygroundController(clipboardManager)
21+
): BillPlaygroundController =
22+
InternalBillPlaygroundController(
23+
clipboard = clipboardManager
24+
)
2025
}

0 commit comments

Comments
 (0)