Skip to content

Commit b689edd

Browse files
committed
feat: add persistence backing for currency creator
if we ever want to support "draft"'s of unfinished currency creator flows this will serve as the backing. Upon successful completion, the draft is cleared. Once server establishes a TTL for attestation we can prune old drafts effeciently Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent 27f7311 commit b689edd

17 files changed

Lines changed: 827 additions & 4 deletions

File tree

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.flipcash.app.core.tokens
2+
3+
import android.net.Uri
4+
import com.flipcash.services.models.ModerationResult
5+
import com.getcode.opencode.model.ui.TokenBillCustomizations
6+
import com.getcode.solana.keys.Mint
7+
8+
data class CurrencyCreatorDraft(
9+
val id: Long = 0,
10+
val name: String,
11+
val description: String,
12+
val iconUri: Uri?,
13+
val customizations: TokenBillCustomizations?,
14+
val currentStep: CurrencyCreatorStep,
15+
val nameAttestation: ModerationResult.Attestation,
16+
val iconAttestation: ModerationResult.Attestation,
17+
val descriptionAttestation: ModerationResult.Attestation,
18+
val createdMint: Mint?,
19+
val savedAtMillis: Long,
20+
)

apps/flipcash/features/currency-creator/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ android {
99
dependencies {
1010
implementation(project(":apps:flipcash:features:bill-customization"))
1111
implementation(project(":apps:flipcash:shared:bills"))
12+
implementation(project(":apps:flipcash:shared:currency-creator"))
1213
implementation(project(":apps:flipcash:shared:onramp:deeplinks"))
1314
implementation(project(":apps:flipcash:shared:payments"))
1415
implementation(project(":apps:flipcash:shared:session"))

apps/flipcash/features/currency-creator/src/main/kotlin/com/flipcash/app/currencycreator/internal/CurrencyCreatorViewModel.kt

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ import androidx.lifecycle.viewModelScope
77
import com.flipcash.app.core.AppRoute
88
import com.flipcash.app.core.bill.Bill
99
import com.flipcash.app.core.tokens.CurrencyCreatorStep
10+
import com.flipcash.app.currencycreator.CurrencyCreatorCoordinator
1011
import com.flipcash.app.currencycreator.internal.components.CurrencyCreatorTopBarController
1112
import com.flipcash.app.onramp.ExternalWalletOnRampController
1213
import com.flipcash.app.onramp.ExternalWalletOnRampState
14+
import com.flipcash.app.core.tokens.CurrencyCreatorDraft
1315
import com.flipcash.app.tokens.BalancePoller
1416
import com.flipcash.app.userflags.UserFlagsCoordinator
1517
import com.flipcash.libs.coroutines.DispatcherProvider
@@ -52,7 +54,10 @@ import com.getcode.view.BaseViewModel2
5254
import com.getcode.view.LoadingSuccessState
5355
import dagger.hilt.android.lifecycle.HiltViewModel
5456
import kotlinx.coroutines.delay
57+
import kotlinx.coroutines.FlowPreview
58+
import kotlinx.coroutines.flow.debounce
5559
import kotlinx.coroutines.flow.distinctUntilChanged
60+
import kotlinx.coroutines.flow.drop
5661
import kotlinx.coroutines.flow.filter
5762
import kotlinx.coroutines.flow.filterIsInstance
5863
import kotlinx.coroutines.flow.flowOn
@@ -84,6 +89,7 @@ internal class CurrencyCreatorViewModel @Inject constructor(
8489
resources: ResourceHelper,
8590
val contentReader: ContentReader,
8691
val purchaseMethodController: PurchaseMethodController,
92+
private val currencyCreatorCoordinator: CurrencyCreatorCoordinator,
8793
) : BaseViewModel2<CurrencyCreatorViewModel.State, CurrencyCreatorViewModel.Event>(
8894
initialState = State(),
8995
updateStateForEvent = updateStateForEvent,
@@ -186,6 +192,40 @@ internal class CurrencyCreatorViewModel @Inject constructor(
186192
.onEach { dispatchEvent(Event.OnPurchaseAmountChanged(it)) }
187193
.launchIn(viewModelScope)
188194

195+
// Debounced draft persistence — save state 300ms after changes.
196+
// The coordinator handles ID assignment; the VM is ID-unaware.
197+
@OptIn(FlowPreview::class)
198+
stateFlow
199+
.drop(1) // skip initial empty state
200+
.filter { it.currentStep != null }
201+
.debounce(300)
202+
.distinctUntilChanged()
203+
.onEach { state ->
204+
currencyCreatorCoordinator.saveDraft(
205+
CurrencyCreatorDraft(
206+
name = state.nameFieldState.text.toString(),
207+
description = state.descriptionFieldState.text.toString(),
208+
iconUri = state.icon.dataOrNull,
209+
customizations = state.customizations,
210+
currentStep = state.currentStep!!,
211+
nameAttestation = state.attestations.name,
212+
iconAttestation = state.attestations.icon,
213+
descriptionAttestation = state.attestations.description,
214+
createdMint = state.createdMint,
215+
savedAtMillis = System.currentTimeMillis(),
216+
)
217+
)
218+
}
219+
.flowOn(dispatchers.IO)
220+
.launchIn(viewModelScope)
221+
222+
// Clear this flow's draft on successful purchase
223+
eventFlow
224+
.filterIsInstance<Event.PurchaseCompleted>()
225+
.onEach { currencyCreatorCoordinator.clearDraft() }
226+
.flowOn(dispatchers.IO)
227+
.launchIn(viewModelScope)
228+
189229
eventFlow
190230
.filterIsInstance<Event.OnIconSelected>()
191231
.mapNotNull { event ->
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
build/
2+
.gradle/
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
plugins {
2+
alias(libs.plugins.flipcash.android.feature)
3+
}
4+
5+
android {
6+
namespace = "${Gradle.flipcashNamespace}.shared.currencycreator"
7+
}
8+
9+
dependencies {
10+
implementation(project(":apps:flipcash:shared:persistence:sources"))
11+
implementation(project(":services:flipcash"))
12+
implementation(project(":services:opencode"))
13+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.flipcash.app.currencycreator
2+
3+
import com.flipcash.app.core.tokens.CurrencyCreatorDraft
4+
import com.flipcash.app.persistence.sources.CurrencyCreatorDraftDataSource
5+
import com.flipcash.libs.coroutines.DispatcherProvider
6+
import kotlinx.coroutines.CoroutineScope
7+
import kotlinx.coroutines.SupervisorJob
8+
import kotlinx.coroutines.flow.Flow
9+
import javax.inject.Inject
10+
import javax.inject.Singleton
11+
12+
@Singleton
13+
class CurrencyCreatorCoordinator @Inject constructor(
14+
private val draftDataSource: CurrencyCreatorDraftDataSource,
15+
private val dispatchers: DispatcherProvider,
16+
) {
17+
private val scope = CoroutineScope(SupervisorJob() + dispatchers.IO)
18+
19+
/** Tracks the draft ID for the active flow session. */
20+
private var activeDraftId: Long = 0
21+
22+
/** Observable list of all drafts for future resume detection. */
23+
val drafts: Flow<List<CurrencyCreatorDraft>> = draftDataSource.observe()
24+
25+
/** Save the current flow's draft. Creates a new row on first call,
26+
* upserts by ID on subsequent calls. */
27+
suspend fun saveDraft(draft: CurrencyCreatorDraft) {
28+
val toSave = draft.copy(id = activeDraftId)
29+
val id = draftDataSource.insert(toSave)
30+
if (activeDraftId == 0L) {
31+
activeDraftId = id
32+
}
33+
}
34+
35+
/** Clear the active flow's draft on completion or discard. */
36+
suspend fun clearDraft() {
37+
if (activeDraftId != 0L) {
38+
draftDataSource.deleteById(activeDraftId)
39+
activeDraftId = 0
40+
}
41+
}
42+
}

0 commit comments

Comments
 (0)