Skip to content

Commit 2a6d445

Browse files
committed
feat: add UI for buy/sell
Signed-off-by: Brandon McAnsh <[email protected]>
1 parent ab4a8ff commit 2a6d445

File tree

44 files changed

+1841
-203
lines changed

Some content is hidden

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

44 files changed

+1841
-203
lines changed

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,11 @@ import com.flipcash.app.pools.create.PoolQuestionScreen
3434
import com.flipcash.app.purchase.PurchaseAccountScreen
3535
import com.flipcash.app.scanner.ScannerScreen
3636
import com.flipcash.app.shareapp.ShareAppScreen
37-
import com.flipcash.app.tokens.SelectTokenScreen
37+
import com.flipcash.app.tokens.BuySellFlow
38+
import com.flipcash.app.tokens.TokenBuySellEntryScreen
39+
import com.flipcash.app.tokens.TokenSelectScreen
3840
import com.flipcash.app.tokens.TokenInfoScreen
41+
import com.flipcash.app.tokens.TokenSellReceiptScreen
3942
import com.flipcash.app.transactions.TransactionHistoryScreen
4043
import com.flipcash.app.transfers.TransferInformationalScreen
4144
import com.flipcash.app.withdrawal.WithdrawalConfirmationScreen
@@ -87,8 +90,17 @@ internal fun AppScreenContent(content: @Composable () -> Unit) {
8790
TransactionHistoryScreen(it.mint)
8891
}
8992

93+
register<AppRoute.Token.SwapTransact> {
94+
BuySellFlow.start()
95+
TokenBuySellEntryScreen(it.purpose)
96+
}
97+
98+
register<AppRoute.Token.SellReceipt> {
99+
TokenSellReceiptScreen()
100+
}
101+
90102
register<AppRoute.Sheets.TokenSelection> {
91-
SelectTokenScreen(it.purpose)
103+
TokenSelectScreen(it.purpose)
92104
}
93105

