fix: block numberpad input exceeding max amount#908
Conversation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…r-max # Conflicts: # app/src/main/java/to/bitkit/ui/screens/transfer/SpendingAmountScreen.kt
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ae21007459
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 503470d080
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
PR ready for review |
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
@piotr-iohk could you approved after testing if you don't find issues? |
Matches the Android behavior from synonymdev/bitkit-android#908: a keypress blocked by the screen-specific cap now shows a short warning toast, with accessibilityIdentifier SendAmountExceededToast on the send-flow screens for E2E parity. Also replaces the onAppear + onChange cap-update duplication with a single onChange(initial: true) across the amount screens, per review feedback.


Closes #801
Follow-up to #870 (merged) that capped onchain send validation. That PR disabled the Continue button when an amount exceeded the balance, but users could still type whatever they wanted with no explanation of why the button was disabled — poor UX.
This PR brings the Android number pad to parity with the iOS work in synonymdev/bitkit-ios#584 and synonymdev/bitkit-ios#585.
Description
Prevents users from entering amounts above the allowed maximum on numberpad screens, using the same pattern as the React Native / iOS apps:
Applied to every amount-entry screen that has a contextual maximum:
availableAmountmaxWithdrawableSat(same Send screen)min(maxSendableSat, availableAmount), matching the MAX buttonmaxAllowedToSend(accounts for LSP fees)transferValues.maxLspBalanceReceive / CJIT amount screens are intentionally left uncapped — a receive amount has no balance ceiling.
Implementation
maxAmountfield (defaults toMAX_AMOUNT) andsetMaxAmount()setter toAmountInputViewModelAmountInputEffect.MaxExceededone-shot event viaSharedFlowso screens can show a toastsetMaxAmount(limit)viaLaunchedEffectso the limit stays in sync as fees/balances updateMaxExceededis only emitted when a dynamic limit is set (not on the global 999M sats cap)Toast.VISIBILITY_TIME_SHORT = 1500LconstantTwo behavioral fixes (matching iOS) on top of the original cap:
deletekeystrokes always apply so the user can reduce the amount instead of being stuck. Deleting no longer triggers the error flash or theMaxExceededtoast.0(or negative) cap is treated as "no cap", so the pad still accepts input at a zero balance (Continue just stays disabled) instead of freezing and toasting on every keystroke.The manual external funding screen was also migrated off its old toast-on-change validation in
ExternalNodeViewModelto the shared cap mechanism.Context-aware over-limit messages (follow-up)
The over-limit toast on the Send screen previously always read "Insufficient balance / The amount exceeds your available balance", which was misleading for LNURL flows where the binding limit is the invoice/withdraw maximum rather than the wallet balance. The toast text now matches the actual constraint:
maxSendableis lower than the available spending balance → "Amount Too High / The amount exceeds this invoice's maximum."maxWithdrawable→ "Amount Too High / The amount exceeds the maximum you can withdraw."Added string resources
wallet__lnurl_pay__error_max__{title,description}andwallet__lnurl_w_error_max__{title,description}.Fresh limit in over-limit toasts (follow-up)
On the Transfer → Spending screens the over-limit toast formats the limit into its message (
"… {amount}"). The toast is fired from a long-livedLaunchedEffect(Unit)collector, which could format a stale value (often the initial0) captured before the limit finished loading — so a correctly-blocked keypress could show a "₿ 0" maximum. Both screens now read the current limit viarememberUpdatedStatebefore building the toast (SpendingAmountScreen,SpendingAdvancedScreen)."Available" matches the sendable max on Transfer to Spending (follow-up)
On the Transfer to Spending screen the "Available" row showed the gross on-chain balance while the MAX button and input cap used the lower after-LSP-fee amount, so the displayed number didn't match what could actually be sent.
balanceAfterFeenow equalsmaxAllowedToSend(the after-fee, capped value), so the displayed "Available", the MAX button, the input cap, and the 25% button are all consistent. This is a deliberate divergence from iOS/RN, which keep "Available" showing the gross balance.Tests
Added unit tests in
AmountInputViewModelTestmirroring iOSNumberPadTests:delete is allowed when amount is above the capno max exceeded effect emitted on delete above capsetMaxAmount with zero keeps input usablesetMaxAmount with negative value keeps input usableFull unit suite and detekt pass.
QA Notes
Send (onchain)
send-on-chain.webm
Send (lightning)
send-lightning.webm
LNURL Withdraw / Pay
min(maxSendable, available); MAX button and pad agreemaxSendable< spending balance → toast reads "Amount Too High / The amount exceeds this invoice's maximum." (not "Insufficient balance")lnulr-withdraw.webm
lnurl-pay.webm
Transfer to Spending
maxAllowedToSendtransfer.webm
Receiving Capacity (Advanced)
maxLspBalancetransfer-and-receive-capacity.webm
Manual external channel funding
external-node.webm
Edge cases
regression:zero available balance → pad still accepts input (not frozen), Continue stays disabledregression:an over-cap amount (cap dropped after input) can be reduced with delete without being rejected