Skip to content

Commit 6b65b1c

Browse files
committed
feat(deposit): deposit-first user experience
Prioritize depositing funds as the primary entry point for new and empty-wallet users. Balance, menu, send, messenger, and cash screens now surface deposit options contextually, routing through the unified Swap flow. Coinbase on-ramp gains phone-region resolution for better availability detection. Phantom deposits swap USDC→USDF directly via CoinbaseStableSwapper into the VM deposit PDA, eliminating the server-side sweep. Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent 10f2c5f commit 6b65b1c

48 files changed

Lines changed: 1010 additions & 191 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/flipcash/app/src/main/kotlin/com/flipcash/app/internal/ui/navigation/AppScreenContent.kt

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ import com.flipcash.app.shareapp.ShareAppScreen
4646
import com.flipcash.app.tokens.SwapFlowScreen
4747
import com.flipcash.app.tokens.TokenInfoScreen
4848
import com.flipcash.app.tokens.TokenSelectScreen
49-
import com.flipcash.app.tokens.TokenTxProcessingScreen
49+
5050
import com.flipcash.app.transactions.TransactionHistoryScreen
5151
import com.flipcash.app.userflags.UserFlagsScreen
5252
import com.flipcash.app.withdrawal.WithdrawalFlowScreen
@@ -110,10 +110,6 @@ fun appEntryProvider(
110110
annotatedEntry<AppRoute.Token.Swap> { key ->
111111
SwapFlowScreen(route = key, resultStateRegistry = resultStateRegistry)
112112
}
113-
// TODO: fold this into above entry
114-
annotatedEntry<AppRoute.Token.TxProcessing> { key ->
115-
TokenTxProcessingScreen(key.swapId, key.swapPurpose, key.amount, key.isFundingShortfall)
116-
}
117113
annotatedEntry<AppRoute.Token.Discovery> { TokenDiscoveryScreen() }
118114
annotatedEntry<AppRoute.Token.CurrencyCreator> { key ->
119115
CurrencyCreatorFlowScreen(route = key, resultStateRegistry = resultStateRegistry)

apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/AppRoute.kt

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,10 @@ import com.flipcash.app.core.verification.VerificationStep
1515
import com.flipcash.app.core.withdrawal.WithdrawalResult
1616
import com.flipcash.app.core.withdrawal.WithdrawalStep
1717
import com.flipcash.app.core.onboarding.OnboardingStep
18-
import com.getcode.navigation.NonDismissableRoute
19-
import com.getcode.navigation.NonDraggableRoute
2018
import com.getcode.navigation.flow.FlowRoute
2119
import com.getcode.navigation.flow.FlowRouteWithResult
22-
import com.getcode.opencode.exchange.VerifiedFiat
23-
import com.getcode.opencode.internal.solana.model.SwapId
2420
import com.getcode.opencode.model.financial.Fiat
2521
import com.getcode.solana.keys.Mint
26-
import com.getcode.solana.keys.PublicKey
2722
import com.getcode.ui.core.RestrictionType
2823
import kotlinx.parcelize.Parcelize
2924
import kotlinx.serialization.Serializable
@@ -189,13 +184,6 @@ sealed interface AppRoute : NavKey, Parcelable {
189184
@Serializable
190185
data object PhantomConfirmTransaction: Token
191186

192-
@Serializable
193-
data class TxProcessing(
194-
val swapId: SwapId,
195-
val swapPurpose: SwapPurpose? = null,
196-
val amount: VerifiedFiat? = null,
197-
val isFundingShortfall: Boolean = false,
198-
) : Token, NonDismissableRoute, NonDraggableRoute
199187

200188
@Serializable
201189
data object Discovery: AppRoute

apps/flipcash/core/src/main/res/values/strings.xml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,11 @@
6666
<string name="subtitle_balanceIsHeldInUsdStablecoins">Your balance is held in US dollar stablecoins</string>
6767
<string name="subtitle_currentValueOfAllCurrencies">The current value of your currencies</string>
6868
<string name="subtitle_ofUsdStablecoins">of US dollar stablecoins</string>
69-
<string name="action_depositFunds">Deposit</string>
69+
<string name="action_deposit">Deposit</string>
7070
<string name="title_deposit">Deposit</string>
7171
<string name="title_depositFunds">Deposit Funds</string>
72+
<string name="action_depositFunds">Deposit Funds</string>
73+
7274

7375
<string name="title_withdraw">Withdraw</string>
7476
<string name="title_withdrawFunds">Withdraw Funds</string>
@@ -235,7 +237,7 @@
235237
<string name="title_tapAboveToAddCashToWallet">Tap above to Add Cash to your wallet</string>
236238
<string name="title_tapBelowToAddCashWallet">You don\'t have any cash yet.\nTap below to add cash to your wallet</string>
237239
<string name="title_noBalanceYet">No Balance Yet</string>
238-
<string name="description_noBalanceYet">Buy a currency to get started, or get another Flipcash user to give you some cash</string>
240+
<string name="description_noBalanceYet">Deposit funds to get started</string>
239241
<string name="description_noBalanceYetDiscover">Buy your first currency to get started</string>
240242
<string name="action_dismiss">Dismiss</string>
241243
<string name="title_success">Success</string>

apps/flipcash/features/balance/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ dependencies {
1313

1414
implementation(project(":apps:flipcash:shared:analytics"))
1515
implementation(project(":apps:flipcash:shared:featureflags"))
16+
implementation(project(":apps:flipcash:shared:payments"))
1617
implementation(project(":apps:flipcash:shared:tokens"))
1718
implementation(project(":apps:flipcash:shared:userflags"))
1819
implementation(project(":libs:datetime"))

apps/flipcash/features/balance/src/main/kotlin/com/flipcash/app/balance/internal/BalanceScreenContent.kt

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
1010
import androidx.compose.foundation.layout.navigationBarsPadding
1111
import androidx.compose.foundation.layout.padding
1212
import androidx.compose.foundation.shape.CircleShape
13-
import androidx.compose.material.Text
13+
import androidx.compose.material3.Text
1414
import androidx.compose.runtime.Composable
1515
import androidx.compose.runtime.CompositionLocalProvider
1616
import androidx.compose.runtime.getValue
@@ -97,29 +97,23 @@ private fun BalanceScreenContent(
9797

9898
Text(
9999
modifier = Modifier.fillMaxWidth(0.6f),
100-
text = if (tokenState.discoveryEnabled) {
101-
stringResource(R.string.description_noBalanceYetDiscover)
102-
} else {
103-
stringResource(R.string.description_noBalanceYet)
104-
},
100+
text = stringResource(R.string.description_noBalanceYet),
105101
style = CodeTheme.typography.textSmall,
106102
color = CodeTheme.colors.textSecondary,
107103
textAlign = TextAlign.Center,
108104
)
109105

110-
if (tokenState.discoveryEnabled) {
111-
CodeButton(
112-
onClick = {
113-
dispatchEvent(
114-
BalanceViewModel.Event.OpenScreen(AppRoute.Token.Discovery)
115-
)
116-
},
117-
modifier = Modifier.align(Alignment.CenterHorizontally),
118-
contentPadding = PaddingValues(),
119-
text = stringResource(R.string.action_discoverCurrencies),
120-
shape = CircleShape,
121-
)
122-
}
106+
CodeButton(
107+
onClick = {
108+
dispatchEvent(BalanceViewModel.Event.PresentDepositOptions)
109+
},
110+
modifier = Modifier
111+
.padding(top = CodeTheme.dimens.grid.x2)
112+
.align(Alignment.CenterHorizontally),
113+
contentPadding = PaddingValues(),
114+
text = stringResource(R.string.action_depositFunds),
115+
shape = CircleShape,
116+
)
123117
}
124118
}
125119
},

apps/flipcash/features/balance/src/main/kotlin/com/flipcash/app/balance/internal/BalanceViewModel.kt

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.flipcash.app.balance.internal
22

33
import androidx.lifecycle.viewModelScope
44
import com.flipcash.app.core.AppRoute
5+
import com.flipcash.app.payments.PurchaseMethodController
56
import com.flipcash.app.userflags.UserFlagsCoordinator
67
import com.flipcash.services.internal.model.thirdparty.OnRampProvider
78
import com.flipcash.services.user.AuthState
@@ -23,6 +24,7 @@ internal class BalanceViewModel @Inject constructor(
2324
userManager: UserManager,
2425
userFlags: UserFlagsCoordinator,
2526
dispatchers: DispatcherProvider,
27+
purchaseMethodController: PurchaseMethodController,
2628
) : BaseViewModel<BalanceViewModel.State, BalanceViewModel.Event>(
2729
initialState = State(),
2830
updateStateForEvent = updateStateForEvent,
@@ -38,6 +40,7 @@ internal class BalanceViewModel @Inject constructor(
3840
data object OpenCurrencySelection : Event
3941

4042
data class OpenScreen(val screen: AppRoute) : Event
43+
data object PresentDepositOptions: Event
4144
}
4245

4346
init {
@@ -46,9 +49,13 @@ internal class BalanceViewModel @Inject constructor(
4649
.flatMapLatest { userFlags.resolvedFlags }
4750
.mapNotNull { it.preferredOnRampProvider.effectiveValue }
4851
.filterIsInstance<OnRampProvider.Defined>()
49-
.onEach { provider ->
50-
dispatchEvent(Event.OnPreferredOnRampProviderChanged(provider))
51-
}
52+
.onEach { provider -> dispatchEvent(Event.OnPreferredOnRampProviderChanged(provider)) }
53+
.launchIn(viewModelScope)
54+
55+
eventFlow
56+
.filterIsInstance<Event.PresentDepositOptions>()
57+
.mapNotNull { purchaseMethodController.presentDepositOptions() }
58+
.onEach { route -> dispatchEvent(Event.OpenScreen(route)) }
5259
.launchIn(viewModelScope)
5360
}
5461

@@ -59,6 +66,7 @@ internal class BalanceViewModel @Inject constructor(
5966
is Event.OnPreferredOnRampProviderChanged -> { state ->
6067
state.copy(preferredOnRampProvider = event.provider)
6168
}
69+
Event.PresentDepositOptions -> { state -> state }
6270
is Event.OpenScreen -> { state -> state }
6371
}
6472
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import com.flipcash.app.currencycreator.CurrencyCreatorCoordinator
1414
import com.flipcash.app.currencycreator.internal.components.CurrencyCreatorTopBarController
1515
import com.flipcash.app.onramp.DeeplinkError
1616
import com.flipcash.app.onramp.DeeplinkOnRampError
17+
import com.flipcash.app.onramp.PhantomSwapResult
1718
import com.flipcash.app.onramp.PhantomWalletController
1819
import com.flipcash.app.onramp.isAlert
1920
import com.flipcash.app.onramp.isNetworkCause
@@ -542,7 +543,8 @@ internal class CurrencyCreatorViewModel @Inject constructor(
542543
amount = totalAmount,
543544
fee = feeAmount,
544545
token = token,
545-
).onSuccess { swapId ->
546+
).onSuccess { result ->
547+
val swapId = (result as PhantomSwapResult.WithSwapId).swapId
546548
dispatchEvent(Event.PurchaseSubmitted(swapId, token.address))
547549
}.onFailure { error ->
548550
handlePhantomError(error)

apps/flipcash/features/direct-send/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ dependencies {
1919
implementation(project(":libs:messaging"))
2020
implementation(project(":libs:permissions:bindings"))
2121
implementation(project(":apps:flipcash:shared:featureflags"))
22+
implementation(project(":apps:flipcash:shared:payments"))
2223
implementation(project(":apps:flipcash:shared:permissions"))
2324
implementation(project(":apps:flipcash:shared:contacts"))
2425
implementation(project(":apps:flipcash:shared:tokens"))

apps/flipcash/features/direct-send/src/main/kotlin/com/flipcash/app/directsend/SendFlowScreen.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import com.flipcash.app.directsend.internal.screens.ContactListScreen
1515
import com.flipcash.app.directsend.internal.screens.ContactsPermissionGateScreen
1616
import com.flipcash.app.directsend.internal.screens.PhoneGateLandingScreen
1717
import com.getcode.navigation.annotatedEntry
18+
import com.getcode.navigation.core.LocalCodeNavigator
1819
import com.getcode.navigation.flow.FlowExitReason
1920
import com.getcode.navigation.flow.FlowHost
2021
import com.getcode.navigation.flow.flowSharedViewModel

apps/flipcash/features/direct-send/src/main/kotlin/com/flipcash/app/directsend/internal/SendFlowViewModel.kt

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ import com.flipcash.app.contacts.ContactCoordinator
88
import com.flipcash.app.contacts.ContactCoordinator.ContactState
99
import com.flipcash.app.contacts.device.DeviceContact
1010
import com.flipcash.app.contacts.device.PickedContactData
11+
import com.flipcash.app.core.AppRoute
1112
import com.flipcash.app.core.send.SendStep
1213
import com.flipcash.app.featureflags.FeatureFlag
1314
import com.flipcash.app.featureflags.FeatureFlagController
15+
import com.flipcash.app.payments.PurchaseMethodController
1416
import com.flipcash.app.permissions.PickedContact
1517
import com.flipcash.app.tokens.TokenCoordinator
1618
import com.flipcash.features.directsend.R
@@ -31,6 +33,7 @@ import kotlinx.coroutines.flow.launchIn
3133
import kotlinx.coroutines.flow.map
3234
import kotlinx.coroutines.flow.onEach
3335
import kotlinx.coroutines.flow.take
36+
import kotlinx.coroutines.launch
3437
import javax.inject.Inject
3538
import kotlin.time.Duration.Companion.seconds
3639

@@ -41,6 +44,7 @@ internal class SendFlowViewModel @Inject constructor(
4144
private val contactCoordinator: ContactCoordinator,
4245
private val tokenCoordinator: TokenCoordinator,
4346
private val resources: ResourceHelper,
47+
purchaseMethodController: PurchaseMethodController,
4448
) : BaseViewModel<SendFlowViewModel.State, SendFlowViewModel.Event>(
4549
initialState = State(),
4650
updateStateForEvent = updateStateForEvent,
@@ -75,7 +79,8 @@ internal class SendFlowViewModel @Inject constructor(
7579

7680
data class NavigateToChat(val contact: DeviceContact) : Event
7781
data class NavigateToDirectSend(val contact: DeviceContact) : Event
78-
data object NavigateToDiscovery : Event
82+
data object PresentDepositOptions : Event
83+
data class NavigateToUsdfDepositOption(val route: AppRoute): Event
7984
}
8085

8186
private val messengerEnabled = featureFlags.observe(FeatureFlag.Messenger)
@@ -169,9 +174,9 @@ internal class SendFlowViewModel @Inject constructor(
169174
message = resources.getString(R.string.description_noBalanceYet),
170175
actions = listOf(
171176
BottomBarAction(
172-
text = resources.getString(R.string.action_discoverCurrencies)
177+
text = resources.getString(R.string.action_depositFunds)
173178
) {
174-
dispatchEvent(Event.NavigateToDiscovery)
179+
dispatchEvent(Event.PresentDepositOptions)
175180
},
176181
),
177182
showCancel = true,
@@ -185,6 +190,14 @@ internal class SendFlowViewModel @Inject constructor(
185190
}
186191
}.launchIn(viewModelScope)
187192

193+
eventFlow
194+
.filterIsInstance<Event.PresentDepositOptions>()
195+
.onEach {
196+
purchaseMethodController.presentDepositOptions()?.let { route ->
197+
dispatchEvent(Event.NavigateToUsdfDepositOption(route))
198+
}
199+
}.launchIn(viewModelScope)
200+
188201
eventFlow
189202
.filterIsInstance<Event.ContactRemoved>()
190203
.onEach { event -> contactCoordinator.removeContact(event.e164) }
@@ -272,7 +285,8 @@ internal class SendFlowViewModel @Inject constructor(
272285
is Event.SendInvite -> { state -> state }
273286
is Event.NavigateToChat -> { state -> state }
274287
is Event.NavigateToDirectSend -> { state -> state }
275-
is Event.NavigateToDiscovery -> { state -> state }
288+
is Event.PresentDepositOptions -> { state -> state }
289+
is Event.NavigateToUsdfDepositOption -> { state -> state }
276290
}
277291
}
278292
}

0 commit comments

Comments
 (0)