diff --git a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/ConversionGraphic.kt b/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/ConversionGraphic.kt
new file mode 100644
index 0000000000..a4c2713e51
--- /dev/null
+++ b/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/ConversionGraphic.kt
@@ -0,0 +1,75 @@
+package com.flipcash.app.core.ui
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.dp
+import com.flipcash.core.R
+
+/**
+ * A coin face the [ConversionGraphic] can show on either side of the arrow.
+ */
+enum class ConversionCoin {
+ /** The gold "Dollars" coin — Flipcash's own reserve currency. */
+ Dollars,
+
+ /** The USDC coin with the Solana badge on its bottom-right corner. */
+ UsdcOnSolana,
+}
+
+/**
+ * The "converted 1:1" education art (Figma node 9216:19798): two coins separated by an arrow.
+ *
+ * Composed here rather than shipped as one flattened asset so the same art serves both directions —
+ * [ConversionCoin.Dollars] to [ConversionCoin.UsdcOnSolana] when withdrawing, and the inverse when
+ * adding money from another wallet.
+ */
+@Composable
+fun ConversionGraphic(
+ from: ConversionCoin,
+ to: ConversionCoin,
+ modifier: Modifier = Modifier,
+) {
+ Row(
+ modifier = modifier,
+ // The badged coin's art is 111x112 — a 100 dp coin face plus the Solana badge overhanging
+ // its bottom-right corner. Top-aligning keeps both 100 dp faces on the same line; centering
+ // the boxes instead would push the badged one 6 dp out of line with its partner.
+ verticalAlignment = Alignment.Top,
+ // 14 dp between each pair, matching the 278x124 Figma frame's coin/arrow gaps.
+ horizontalArrangement = Arrangement.spacedBy(14.dp),
+ ) {
+ Coin(from)
+ Image(
+ // Nudged onto the coin faces' centre line, which the top alignment above leaves at
+ // 50 dp: (100 - 28) / 2.
+ modifier = Modifier.padding(top = 36.dp).size(28.dp),
+ painter = painterResource(R.drawable.ic_arrow_right),
+ contentDescription = null,
+ )
+ Coin(to)
+ }
+}
+
+@Composable
+private fun Coin(coin: ConversionCoin) {
+ when (coin) {
+ ConversionCoin.Dollars -> Image(
+ modifier = Modifier.size(100.dp),
+ painter = painterResource(R.drawable.ic_coin_dollars),
+ contentDescription = null,
+ )
+
+ // Natural size (111x112): the 100 dp coin plus the badge overhanging its corner.
+ ConversionCoin.UsdcOnSolana -> Image(
+ painter = painterResource(R.drawable.ic_usdc_on_solana),
+ contentDescription = null,
+ )
+ }
+}
diff --git a/apps/flipcash/core/src/main/res/drawable/ic_deposit_usdc_as_usdf.xml b/apps/flipcash/core/src/main/res/drawable/ic_deposit_usdc_as_usdf.xml
deleted file mode 100644
index 466d874318..0000000000
--- a/apps/flipcash/core/src/main/res/drawable/ic_deposit_usdc_as_usdf.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/apps/flipcash/core/src/main/res/values/strings.xml b/apps/flipcash/core/src/main/res/values/strings.xml
index da36deba76..37a603b5d1 100644
--- a/apps/flipcash/core/src/main/res/values/strings.xml
+++ b/apps/flipcash/core/src/main/res/values/strings.xml
@@ -808,8 +808,9 @@
Discover
- Deposit USDC
- Your Solana USDC will be converted 1:1 to USD on Flipcash (USDF)
+ Deposit USDC
+
+ Your USDC will be converted 1:1 to Dollars on Flipcash
Buy With Phantom
Purchase using Solana USDC in Phantom. Simply connect your wallet and confirm the transaction
diff --git a/apps/flipcash/features/deposit/src/main/kotlin/com/flipcash/app/deposit/internal/UsdcDepositInformationScreen.kt b/apps/flipcash/features/deposit/src/main/kotlin/com/flipcash/app/deposit/internal/UsdcDepositInformationScreen.kt
index ce692fef68..571e6dbf25 100644
--- a/apps/flipcash/features/deposit/src/main/kotlin/com/flipcash/app/deposit/internal/UsdcDepositInformationScreen.kt
+++ b/apps/flipcash/features/deposit/src/main/kotlin/com/flipcash/app/deposit/internal/UsdcDepositInformationScreen.kt
@@ -1,6 +1,5 @@
package com.flipcash.app.deposit.internal
-import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -12,11 +11,12 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import com.flipcash.app.core.deposit.DepositResult
import com.flipcash.app.core.deposit.DepositStep
+import com.flipcash.app.core.ui.ConversionCoin
+import com.flipcash.app.core.ui.ConversionGraphic
import com.flipcash.core.R
import com.getcode.navigation.flow.rememberFlowNavigator
import com.getcode.solana.keys.Mint
@@ -82,24 +82,24 @@ internal fun UsdcDepositInformationScreen(showOtherOptions: Boolean) {
modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.Center,
) {
- Image(
- painter = painterResource(R.drawable.ic_deposit_usdc_as_usdf),
- contentDescription = null,
+ ConversionGraphic(
+ from = ConversionCoin.UsdcOnSolana,
+ to = ConversionCoin.Dollars,
)
}
Column(
- modifier = Modifier.fillMaxWidth(0.60f),
+ modifier = Modifier.fillMaxWidth(0.80f),
verticalArrangement = Arrangement.spacedBy(CodeTheme.dimens.grid.x3),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
- text = stringResource(R.string.title_depositUsdcAsUsdf),
+ text = stringResource(R.string.title_depositUsdcAsDollars),
style = CodeTheme.typography.textLarge,
color = CodeTheme.colors.textMain,
)
Text(
- text = stringResource(R.string.description_depositUsdcAsUsdf),
+ text = stringResource(R.string.description_depositUsdcAsDollars),
style = CodeTheme.typography.textSmall,
color = CodeTheme.colors.textSecondary,
textAlign = TextAlign.Center,
diff --git a/apps/flipcash/features/tokens/src/main/kotlin/com/flipcash/app/tokens/internal/TokenTxProcessingScreen.kt b/apps/flipcash/features/tokens/src/main/kotlin/com/flipcash/app/tokens/internal/TokenTxProcessingScreen.kt
index a68130b5b7..7cc9260a0d 100644
--- a/apps/flipcash/features/tokens/src/main/kotlin/com/flipcash/app/tokens/internal/TokenTxProcessingScreen.kt
+++ b/apps/flipcash/features/tokens/src/main/kotlin/com/flipcash/app/tokens/internal/TokenTxProcessingScreen.kt
@@ -127,12 +127,20 @@ private fun TokenTxProcessingScreen(
LoadingSuccessState.State.Idle -> ""
LoadingSuccessState.State.Loading -> stringResource(R.string.title_processingYourTransaction)
LoadingSuccessState.State.Success -> {
- val name = when (state.purpose) {
- is SwapPurpose.Convert -> state.destinationTokenName
+ val name = when (val purpose = state.purpose) {
+ // Adding money always lands in the reserve, so naming it reads as
+ // "$1.00 of Dollars" — the amount alone already says it.
+ is SwapPurpose.Buy if purpose.fundingSource != FundingSource.Flexible -> null
+ // Converting into the reserve reads the same way, so it goes unnamed
+ // too; every other destination still names what the user received.
+ is SwapPurpose.Convert ->
+ state.destinationTokenName.takeUnless { state.isConvertingToDollars }
is SwapPurpose.BalanceIncrease -> state.tokenName
else -> stringResource(R.string.title_cashReserves)
}
- state.netTransferAmount.formatted(suffix = stringResource(R.string.label_ofToken, name))
+ state.netTransferAmount.formatted(
+ suffix = name?.let { stringResource(R.string.label_ofToken, it) },
+ )
}
},
style = CodeTheme.typography.textLarge,
diff --git a/apps/flipcash/features/withdrawal/src/main/kotlin/com/flipcash/app/withdrawal/internal/screens/UsdcWithdrawalInformationScreen.kt b/apps/flipcash/features/withdrawal/src/main/kotlin/com/flipcash/app/withdrawal/internal/screens/UsdcWithdrawalInformationScreen.kt
index 4d1dd4b1a6..08bc2cc2f0 100644
--- a/apps/flipcash/features/withdrawal/src/main/kotlin/com/flipcash/app/withdrawal/internal/screens/UsdcWithdrawalInformationScreen.kt
+++ b/apps/flipcash/features/withdrawal/src/main/kotlin/com/flipcash/app/withdrawal/internal/screens/UsdcWithdrawalInformationScreen.kt
@@ -4,12 +4,10 @@ import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@@ -18,8 +16,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
-import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.flipcash.app.core.ui.ConversionCoin
+import com.flipcash.app.core.ui.ConversionGraphic
import com.flipcash.app.core.withdrawal.WithdrawalResult
import com.flipcash.app.core.withdrawal.WithdrawalStep
import com.flipcash.app.featureflags.FeatureFlag
@@ -96,7 +95,10 @@ internal fun UsdcWithdrawalInformationScreen(showOtherOptions: Boolean) {
contentAlignment = Alignment.Center,
) {
if (isNewUi) {
- ConversionGraphic()
+ ConversionGraphic(
+ from = ConversionCoin.Dollars,
+ to = ConversionCoin.UsdcOnSolana,
+ )
} else {
Image(
painter = painterResource(R.drawable.ic_withdraw_usdf_as_usdc),
@@ -132,33 +134,3 @@ internal fun UsdcWithdrawalInformationScreen(showOtherOptions: Boolean) {
}
}
}
-
-/**
- * v2 "Dollars → USDC" graphic (Figma node 9216:19798). The v1 art carried the Flipcash "F" mark on
- * the left; v2 swaps in the gold Dollars coin, so the two halves are composed here rather than
- * shipped as one flattened asset.
- */
-@Composable
-private fun ConversionGraphic() {
- Row(
- verticalAlignment = Alignment.CenterVertically,
- // 14 dp between each pair, matching the 278x124 Figma frame's coin/arrow gaps.
- horizontalArrangement = Arrangement.spacedBy(14.dp),
- ) {
- Image(
- modifier = Modifier.size(100.dp),
- painter = painterResource(R.drawable.ic_coin_dollars),
- contentDescription = null,
- )
- Image(
- modifier = Modifier.size(28.dp),
- painter = painterResource(R.drawable.ic_arrow_right),
- contentDescription = null,
- )
- // Natural size (111x112): the 100 dp coin plus the Solana badge overhanging its corner.
- Image(
- painter = painterResource(R.drawable.ic_usdc_on_solana),
- contentDescription = null,
- )
- }
-}
diff --git a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/ui/SwapViewModel.kt b/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/ui/SwapViewModel.kt
index 63a177f07f..7c3e2a5aa7 100644
--- a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/ui/SwapViewModel.kt
+++ b/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/ui/SwapViewModel.kt
@@ -291,6 +291,10 @@ class SwapViewModel @Inject constructor(
val isConvertingFromDollars: Boolean
get() = (purpose as? SwapPurpose.Convert)?.mint == Mint.usdf
+ /** Whether a conversion lands in Dollars, which the reserve's own name never has to spell out. */
+ val isConvertingToDollars: Boolean
+ get() = (purpose as? SwapPurpose.Convert)?.destinationMint == Mint.usdf
+
val canTransact: Boolean
get() = buyProgress.isIdle && sellProgress.isIdle && processingProgress.isIdle
diff --git a/apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/ui/SwapViewModelStateTest.kt b/apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/ui/SwapViewModelStateTest.kt
index e5dcabb3c0..750fc3a87a 100644
--- a/apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/ui/SwapViewModelStateTest.kt
+++ b/apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/ui/SwapViewModelStateTest.kt
@@ -277,4 +277,31 @@ class SwapViewModelStateTest {
fun `netTransferAmount is Zero when confirmedNetTransferAmount is null`() {
assertEquals(Fiat.Zero, SwapViewModel.State().netTransferAmount)
}
+
+ // --- Computed: convert direction ---
+
+ @Test
+ fun `isConvertingToDollars is true when a conversion lands in the reserve`() {
+ val state = SwapViewModel.State(
+ purpose = SwapPurpose.Convert(mint = mint(), destinationMint = Mint.usdf),
+ )
+ assertTrue(state.isConvertingToDollars)
+ assertFalse(state.isConvertingFromDollars)
+ }
+
+ @Test
+ fun `isConvertingToDollars is false when a conversion lands in another token`() {
+ val state = SwapViewModel.State(
+ purpose = SwapPurpose.Convert(mint = Mint.usdf, destinationMint = mint()),
+ )
+ assertFalse(state.isConvertingToDollars)
+ assertTrue(state.isConvertingFromDollars)
+ }
+
+ @Test
+ fun `convert direction flags are false for non-convert purposes`() {
+ val state = SwapViewModel.State(purpose = SwapPurpose.Sell(Mint.usdf))
+ assertFalse(state.isConvertingToDollars)
+ assertFalse(state.isConvertingFromDollars)
+ }
}