Skip to content

feat: register-style fiat input on the zap dialog#519

Merged
barrydeen merged 1 commit into
barrydeen:mainfrom
dmnyc:feat/zap-fiat-input
May 7, 2026
Merged

feat: register-style fiat input on the zap dialog#519
barrydeen merged 1 commit into
barrydeen:mainfrom
dmnyc:feat/zap-fiat-input

Conversation

@dmnyc
Copy link
Copy Markdown
Contributor

@dmnyc dmnyc commented May 6, 2026

Summary

Mirrors the iOS PR (barrydeen/wisp-ios#82). Custom-amount input on the zap dialog is fiat-aware. In fiat mode the user types digits register-style — they fill from the cents place, so 21 reads as $0.21 and 2100 reads as $21.00. The hero amount, custom input, and Send button all show the formatted dollar string with the configured currency's symbol. The Send-button label is "Send $X.XX" (was "Zap $X.XX") and the in-progress label on the live-stream zap button is "Sending…" / "Send" instead of "Zapping…" / "Zap" when fiat mode is on. Sub-dollar fiat amounts across the app drop trailing zeros ($0.84 instead of $0.840, $0.8 instead of $0.800).

Implementation notes

  • VisualTransformation for the live cursor: the field is bound to the raw digit string ("21") and a CentsVisualTransformation renders the formatted dollar view ($0.21). Compose's cursor lives in raw-string coordinates and the OffsetMapping pegs it to the end of the formatted view, so backspace removes the rightmost digit cleanly. The earlier String-only attempt — bind the field to the formatted string and re-format in onValueChange — broke backspace on Android: when Compose's internal text changed from $0.2 to our re-formatted $0.02, it couldn't map the old cursor position into the new string and snapped it to the start, so subsequent backspaces just moved the cursor instead of deleting.
  • ExchangeRateRepository.fiatToSats(majorAmount, currency) — inverse of satsToFiat. Used at the input boundary to convert (cents / 100) dollars → sats via the cached BTC-to-fiat rate.
  • AmountFormatter.renderCurrency patterns switch to # (optional) past the decimal for sub-dollar tiers so trailing zeros drop. Whole-dollar amounts still pad to two places ($1.00) matching retail convention.
  • zap_x_amount string changes from "Zap %1\$s" to "Send %1\$s" (English only — other locales fall back to English; this string isn't translated yet).

Files

  • app/src/main/kotlin/com/wisp/app/repo/ExchangeRateRepository.kt
  • app/src/main/kotlin/com/wisp/app/ui/component/ZapDialog.kt
  • app/src/main/kotlin/com/wisp/app/ui/screen/LiveStreamScreen.kt
  • app/src/main/kotlin/com/wisp/app/ui/util/AmountFormatter.kt
  • app/src/main/res/values/strings.xml

Test plan

  • Fiat mode on, tap a profile or post → Zap → Custom → type 21 → field reads $0.21, hero matches, Send button reads Send $0.21.
  • Continue to type 2100 → field reads $21.00. Backspace → $2.10$0.21. Cursor stays at the end; backspace removes the rightmost digit (no cursor-stuck-left-of-symbol bug).
  • Open a live stream in fiat mode → header zap button reads Send / Sending… / Sent! (was Zap / Zapping… / Sent!). Toggle fiat mode off — wording reverts.
  • Sub-dollar quick-amount chips render as $0.84 (not $0.840); whole-dollar values still render as $1.00.
  • Fiat mode off — input is plain integer sats, button reads Zap N sats, no behavioural change.

…wording

Mirrors the iOS work (barrydeen/wisp-ios): the zap dialog's custom
amount input is fiat-aware and uses register-style entry — the user
types digits, they fill from the cents place ('21' -> $0.21,
'2100' -> $21.00), and the field renders the formatted dollar string
with the configured currency's symbol.

Implementation notes:

* Field is bound to the raw digit string and a [VisualTransformation]
  renders the formatted dollar view. Compose's cursor lives in raw-
  string coordinates and the OffsetMapping pegs the cursor to the end
  of the formatted view, so backspace removes the rightmost digit
  cleanly. The earlier String-only attempt — binding the field to the
  formatted string and re-formatting in onValueChange — broke
  backspace on Android: when Compose's internal text changed from
  '$0.2' to our re-formatted '$0.02', it couldn't map the old cursor
  position into the new string and snapped it to the start, so
  subsequent backspaces just moved the cursor instead of deleting.

* New ExchangeRateRepository.fiatToSats(majorAmount, currency) — the
  inverse of satsToFiat — converts the typed (cents / 100) dollar
  amount through the cached BTC-to-fiat rate.

* AmountFormatter.renderCurrency drops trailing zeros for sub-dollar
  amounts ('$0.84' instead of '$0.840', '$0.8' instead of
  '$0.800') by switching the DecimalFormat patterns to '#' optional
  digits past the decimal. Whole-dollar amounts still pad to two
  places ('$1.00') matching retail convention.

* zap_x_amount string flips from 'Zap %1$s' to 'Send %1$s' so the
  Send button reads 'Send $X.XX' in fiat mode (was 'Zap $X.XX').

* LiveStreamScreen's zap button reads 'Send' / 'Sending...' in fiat
  mode, 'Zap' / 'Zapping...' otherwise — matches the surrounding
  wallet UX.

Companion to the iOS PR for the same UX (cross-linked at filing time).
@barrydeen barrydeen merged commit b4e361a into barrydeen:main May 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants