Skip to content

Commit 87257f1

Browse files
committed
feat: add token/currency discovery
Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent 3f197aa commit 87257f1

22 files changed

Lines changed: 985 additions & 60 deletions

File tree

apps/flipcash/app/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ dependencies {
187187
implementation(project(":apps:flipcash:features:tokens"))
188188
implementation(project(":apps:flipcash:features:transactions"))
189189
implementation(project(":apps:flipcash:features:bill-customization"))
190+
implementation(project(":apps:flipcash:features:discovery"))
190191

191192
implementation(project(":libs:crypto:solana"))
192193
implementation(project(":libs:datetime"))

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ import com.flipcash.app.advanced.AdvancedFeaturesScreen
2222
import com.flipcash.app.appsettings.AppSettingsScreen
2323
import com.flipcash.app.backupkey.BackupKeyScreen
2424
import com.flipcash.app.balance.BalanceScreen
25-
import com.flipcash.app.balance.PreloadBalance
2625
import com.flipcash.app.cash.CashScreen
2726
import com.flipcash.app.contact.verification.VerificationFlowScreen
2827
import com.flipcash.app.core.AppRoute
2928
import com.flipcash.app.currency.RegionSelectionScreen
3029
import com.flipcash.app.deposit.DepositScreen
30+
import com.flipcash.app.discovery.TokenDiscoveryScreen
3131
import com.flipcash.app.internal.ui.navigation.decorators.rememberNavMessagingEntryDecorator
3232
import com.flipcash.app.lab.LabsScreen
3333
import com.flipcash.app.lab.PreloadLabs
@@ -72,7 +72,6 @@ import dev.theolm.rinku.DeepLink
7272

7373
@Composable
7474
fun AppPreloads() {
75-
PreloadBalance()
7675
PreloadLabs()
7776
}
7877

@@ -124,6 +123,7 @@ fun appEntryProvider(
124123
TokenTxProcessingScreen(key.swapId, key.awaitExternalWallet)
125124
}
126125
annotatedEntry<AppRoute.Token.SellReceipt> { TokenSellReceiptScreen() }
126+
annotatedEntry<AppRoute.Token.Discovery> { TokenDiscoveryScreen() }
127127

128128
// Verification
129129
annotatedEntry<AppRoute.Verification> { key ->

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,11 @@ sealed interface AppRoute : NavKey, Parcelable {
121121

122122
@Serializable
123123
data object SellReceipt : Token
124-
}
125124

125+
@Serializable
126+
data object Discovery: AppRoute
127+
128+
}
126129
@Serializable
127130
@Parcelize
128131
sealed interface OnRamp : AppRoute {
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.flipcash.app.core.ui
2+
3+
import androidx.compose.animation.core.RepeatMode
4+
import androidx.compose.animation.core.animateFloat
5+
import androidx.compose.animation.core.infiniteRepeatable
6+
import androidx.compose.animation.core.rememberInfiniteTransition
7+
import androidx.compose.animation.core.tween
8+
import androidx.compose.foundation.background
9+
import androidx.compose.runtime.Composable
10+
import androidx.compose.runtime.getValue
11+
import androidx.compose.ui.Modifier
12+
import androidx.compose.ui.graphics.Color
13+
import androidx.compose.ui.graphics.Shape
14+
import com.getcode.theme.CodeTheme
15+
16+
@Composable
17+
fun Modifier.shimmer(
18+
shape: Shape = CodeTheme.shapes.medium
19+
): Modifier {
20+
val alpha = rememberShimmerAlpha()
21+
return this.background(Color.White.copy(alpha = alpha), shape)
22+
}
23+
24+
@Composable
25+
fun rememberShimmerAlpha(): Float {
26+
val transition = rememberInfiniteTransition()
27+
val alpha by transition.animateFloat(
28+
initialValue = 0.15f,
29+
targetValue = 0.35f,
30+
animationSpec = infiniteRepeatable(
31+
animation = tween(800),
32+
repeatMode = RepeatMode.Reverse,
33+
),
34+
)
35+
return alpha
36+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.flipcash.app.core.util
2+
3+
fun Number.abbreviated(): String {
4+
val value = toDouble()
5+
return when {
6+
value >= 1_000_000_000_000.0 -> "%.2fT".format(value / 1_000_000_000_000.0).trimTrailingZeros()
7+
value >= 1_000_000_000.0 -> "%.2fB".format(value / 1_000_000_000.0).trimTrailingZeros()
8+
value >= 1_000_000.0 -> "%.2fM".format(value / 1_000_000.0).trimTrailingZeros()
9+
value >= 1_000.0 -> "%.2fK".format(value / 1_000.0).trimTrailingZeros()
10+
else -> value.toLong().toString()
11+
}
12+
}
13+
14+
private fun String.trimTrailingZeros(): String {
15+
val suffix = last()
16+
return dropLast(1).trimEnd('0').trimEnd('.') + suffix
17+
}

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@
218218
<string name="title_tapBelowToAddCashWallet">You don’t have any cash yet.\nTap below to add cash to your wallet</string>
219219
<string name="title_noBalanceYet">No Balance Yet</string>
220220
<string name="description_noBalanceYet">Get another Flipcash user to give you some cash to get a balance</string>
221+
<string name="description_noBalanceYetDiscover">Buy your first currency to get started</string>
221222
<string name="action_dismiss">Dismiss</string>
222223
<string name="title_success">Success</string>
223224

@@ -439,4 +440,18 @@
439440

440441
<string name="action_retry">Retry</string>
441442
<string name="error_unableToLoadChartData">Unable To Load</string>
443+
444+
<string name="title_discoverCurrencies">Currencies</string>
445+
<string name="action_discoverCurrencies">Discover Currencies</string>
446+
<string name="title_discoverPopular">Popular</string>
447+
<string name="title_discoverNew">New</string>
448+
<string name="label_holders">Holders</string>
449+
<string name="label_holdersWithCount">%1$s Holders</string>
450+
<string name="title_discoverFailedToLoad">Couldn\'t Load Currencies</string>
451+
<string name="subtitle_discoverFailedToLoad">Check your connection and try again</string>
452+
<string name="title_discoverEmpty">No currencies found</string>
453+
<string name="error_discoverFailedToLoad">Check your connection and try again</string>
454+
<string name="subtitle_discoverEmptyNew">Check back soon for new currencies</string>
455+
<string name="subtitle_discoverEmptyPopular">No popular currencies right now</string>
456+
442457
</resources>

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

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ import androidx.compose.runtime.LaunchedEffect
77
import androidx.compose.ui.Alignment
88
import androidx.compose.ui.Modifier
99
import androidx.compose.ui.res.stringResource
10-
import androidx.hilt.navigation.compose.hiltViewModel
11-
import com.getcode.navigation.extensions.getActivityScopedViewModel
10+
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
1211
import com.flipcash.app.balance.internal.BalanceScreen
1312
import com.flipcash.app.balance.internal.BalanceViewModel
1413
import com.flipcash.app.core.AppRoute
@@ -40,7 +39,7 @@ fun BalanceScreen() {
4039
}
4140
)
4241

43-
val viewModel = getActivityScopedViewModel<BalanceViewModel>()
42+
val viewModel = hiltViewModel<BalanceViewModel>()
4443
val tokenViewModel = hiltViewModel<SelectTokenViewModel>()
4544
BalanceScreen(viewModel, tokenViewModel)
4645

@@ -72,9 +71,4 @@ fun BalanceScreen() {
7271
.launchIn(this)
7372
}
7473
}
75-
}
76-
77-
@Composable
78-
fun PreloadBalance() {
79-
val viewModel = getActivityScopedViewModel<BalanceViewModel>()
80-
}
74+
}

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

