diff --git a/apps/flipcash/core/src/main/res/values/strings.xml b/apps/flipcash/core/src/main/res/values/strings.xml
index 573d62f2a7..73ab052914 100644
--- a/apps/flipcash/core/src/main/res/values/strings.xml
+++ b/apps/flipcash/core/src/main/res/values/strings.xml
@@ -52,7 +52,6 @@
You do not have enough funds to complete this transaction. You can deposit more funds in Settings
You do not have enough funds to complete this withdrawal. Please try a smaller amount
- Enter up to %1$s
You can only give up to %1$s
Enter up to %1$s
diff --git a/apps/flipcash/features/cash/src/main/kotlin/com/flipcash/app/cash/internal/CashScreenContent.kt b/apps/flipcash/features/cash/src/main/kotlin/com/flipcash/app/cash/internal/CashScreenContent.kt
index 00cbdcb854..ad77a7d9be 100644
--- a/apps/flipcash/features/cash/src/main/kotlin/com/flipcash/app/cash/internal/CashScreenContent.kt
+++ b/apps/flipcash/features/cash/src/main/kotlin/com/flipcash/app/cash/internal/CashScreenContent.kt
@@ -1,17 +1,13 @@
package com.flipcash.app.cash.internal
import androidx.compose.runtime.Composable
-import com.flipcash.app.core.AppRoute
import com.flipcash.shared.amountentry.AmountEntryScreen
-import com.getcode.navigation.core.LocalCodeNavigator
@Composable
internal fun GiveScreenContent(viewModel: CashScreenViewModel) {
- val navigator = LocalCodeNavigator.current
-
AmountEntryScreen(
controller = viewModel.amountDelegate,
onConfirm = { viewModel.dispatchEvent(CashScreenViewModel.Event.OnGive) },
- onChangeCurrency = { navigator.push(AppRoute.Main.RegionSelection) },
+ largeHeader = true,
)
}
diff --git a/apps/flipcash/features/cash/src/main/kotlin/com/flipcash/app/cash/internal/CashScreenViewModel.kt b/apps/flipcash/features/cash/src/main/kotlin/com/flipcash/app/cash/internal/CashScreenViewModel.kt
index 5f0cb92a1e..cffeaf377a 100644
--- a/apps/flipcash/features/cash/src/main/kotlin/com/flipcash/app/cash/internal/CashScreenViewModel.kt
+++ b/apps/flipcash/features/cash/src/main/kotlin/com/flipcash/app/cash/internal/CashScreenViewModel.kt
@@ -81,7 +81,13 @@ internal class CashScreenViewModel @Inject constructor(
scope = viewModelScope,
style = AmountEntryStyle(
actionLabel = AmountEntryLabel.Plain(resources.getString(R.string.action_next)),
- infoHint = { resources.getString(R.string.subtitle_giveCashHint, it) },
+ // The v2 header carries no currency flag, so there's nothing left to tap to reach
+ // region selection. Changing currency lives on the wallet balance instead.
+ canChangeCurrency = false,
+ // States the ceiling the way Convert and the v2 Get do. The over-max line stays
+ // give-specific: the cap is the lower of the balance and the per-transaction send
+ // limit, and when the limit is what binds, "available" alone wouldn't explain it.
+ infoHint = { resources.getString(R.string.subtitle_amountAvailable, it) },
overMaxHint = { resources.getString(R.string.subtitle_giveCashHintLimitExceeded, it) },
),
loadingState = stateFlow.map { it.generatingBill }
@@ -267,8 +273,13 @@ internal class CashScreenViewModel @Inject constructor(
verifiedState = result.verifiedState,
)
- dispatchEvent(Event.UpdateLoadingState(loading = false, success = true))
- dispatchEvent(Event.PresentBill(bill))
+ // Hold the checkmark before the bill takes over. Dispatching success and the bill
+ // on the same frame drew the checkmark and replaced it in one pass, so the button
+ // went from spinner straight to the bill. Nothing resets the state afterwards:
+ // presenting the bill pops this screen, so the next Give builds a fresh view model.
+ dispatchSuccessThen(Event.UpdateLoadingState(loading = false, success = true)) {
+ dispatchEvent(Event.PresentBill(bill))
+ }
}.launchIn(viewModelScope)
eventFlow
diff --git a/apps/flipcash/shared/bills/src/main/kotlin/com/flipcash/app/bills/BillOverlay.kt b/apps/flipcash/shared/bills/src/main/kotlin/com/flipcash/app/bills/BillOverlay.kt
index ba81b38900..25d4ae3de9 100644
--- a/apps/flipcash/shared/bills/src/main/kotlin/com/flipcash/app/bills/BillOverlay.kt
+++ b/apps/flipcash/shared/bills/src/main/kotlin/com/flipcash/app/bills/BillOverlay.kt
@@ -4,6 +4,7 @@ import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.core.snap
import androidx.compose.animation.core.tween
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.layout.Box
@@ -146,12 +147,22 @@ fun BillOverlay(modifier: Modifier = Modifier) {
// When the tip modal is up, pin the tip card just above it: reserve the modal's height as
// bottom inset AND bottom-align the card (bias 0 = centered, 1 = bottom). Both animated so the
// card slides from centered down to just above the modal, and back, in lockstep with the modal.
- val tipModalUp = managementHeight > 0.dp && updatedBillState.bill is Scannable.TipCard
+ val isTipCard = updatedBillState.bill is Scannable.TipCard
+ val tipModalUp = managementHeight > 0.dp && isTipCard
val modalSpeed = ModalAnimationSpeed.Normal(updatedBillState.confirmationDelayMillis)
- val offset = if (updatedBillState.bill is Scannable.TipCard) CodeTheme.dimens.grid.x8 else CodeTheme.dimens.grid.x2
+ val offset = if (isTipCard) CodeTheme.dimens.grid.x8 else CodeTheme.dimens.grid.x2
+ // Only the tip card's inset is animated. Everywhere else the inset's one and only change is
+ // 0 -> the management row's measured height, which lands a frame after the bill composes;
+ // animating it slid the card up from behind the send/cancel pills half a second after it had
+ // already appeared over them. Snapping puts the card at its final position while the enter
+ // spring is still carrying it up from off-screen, so there is nothing to see.
val billBottomInset by animateDpAsState(
targetValue = managementHeight + offset,
- animationSpec = tween(durationMillis = modalSpeed.duration, delayMillis = modalSpeed.delay),
+ animationSpec = if (isTipCard) {
+ tween(durationMillis = modalSpeed.duration, delayMillis = modalSpeed.delay)
+ } else {
+ snap()
+ },
label = "billBottomInset",
)
val billVerticalBias by animateFloatAsState(