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(