Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion apps/flipcash/core/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@
<string name="error_description_insufficientFunds">You do not have enough funds to complete this transaction. You can deposit more funds in Settings</string>
<string name="error_description_withdrawInsufficientFunds">You do not have enough funds to complete this withdrawal. Please try a smaller amount</string>

<string name="subtitle_giveCashHint">Enter up to %1$s</string>
<string name="subtitle_giveCashHintLimitExceeded">You can only give up to %1$s</string>

<string name="subtitle_sendHint">Enter up to %1$s</string>
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down
Loading