94106
register<AppRoute.Sheets.Wallet> {

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import cafe.adriel.voyager.core.registry.ScreenProvider
55
import com.flipcash.app.core.money.RegionSelectionKind
66
import com.flipcash.app.core.navigation.DeeplinkType
77
import com.flipcash.app.core.tokens.TokenPurpose
8+
import com.flipcash.app.core.tokens.TokenSwapPurpose
89
import com.flipcash.app.core.transfers.TransferDirection
910
import com.getcode.ed25519.Ed25519
1011
import com.getcode.opencode.model.core.ID
@@ -66,6 +67,8 @@ sealed interface AppRoute : ScreenProvider, Parcelable {
6667
sealed interface Token: AppRoute {
6768
data class Info(val mint: Mint): Token
6869
data class Transactions(val mint: Mint): Token
70+
data class SwapTransact(val purpose: TokenSwapPurpose): Token
71+
data object SellReceipt: Token
6972
}
7073

7174
@Parcelize

apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/onramp/deeplinks/WalletDeeplinkConnectionResult.kt

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import com.flipcash.app.core.AppRoute
55
import com.getcode.ed25519.Ed25519
66
import com.getcode.opencode.model.core.ID
77
import com.getcode.opencode.utils.base64
8+
import com.getcode.opencode.utils.base64UrlSafe
89
import com.getcode.solana.keys.Mint
910
import com.getcode.solana.keys.PublicKey
1011
import com.getcode.solana.keys.base58
@@ -41,6 +42,9 @@ sealed class OnRampDeeplinkOrigin: Parcelable {
4142
@Parcelize
4243
data object Wallet: OnRampDeeplinkOrigin()
4344

45+
@Parcelize
46+
data class TokenInfo(val mint: Mint): OnRampDeeplinkOrigin()
47+
4448
@Parcelize
4549
data object Reserves: OnRampDeeplinkOrigin()
4650

@@ -55,8 +59,9 @@ sealed class OnRampDeeplinkOrigin: Parcelable {
5559
is PoolWithId -> "pool-id_${id.base58}"
5660
is PoolWithRendezvous -> "pool-seed_${keyPair.seed.base64}"
5761
Menu -> "menu"
58-
is Give -> "give-${tokenAddress?.base58()}"
62+
is Give -> "give-${tokenAddress?.base58()?.base64UrlSafe}"
5963
Wallet -> "wallet"
64+
is TokenInfo -> "token-${mint.base58().base64UrlSafe}"
6065
Reserves -> "reserves"
6166
}.lowercase()
6267
}
@@ -72,7 +77,7 @@ sealed class OnRampDeeplinkOrigin: Parcelable {
7277
}
7378
is AppRoute.Sheets.Wallet -> Wallet
7479
is AppRoute.Token.Info -> {
75-
if (route.mint == Mint.usdc) Reserves else null
80+
if (route.mint == Mint.usdc) Reserves else TokenInfo(route.mint)
7681
}
7782

7883
else -> null
@@ -83,14 +88,22 @@ sealed class OnRampDeeplinkOrigin: Parcelable {
8388
return when {
8489
value == "menu" -> Menu
8590
value?.startsWith("give-") == true -> {
86-
val tokenAddress = value.removePrefix("give-")
91+
val tokenAddress = value.removePrefix("give-").decodeBase64().base58
8792
val mint = runCatching {
8893
PublicKey.fromBase58(tokenAddress)
8994
}.getOrNull() ?: return null
9095
Give(mint)
9196
}
9297
value == "wallet" -> Wallet
9398
value == "reserves" -> Reserves
99+
value?.startsWith("token-") == true -> {
100+
val mintString = value.removePrefix("token-").decodeBase64().base58
101+
val mint = runCatching {
102+
PublicKey.fromBase58(mintString)
103+
}.onFailure { it.printStackTrace() }.getOrNull() ?: return null
104+
105+
TokenInfo(mint)
106+
}
94107
value?.startsWith("pool-") == true -> {
95108
val idStringWithPrefix = value.removePrefix("pool-")
96109
val splits = idStringWithPrefix.split("_")
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package com.flipcash.app.core.onramp.ui
2+
3+
import androidx.compose.foundation.Image
4+
import androidx.compose.foundation.layout.Box
5+
import androidx.compose.foundation.layout.fillMaxSize
6+
import androidx.compose.foundation.layout.padding
7+
import androidx.compose.foundation.text.InlineTextContent
8+
import androidx.compose.foundation.text.appendInlineContent
9+
import androidx.compose.runtime.Composable
10+
import androidx.compose.ui.Alignment
11+
import androidx.compose.ui.Modifier
12+
import androidx.compose.ui.graphics.Color
13+
import androidx.compose.ui.graphics.ColorFilter
14+
import androidx.compose.ui.res.painterResource
15+
import androidx.compose.ui.res.stringResource
16+
import androidx.compose.ui.text.Placeholder
17+
import androidx.compose.ui.text.PlaceholderVerticalAlign
18+
import androidx.compose.ui.text.buildAnnotatedString
19+
import androidx.compose.ui.unit.dp
20+
import androidx.compose.ui.unit.sp
21+
import com.flipcash.core.R
22+
import com.flipcash.services.internal.model.thirdparty.OnRampProvider
23+
import com.getcode.theme.CodeTheme
24+
25+
@Composable
26+
fun buildExternalWalletButtonLabel(
27+
prefix: String,
28+
provider: OnRampProvider.UsesDeeplinks,
29+
iconColor: Color
30+
): AnnotatedButtonLabel {
31+
val (title, icon) = when (provider) {
32+
OnRampProvider.Backpack -> stringResource(R.string.label_backpack) to painterResource(R.drawable.ic_backpack_wallet)
33+
OnRampProvider.Phantom -> stringResource(R.string.label_phantom) to painterResource(R.drawable.ic_phantom_wallet)
34+
OnRampProvider.Solflare -> stringResource(R.string.label_solflare) to painterResource(R.drawable.ic_solflare_wallet)
35+
}
36+
37+
return buildAnnotatedString {
38+
append(prefix)
39+
appendInlineContent("[icon]", alternateText = " ")
40+
append(title)
41+
} to mapOf(
42+
"[icon]" to InlineTextContent(
43+
placeholder = Placeholder(
44+
width = 25.sp,
45+
height = 14.sp,
46+
placeholderVerticalAlign = PlaceholderVerticalAlign.TextCenter
47+
),
48+
children = {
49+
Box(
50+
modifier = Modifier.fillMaxSize(),
51+
contentAlignment = Alignment.Center
52+
) {
53+
Image(
54+
modifier = Modifier.padding(
55+
start = CodeTheme.dimens.staticGrid.x1 + 2.dp,
56+
end = CodeTheme.dimens.staticGrid.x1
57+
),
58+
painter = icon,
59+
colorFilter = ColorFilter.tint(iconColor),
60+
contentDescription = null
61+
)
62+
}
63+
}
64+
)
65+
)
66+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.flipcash.app.core.onramp.ui
2+
3+
import androidx.compose.foundation.text.InlineTextContent
4+
import androidx.compose.runtime.Composable
5+
import androidx.compose.ui.graphics.Color
6+
import androidx.compose.ui.text.AnnotatedString
7+
import com.flipcash.services.internal.model.thirdparty.OnRampProvider
8+
import com.getcode.ui.theme.ButtonState
9+
import com.getcode.ui.theme.getButtonColors
10+
11+
typealias AnnotatedButtonLabel = Pair<AnnotatedString, Map<String, InlineTextContent>>
12+
13+
@Composable
14+
fun buildPhantomButtonLabel(
15+
prefix: String,
16+
iconColor: Color = getButtonColors(
17+
true,
18+
ButtonState.Filled
19+
).contentColor(true).value
20+
): AnnotatedButtonLabel {
21+
return buildExternalWalletButtonLabel(prefix, OnRampProvider.Phantom, iconColor)
22+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.flipcash.app.core.tokens
2+
3+
import android.os.Parcelable
4+
import com.getcode.solana.keys.Mint
5+
import kotlinx.parcelize.Parcelize
6+
7+
/**
8+
* Represents the intent or specific action behind a token swap operation.
9+
*
10+
* This sealed interface defines the possible directions of a trade, carrying the necessary context
11+
* (such as the specific [Mint]) required to initialize or execute the swap.
12+
*
13+
* @see Buy Represents a purchase intent (e.g., swapping base currency for a specific token).
14+
* @see Sell Represents a liquidation intent (e.g., swapping a specific token back to base currency).
15+
*/
16+
@Parcelize
17+
sealed interface TokenSwapPurpose : Parcelable {
18+
sealed interface BalanceIncrease
19+
sealed interface BalanceDecrease
20+
data class Buy(val mint: Mint) : TokenSwapPurpose, BalanceIncrease
21+
data class FundWithWallet(val mint: Mint): TokenSwapPurpose, BalanceIncrease
22+
data class Sell(val mint: Mint) : TokenSwapPurpose, BalanceDecrease
23+
// data class Swap(val from: Mint, val to: Mint) : TokenSwapPurpose
24+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package com.flipcash.app.core.ui
2+
3+
import androidx.compose.foundation.layout.Arrangement
4+
import androidx.compose.foundation.layout.Row
5+
import androidx.compose.foundation.text.InlineTextContent
6+
import androidx.compose.material.Text
7+
import androidx.compose.runtime.Composable
8+
import androidx.compose.ui.Alignment
9+
import androidx.compose.ui.Modifier
10+
import androidx.compose.ui.text.AnnotatedString
11+
import com.getcode.theme.CodeTheme
12+
13+
@Composable
14+
fun ReceiptLineItem(
15+
label: String,
16+
amount: String,
17+
modifier: Modifier = Modifier,
18+
) {
19+
Row(
20+
modifier = modifier,
21+
horizontalArrangement = Arrangement.SpaceBetween,
22+
verticalAlignment = Alignment.CenterVertically
23+
) {
24+
Text(
25+
text = AnnotatedString(label),
26+
inlineContent = emptyMap(),
27+
style = CodeTheme.typography.textSmall,
28+
color = CodeTheme.colors.textSecondary,
29+
)
30+
Text(
31+
text = amount,
32+
style = CodeTheme.typography.textMedium,
33+
color = CodeTheme.colors.textMain,
34+
)
35+
}
36+
}
37+
38+
@Composable
39+
fun ReceiptLineItem(
40+
label: AnnotatedString,
41+
amount: String,
42+
modifier: Modifier = Modifier,
43+
inlineContentMap: Map<String, InlineTextContent> = mapOf(),
44+
) {
45+
Row(
46+
modifier = modifier,
47+
horizontalArrangement = Arrangement.SpaceBetween,
48+
verticalAlignment = Alignment.CenterVertically
49+
) {
50+
Text(
51+
text = label,
52+
inlineContent = inlineContentMap,
53+
style = CodeTheme.typography.textSmall,
54+
color = CodeTheme.colors.textSecondary,
55+
)
56+
Text(
57+
text = amount,
58+
style = CodeTheme.typography.textMedium,
59+
color = CodeTheme.colors.textMain,
60+
)
61+
}
62+
}

0 commit comments

Comments
 (0)