Lines changed: 45 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ import androidx.compose.foundation.background
44
import androidx.compose.foundation.layout.Arrangement
55
import androidx.compose.foundation.layout.Box
66
import androidx.compose.foundation.layout.Column
7+
import androidx.compose.foundation.layout.PaddingValues
78
import androidx.compose.foundation.layout.Row
89
import androidx.compose.foundation.layout.Spacer
910
import androidx.compose.foundation.layout.fillMaxWidth
1011
import androidx.compose.foundation.layout.padding
12+
import androidx.compose.foundation.shape.CircleShape
1113
import androidx.compose.material.Icon
1214
import androidx.compose.material.Text
1315
import androidx.compose.runtime.Composable
@@ -23,6 +25,7 @@ import androidx.compose.ui.text.style.TextAlign
2325
import androidx.compose.ui.tooling.preview.Preview
2426
import androidx.lifecycle.compose.collectAsStateWithLifecycle
2527
import com.flipcash.app.balance.internal.components.BalanceHeader
28+
import com.flipcash.app.balance.internal.components.CashReservesRow
2629
import com.flipcash.app.core.AppRoute
2730
import com.flipcash.app.core.money.formatted
2831
import com.flipcash.app.core.tokens.TokenPurpose
@@ -37,6 +40,8 @@ import com.getcode.opencode.model.financial.CurrencyCode
3740
import com.getcode.opencode.model.financial.Rate
3841
import com.getcode.theme.CodeTheme
3942
import com.getcode.ui.core.rememberedClickable
43+
import com.getcode.ui.theme.ButtonState
44+
import com.getcode.ui.theme.CodeButton
4045

