diff --git a/apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/ui/CurrencyAppreciationLabel.kt b/apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/ui/CurrencyAppreciationLabel.kt
index 00d7a06d1b..b70c68e9f0 100644
--- a/apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/ui/CurrencyAppreciationLabel.kt
+++ b/apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/ui/CurrencyAppreciationLabel.kt
@@ -1,6 +1,7 @@
package com.flipcash.app.core.ui
import androidx.compose.foundation.background
+import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
@@ -15,13 +16,32 @@ import com.flipcash.app.core.money.formattedAppreciation
import com.flipcash.core.R
import com.getcode.opencode.model.financial.Fiat
import com.getcode.theme.CodeTheme
+import com.getcode.theme.White20
import com.getcode.theme.extraSmall
import com.getcode.ui.components.text.AnimatedNumberText
+/** Controls the visual treatment of the appreciation badge. */
+enum class AppreciationStyle {
+ /** Sentiment-tinted filled pill (green/red). Used in v1 balance and token-info screens. */
+ Classic,
+ /**
+ * Neutral bordered pill with secondary text colour. Used in the v2 wallet screen.
+ * Layout: `[signed delta in a bordered box] [label]`, both in secondary text.
+ */
+ Pill,
+}
+
+/**
+ * Displays a fiat appreciation/depreciation value with an optional label.
+ *
+ * [style] defaults to [AppreciationStyle.Classic] so existing call sites are unchanged.
+ * Pass [AppreciationStyle.Pill] in the v2 wallet header for the neutral bordered treatment.
+ */
@Composable
fun CurrencyAppreciationLabel(
appreciation: Fiat,
modifier: Modifier = Modifier,
+ style: AppreciationStyle = AppreciationStyle.Classic,
) {
val hasAppreciation = appreciation.toDouble() >= 0
val changeColor = if (hasAppreciation) {
@@ -35,25 +55,49 @@ fun CurrencyAppreciationLabel(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(CodeTheme.dimens.grid.x1),
) {
+ val pillModifier = when (style) {
+ AppreciationStyle.Pill ->
+ Modifier
+ .border(
+ width = CodeTheme.dimens.border,
+ color = White20,
+ shape = MaterialTheme.shapes.extraSmall,
+ )
+ .padding(
+ vertical = 3.dp,
+ horizontal = CodeTheme.dimens.grid.x1,
+ )
+ AppreciationStyle.Classic ->
+ Modifier
+ .background(
+ color = changeColor.copy(0.20f),
+ shape = MaterialTheme.shapes.extraSmall,
+ )
+ .padding(
+ vertical = 2.dp,
+ horizontal = CodeTheme.dimens.grid.x1,
+ )
+ }
AnimatedNumberText(
- modifier = Modifier
- .background(
- color = changeColor.copy(0.20f),
- shape = MaterialTheme.shapes.extraSmall,
- )
- .padding(
- vertical = 2.dp,
- horizontal = CodeTheme.dimens.grid.x1
- ),
+ modifier = pillModifier,
value = appreciation.formattedAppreciation(),
style = CodeTheme.typography.textSmall,
- color = changeColor,
+ color = if (style == AppreciationStyle.Pill) CodeTheme.colors.textSecondary else changeColor,
)
- val label = if (hasAppreciation) {
- stringResource(R.string.label_fromCurrencyAppreciation)
- } else {
- stringResource(R.string.label_fromCurrencyDepreciation)
+ val label = when (style) {
+ AppreciationStyle.Pill ->
+ if (hasAppreciation) {
+ stringResource(R.string.label_fromAppreciation)
+ } else {
+ stringResource(R.string.label_fromDepreciation)
+ }
+ AppreciationStyle.Classic ->
+ if (hasAppreciation) {
+ stringResource(R.string.label_fromCurrencyAppreciation)
+ } else {
+ stringResource(R.string.label_fromCurrencyDepreciation)
+ }
}
Text(
text = label,
@@ -61,4 +105,4 @@ fun CurrencyAppreciationLabel(
color = CodeTheme.colors.textSecondary,
)
}
-}
\ No newline at end of file
+}
diff --git a/apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/ui/TokenCardStack.kt b/apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/ui/TokenCardStack.kt
index 3667e156b6..1dd681c47a 100644
--- a/apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/ui/TokenCardStack.kt
+++ b/apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/ui/TokenCardStack.kt
@@ -20,6 +20,9 @@ import kotlin.collections.forEach
* the viewport top (`-itemOffset`); it is read in the **placement** phase so scrolling only re-places
* the cards and never re-measures/re-composes them. [pinInset] holds the collapsing deck below top
* chrome (e.g. the status bar). Cards are drawn front-to-back so the last (highest-value) sits on top.
+ *
+ * When [collapsedReveal] is 0 (the default), back cards collapse completely behind the front card —
+ * no slivers are visible at rest — matching the iOS wallet card-stack behaviour.
*/
@Composable
fun TokenCardStack(
@@ -27,7 +30,7 @@ fun TokenCardStack(
modifier: Modifier = Modifier,
cardHeight: Dp = 224.dp,
fannedReveal: Dp = 64.dp,
- collapsedReveal: Dp = 12.dp,
+ collapsedReveal: Dp = 0.dp,
pinInset: Dp = 0.dp,
scrolledPast: () -> Float = { 0f },
onCardClick: (TokenWithLocalizedBalance) -> Unit = {},
diff --git a/apps/flipcash/core/src/main/res/values/strings.xml b/apps/flipcash/core/src/main/res/values/strings.xml
index a866509dbe..b81693de38 100644
--- a/apps/flipcash/core/src/main/res/values/strings.xml
+++ b/apps/flipcash/core/src/main/res/values/strings.xml
@@ -590,6 +590,8 @@
from currency appreciation
from currency depreciation
+ from appreciation
+ from depreciation
This Will Take a Minute
This transaction typically takes about a minute. You may leave the app while it completes
diff --git a/apps/flipcash/features/balance/src/main/kotlin/com/flipcash/app/balance/internal/WalletScreenContent.kt b/apps/flipcash/features/balance/src/main/kotlin/com/flipcash/app/balance/internal/WalletScreenContent.kt
index 85be9e60d8..739c489a66 100644
--- a/apps/flipcash/features/balance/src/main/kotlin/com/flipcash/app/balance/internal/WalletScreenContent.kt
+++ b/apps/flipcash/features/balance/src/main/kotlin/com/flipcash/app/balance/internal/WalletScreenContent.kt
@@ -30,6 +30,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.flipcash.app.core.AppRoute
+import com.flipcash.app.core.ui.AppreciationStyle
import com.flipcash.app.core.ui.TokenCardStack
import com.flipcash.app.balance.internal.components.BalanceHeader
import com.flipcash.app.balance.internal.components.OnboardingFunnel
@@ -82,15 +83,15 @@ internal fun WalletScreenContent(
)
) {
item {
- Spacer(Modifier.height(CodeTheme.dimens.grid.x15))
- }
-
- item {
+ // v2 wallet header: 96 dp top / 44 dp bottom per Figma node 8966:1578.
BalanceHeader(
modifier = Modifier
.fillMaxWidth(),
balance = tokenState.totalBalance,
appreciation = tokenState.aggregateAppreciation,
+ topPadding = 96.dp,
+ bottomPadding = 44.dp,
+ appreciationStyle = AppreciationStyle.Pill,
) {
dispatchEvent(WalletViewModel.Event.OpenCurrencySelection)
}
diff --git a/apps/flipcash/features/balance/src/main/kotlin/com/flipcash/app/balance/internal/components/BalanceHeader.kt b/apps/flipcash/features/balance/src/main/kotlin/com/flipcash/app/balance/internal/components/BalanceHeader.kt
index 110e8df2b0..0ad8022c22 100644
--- a/apps/flipcash/features/balance/src/main/kotlin/com/flipcash/app/balance/internal/components/BalanceHeader.kt
+++ b/apps/flipcash/features/balance/src/main/kotlin/com/flipcash/app/balance/internal/components/BalanceHeader.kt
@@ -7,6 +7,8 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.Dp
+import com.flipcash.app.core.ui.AppreciationStyle
import com.flipcash.app.core.ui.CurrencyAppreciationLabel
import com.getcode.opencode.compose.LocalExchange
import com.getcode.opencode.model.financial.LocalFiat
@@ -14,18 +16,31 @@ import com.getcode.theme.CodeTheme
import com.getcode.ui.components.text.AmountArea
import com.getcode.ui.theme.CodeCircularProgressIndicator
+/**
+ * Balance header showing the total portfolio value and an optional appreciation pill.
+ *
+ * [topPadding] and [bottomPadding] allow callers to override the default symmetric vertical padding
+ * (both default to [CodeTheme.dimens.grid.x9], preserving v1 behaviour). The v2 wallet screen uses
+ * asymmetric values (~96 dp top, ~44 dp bottom) to match the Figma spec (node 8966:1578).
+ *
+ * [appreciationStyle] defaults to [AppreciationStyle.Classic] (sentiment-tinted fill); pass
+ * [AppreciationStyle.Pill] for the v2 neutral bordered treatment.
+ */
@Composable
internal fun BalanceHeader(
balance: LocalFiat?,
appreciation: LocalFiat?,
modifier: Modifier = Modifier,
+ topPadding: Dp = CodeTheme.dimens.grid.x9,
+ bottomPadding: Dp = CodeTheme.dimens.grid.x9,
+ appreciationStyle: AppreciationStyle = AppreciationStyle.Classic,
onClick: () -> Unit
) {
val exchange = LocalExchange.current
Column(
modifier = modifier
.padding(horizontal = CodeTheme.dimens.inset)
- .padding(vertical = CodeTheme.dimens.grid.x9),
+ .padding(top = topPadding, bottom = bottomPadding),
horizontalAlignment = Alignment.CenterHorizontally,
) {
if (balance == null) {
@@ -45,8 +60,11 @@ internal fun BalanceHeader(
)
if (appreciation != null) {
- CurrencyAppreciationLabel(appreciation.nativeAmount)
+ CurrencyAppreciationLabel(
+ appreciation = appreciation.nativeAmount,
+ style = appreciationStyle,
+ )
}
}
}
-}
\ No newline at end of file
+}