From b83ed04e597776359b248fe9078bbcd71bb9e925 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Sat, 2 May 2026 12:42:11 -0400 Subject: [PATCH] feat(tokens/info): hide balance and fully expand currency info when user doesn't have a balance for the token Signed-off-by: Brandon McAnsh --- .../app/tokens/internal/TokenInfoScreen.kt | 33 ++++++++++++------- .../internal/components/info/TokenDetails.kt | 1 + .../flipcash/app/featureflags/FeatureFlag.kt | 11 +++++++ .../flipcash/app/tokens/TokenCoordinator.kt | 7 ++++ .../app/tokens/ui/TokenInfoViewModel.kt | 15 +++++++++ 5 files changed, 55 insertions(+), 12 deletions(-) diff --git a/apps/flipcash/features/tokens/src/main/kotlin/com/flipcash/app/tokens/internal/TokenInfoScreen.kt b/apps/flipcash/features/tokens/src/main/kotlin/com/flipcash/app/tokens/internal/TokenInfoScreen.kt index 3fc1ba700..f93318fc0 100644 --- a/apps/flipcash/features/tokens/src/main/kotlin/com/flipcash/app/tokens/internal/TokenInfoScreen.kt +++ b/apps/flipcash/features/tokens/src/main/kotlin/com/flipcash/app/tokens/internal/TokenInfoScreen.kt @@ -1,5 +1,6 @@ package com.flipcash.app.tokens.internal +import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -134,20 +135,28 @@ private fun TokenInfoScreen( } is Loadable.Loaded -> { item { - TokenBalance( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = CodeTheme.dimens.inset), - balance = state.balance.nativeAmount, - appreciation = state.appreciation?.nativeAmount?.takeIf { state.showAppreciation }, - onClick = { - dispatch( - TokenInfoViewModel.Event.OpenScreen( - AppRoute.Main.RegionSelection(kind = RegionSelectionKind.Balance) - ) + AnimatedContent( + targetState = !state.minimalUi + ) { holds -> + if (holds) { + TokenBalance( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = CodeTheme.dimens.inset), + balance = state.balance.nativeAmount, + appreciation = state.appreciation?.nativeAmount?.takeIf { state.showAppreciation }, + onClick = { + dispatch( + TokenInfoViewModel.Event.OpenScreen( + AppRoute.Main.RegionSelection(kind = RegionSelectionKind.Balance) + ) + ) + } ) + } else { + Spacer(Modifier.height(CodeTheme.dimens.grid.x10)) } - ) + } } if (!state.isCashReserve && state.showTransactionHistory) { diff --git a/apps/flipcash/features/tokens/src/main/kotlin/com/flipcash/app/tokens/internal/components/info/TokenDetails.kt b/apps/flipcash/features/tokens/src/main/kotlin/com/flipcash/app/tokens/internal/components/info/TokenDetails.kt index f848bf2f9..137c3aafc 100644 --- a/apps/flipcash/features/tokens/src/main/kotlin/com/flipcash/app/tokens/internal/components/info/TokenDetails.kt +++ b/apps/flipcash/features/tokens/src/main/kotlin/com/flipcash/app/tokens/internal/components/info/TokenDetails.kt @@ -81,6 +81,7 @@ internal fun TokenDetailsSection( text = state.token.dataOrNull?.description.orEmpty(), style = CodeTheme.typography.textMedium, color = CodeTheme.colors.textSecondary, + isExpandable = !state.minimalUi, isExpanded = state.descriptionExpanded, contentPadding = PaddingValues(horizontal = CodeTheme.dimens.inset) ) { diff --git a/apps/flipcash/shared/featureflags/src/main/kotlin/com/flipcash/app/featureflags/FeatureFlag.kt b/apps/flipcash/shared/featureflags/src/main/kotlin/com/flipcash/app/featureflags/FeatureFlag.kt index 86436bc1c..ab0461c55 100644 --- a/apps/flipcash/shared/featureflags/src/main/kotlin/com/flipcash/app/featureflags/FeatureFlag.kt +++ b/apps/flipcash/shared/featureflags/src/main/kotlin/com/flipcash/app/featureflags/FeatureFlag.kt @@ -135,6 +135,15 @@ sealed interface FeatureFlag { override val persistLogOut: Boolean = false } + @FeatureFlagMarker + data object HideUnownedTokenBalances: FeatureFlag { + override val key: String = "hide_unowned_balances_enabled" + override val default: Boolean = false + override val launched: Boolean = false + override val visible: Boolean = true + override val persistLogOut: Boolean = false + } + companion object { val entries: List get() = FeatureFlagEntries.entries @@ -162,6 +171,7 @@ val FeatureFlag.title: String FeatureFlag.TokenDiscovery -> "Token Discovery" FeatureFlag.CurrencyCreator -> "Currency Creator" FeatureFlag.BillTextures -> "Bill Textures" + FeatureFlag.HideUnownedTokenBalances -> "Hide Balance for Unowned Tokens In Info" } val FeatureFlag.message: String @@ -180,6 +190,7 @@ val FeatureFlag.message: String FeatureFlag.TokenDiscovery -> "When enabled, you'll gain access to leaderboards for tokens and discovery" FeatureFlag.CurrencyCreator -> "When enabled, you'll gain access to create new currencies" FeatureFlag.BillTextures -> "When enabled, you'll gain the ability to select textures for bills during currency creation" + FeatureFlag.HideUnownedTokenBalances -> "When enabled, the balance header will be hidden for tokens you don't hold in Currency Info" } diff --git a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/TokenCoordinator.kt b/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/TokenCoordinator.kt index 5ed7898ed..b73857a7b 100644 --- a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/TokenCoordinator.kt +++ b/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/TokenCoordinator.kt @@ -218,6 +218,13 @@ class TokenCoordinator @Inject constructor( // endregion + // region Public API - Account check + fun holds(mint: Mint): Boolean { + return accountController.hasAccountFor(mint) + } + + // endregion + // region Public API — Token Metadata (implements TokenMetadataProvider) /** diff --git a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/ui/TokenInfoViewModel.kt b/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/ui/TokenInfoViewModel.kt index 49dddd44a..126fa885b 100644 --- a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/ui/TokenInfoViewModel.kt +++ b/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/ui/TokenInfoViewModel.kt @@ -71,7 +71,15 @@ class TokenInfoViewModel @Inject constructor( val descriptionExpanded: Boolean = false, val historicalMarketCapData: Map>> = emptyMap(), val selectedPeriod: Period = Period.All, + val hideBalanceWhenUnowned: Boolean = false, ) { + val minimalUi: Boolean + get() { + if (!hideBalanceWhenUnowned) return false + if (balance.nativeAmount.hasDisplayableValue) return false + return true + } + val canSell: Boolean get() = balance.underlyingTokenAmount.valueNonZero() @@ -91,6 +99,7 @@ class TokenInfoViewModel @Inject constructor( val data: Loadable> ) : Event + data class HideBalanceWhenUnowned(val enabled: Boolean): Event data class OnMarketCapPeriodSelected(val period: Period) : Event data class OnBalanceUpdated(val balance: LocalFiat) : Event data class OnAppreciatedEnabled(val enabled: Boolean) : Event @@ -110,6 +119,11 @@ class TokenInfoViewModel @Inject constructor( dispatchEvent(Event.MarketCapChartEnabled(it)) }.launchIn(viewModelScope) + features.observe(FeatureFlag.HideUnownedTokenBalances) + .onEach { + dispatchEvent(Event.HideBalanceWhenUnowned(it)) + }.launchIn(viewModelScope) + eventFlow .filterIsInstance() .onEach { dispatchEvent(Event.OnTokenChanged(Loadable.Loading())) } @@ -315,6 +329,7 @@ class TokenInfoViewModel @Inject constructor( is Event.MarketCapChartEnabled -> { state -> state.copy(marketCapChartEnabled = event.enabled) } is Event.OnMintProvided -> { state -> state.copy(mint = event.mint) } is Event.OnTokenChanged -> { state -> state.copy(token = event.token) } + is Event.HideBalanceWhenUnowned -> { state -> state.copy(hideBalanceWhenUnowned = event.enabled) } is Event.OnMarketCapChanged -> { state -> state.copy(marketCap = event.mcap) } is Event.OnBalanceUpdated -> { state -> state.copy(balance = event.balance) } is Event.OnAppreciationUpdated -> { state -> state.copy(appreciation = event.amount) }