4146
@Composable
4247
internal fun BalanceScreen(
@@ -95,53 +100,58 @@ private fun BalanceScreenContent(
95100

96101
Text(
97102
modifier = Modifier.fillMaxWidth(0.6f),
98-
text = stringResource(R.string.description_noBalanceYet),
103+
text = if (tokenState.discoveryEnabled) {
104+
stringResource(R.string.description_noBalanceYetDiscover)
105+
} else {
106+
stringResource(R.string.description_noBalanceYet)
107+
},
99108
style = CodeTheme.typography.textSmall,
100109
color = CodeTheme.colors.textSecondary,
101110
textAlign = TextAlign.Center,
102111
)
112+
113+
if (tokenState.discoveryEnabled) {
114+
CodeButton(
115+
onClick = {
116+
dispatchEvent(
117+
BalanceViewModel.Event.OpenScreen(AppRoute.Token.Discovery)
118+
)
119+
},
120+
modifier = Modifier.align(Alignment.CenterHorizontally),
121+
contentPadding = PaddingValues(),
122+
text = stringResource(R.string.action_discoverCurrencies),
123+
shape = CircleShape,
124+
buttonState = ButtonState.Filled
125+
)
126+
}
103127
}
104128
}
105129
},
106-
footer = { mint, reserves ->
107-
Row(
108-
modifier = Modifier
109-
.fillMaxWidth()
110-
.rememberedClickable {
130+
reserves = { mint, reserves ->
131+
CashReservesRow(reserves) {
132+
dispatchEvent(
133+
BalanceViewModel.Event.OpenScreen(
134+
AppRoute.Token.Info(mint)
135+
)
136+
)
137+
}
138+
},
139+
footer = if (tokenState.discoveryEnabled) {
140+
{
141+
CodeButton(
142+
modifier = Modifier
143+
.fillMaxWidth()
144+
.padding(horizontal = CodeTheme.dimens.inset),
145+
text = stringResource(R.string.action_discoverCurrencies),
146+
buttonState = ButtonState.Filled10,
147+
onClick = {
111148
dispatchEvent(
112-
BalanceViewModel.Event.OpenScreen(
113-
AppRoute.Token.Info(mint)
114-
)
149+
BalanceViewModel.Event.OpenScreen(AppRoute.Token.Discovery)
115150
)
116151
}
117-
.padding(
118-
vertical = CodeTheme.dimens.grid.x3,
119-
horizontal = CodeTheme.dimens.inset),
120-
verticalAlignment = Alignment.CenterVertically,
121-
) {
122-
Text(
123-
text = stringResource(R.string.title_cashReserves),
124-
style = CodeTheme.typography.screenTitle,
125-
color = CodeTheme.colors.textSecondary,
126-
)
127-
128-
Icon(
129-
modifier = Modifier
130-
.padding(start = CodeTheme.dimens.grid.x2),
131-
painter = painterResource(id = R.drawable.ic_chevron_right),
132-
contentDescription = null,
133-
tint = CodeTheme.colors.textSecondary,
134-
)
135-
136-
Spacer(Modifier.weight(1f))
137-
138-
Text(
139-
text = reserves.formatted(),
140-
style = CodeTheme.typography.screenTitle,
141-
color = CodeTheme.colors.textMain,
142152
)
143153
}
144-
},
154+
} else null,
145155
tokens = tokens,
146156
onTokenSelected = {
147157
dispatchEvent(

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

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,17 @@
11
package com.flipcash.app.balance.internal
22

33
import androidx.lifecycle.viewModelScope
4-
import com.flipcash.app.analytics.Analytics
5-
import com.flipcash.app.analytics.FlipcashAnalyticsService
64
import com.flipcash.app.core.AppRoute
7-
import com.flipcash.app.core.tokens.TokenPurpose
8-
import com.flipcash.app.onramp.ConfirmationEvent
9-
import com.flipcash.app.onramp.OnRampAmount
10-
import com.flipcash.app.onramp.OnRampAmountController
115
import com.flipcash.services.internal.model.thirdparty.OnRampProvider
12-
import com.flipcash.services.internal.model.thirdparty.OnRampType
136
import com.flipcash.services.user.AuthState
147
import com.flipcash.services.user.UserManager
158
import com.getcode.view.BaseViewModel2
169
import dagger.hilt.android.lifecycle.HiltViewModel
1710
import kotlinx.coroutines.flow.filter
18-
import kotlinx.coroutines.flow.filterIsInstance
19-
import kotlinx.coroutines.flow.flatMapLatest
2011
import kotlinx.coroutines.flow.launchIn
2112
import kotlinx.coroutines.flow.map
2213
import kotlinx.coroutines.flow.mapNotNull
2314
import kotlinx.coroutines.flow.onEach
24-
import kotlinx.coroutines.flow.take
2515
import javax.inject.Inject
2616

2717
@HiltViewModel
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package com.flipcash.app.balance.internal.components
2+
3+
import androidx.compose.foundation.layout.Row
4+
import androidx.compose.foundation.layout.Spacer
5+
import androidx.compose.foundation.layout.fillMaxWidth
6+
import androidx.compose.foundation.layout.padding
7+
import androidx.compose.material.Icon
8+
import androidx.compose.material.Text
9+
import androidx.compose.runtime.Composable
10+
import androidx.compose.ui.Alignment
11+
import androidx.compose.ui.Modifier
12+
import androidx.compose.ui.res.painterResource
13+
import androidx.compose.ui.res.stringResource
14+
import com.flipcash.app.core.money.formatted
15+
import com.flipcash.features.balance.R
16+
import com.getcode.opencode.model.financial.LocalFiat
17+
import com.getcode.theme.CodeTheme
18+
import com.getcode.ui.core.rememberedClickable
19+
20+
@Composable
21+
internal fun CashReservesRow(
22+
reserves: LocalFiat,
23+
onClick: () -> Unit
24+
) {
25+
Row(
26+
modifier = Modifier
27+
.fillMaxWidth()
28+
.rememberedClickable {
29+
onClick()
30+
}
31+
.padding(
32+
vertical = CodeTheme.dimens.grid.x3,
33+
horizontal = CodeTheme.dimens.inset
34+
),
35+
verticalAlignment = Alignment.CenterVertically,
36+
) {
37+
Text(
38+
text = stringResource(R.string.title_cashReserves),
39+
style = CodeTheme.typography.screenTitle,
40+
color = CodeTheme.colors.textSecondary,
41+
)
42+
43+
Icon(
44+
modifier = Modifier
45+
.padding(start = CodeTheme.dimens.grid.x2),
46+
painter = painterResource(id = R.drawable.ic_chevron_right),
47+
contentDescription = null,
48+
tint = CodeTheme.colors.textSecondary,
49+
)
50+
51+
Spacer(Modifier.weight(1f))
52+
53+
Text(
54+
text = reserves.formatted(),
55+
style = CodeTheme.typography.screenTitle,
56+
color = CodeTheme.colors.textMain,
57+
)
58+
}
59+
}

0 commit comments

Comments
 (0)