feat(zap): hero-input composer + Edit Presets sheet + per-account presets + long-press wiring#575
Open
dmnyc wants to merge 7 commits into
Open
feat(zap): hero-input composer + Edit Presets sheet + per-account presets + long-press wiring#575dmnyc wants to merge 7 commits into
dmnyc wants to merge 7 commits into
Conversation
Adapted from feat/one-tap-zap commit 9ec4fea, with the AppSettingsRepository sync wiring stripped out (NIP-78 cross-device sync of these prefs is deferred to a future phase). InterfacePreferences: • isQuickZapEnabled / setQuickZapEnabled • getQuickZapAmountSats / setQuickZapAmountSats (clamped 1..10K) • getQuickZapAmountFiat / setQuickZapAmountFiat • getQuickZapMessage / setQuickZapMessage • QUICK_ZAP_MAX_SATS = 10_000L InterfaceScreen — new "Zaps" / "Payments" section between Fiat Mode and the Zap Icon toggle, exposing the four settings. No UI wiring yet for the long-press behavior or the in-sheet toggle — those come in subsequent commits in this branch.
…tion, friendly errors Ports the behavioral half of iOS commit barrydeen#4 from feat/one-tap-zap. The full layout rewrite (hero amount, recipient row, FlowLayout preset strip, privacy dropdown, hidden TextField with 450ms focus deferral, register-style cents on every keystroke, EditPresetsSheet, scroll-dismisses-keyboard) is deferred — the existing ZapDialog layout still works and the new behaviors are the user-facing material change. What landed: • **Instant-amount seed on open.** First mount with no initialSatsHint pre-fills `customAmount` from `interfacePrefs.getQuickZapAmountSats()` (or the fiat equivalent in cents), and `message` from `interfacePrefs.getQuickZapMessage()`. Treats the configured instant-zap amount as the "preferred opening amount" even when quick zaps are disabled — matches iOS. • **In-sheet Instant-zaps toggle.** New row above the action buttons, bound directly to `InterfacePreferences.isQuickZapEnabled` so flipping it from the sheet propagates to the post-card long-press behavior (and the NIP-78 backup) without navigating to settings. Label flips with fiat mode. • **1,000,000-sat hard cap.** Zap button disables and a red "Max 1,000,000 sats per zap" caption surfaces above the action row when the effective amount crosses the cap. Hard cap, not a confirmation. • **10,000-sat soft confirmation.** Below 10K the Zap button fires immediately. At/above 10K it routes through an AlertDialog ("Zap N sats? — This is a large amount, double-check before sending") with Send / Cancel. Below the cap so users can recover from a stray preset tap. • **`friendlyZapErrorMessage()` utility.** Mirrors iOS's `ZapAnimationStore.friendlyMessage(for:)` substring-match table plus the Swift-enum description fallback (extracts `("…")` when present). Internal so post-card error pills + future layouts can both call it.
…abled Ports iOS commit barrydeen#5 from feat/one-tap-zap. Splits the zap-glyph gesture into two paths and renders the button as disabled for the user's own posts. ActionBar: • New optional `onZapLongPress: (() -> Unit)?` parameter — null means "no long-press behavior, tap-only" (existing call sites keep working without changes). • Zap glyph switches from IconButton to a Box with `combinedClickable`, supporting onClick (open composer) AND onLongClick (fire instant zap). When `onZapLongPress` is null, the long-press handler is omitted entirely so the glyph behaves exactly as before. • `longPressFired` flag pinned in remember{} — Compose, like SwiftUI, fires both onClick AND onLongClick on release of a long-press, so the tap handler short-circuits the second fire when the flag is set. • Disabled tint moved from 0.4f to 0.35f opacity to match the iOS self-zap rendering. PostCard: • Plumbs `onZapLongPress` through to ActionBar. • Self-zap disabled: `zapEnabled = zapEnabled && !isOwnEvent` so the user's own posts render the glyph at low opacity AND both tap + long-press are short-circuited (the long-press handler returns null when zapEnabled is false in ActionBar). What's NOT in this commit: • The actual "instant zap fires the configured amount" wiring at call sites (RichContent's WispActions etc.). The gesture infrastructure is in place; plugging it in requires reaching into ZapSender / WalletViewModel and is a separate, larger commit that touches every call site that constructs WispActions.
…dismiss
Previous pass only added behaviors (instant-amount seed, in-sheet
toggle, caps, confirmation). The layout still used a centered
Dialog that filled the screen and offered no dismiss gesture —
"too tall and impossible to dismiss". This commit rebuilds the
composer from scratch to match the iOS reference screenshot.
Container: switch from `Dialog` to `ModalBottomSheet`. Gives you:
• Drag handle at the top (Material3 supplies it).
• Swipe-down dismiss + scrim-tap dismiss.
• Partial-height presentation so the sheet doesn't take over
the whole viewport.
Layout (top to bottom, mirrors iOS spec §2.6 of the port doc):
1. **Toolbar** — "Close" pill on the left, orange-tinted
"Presets" pill on the right (opens the Save-preset dialog).
2. **Recipient row** (when `recipientPubkey` + `profileLookup`
are provided) — 32dp avatar, display name + lud16 stacked,
trailing copy-icon button that pushes the lud16 to the
clipboard. Hidden gracefully when no profile data is wired.
3. **Hero amount** — 56sp orange rounded-bold number with a
muted-orange unit caption ("sats" or fiat code) underneath.
4. **Preset strip** — wrapping FlowRow of pills. Last chip is
`Custom` with an inline + badge that saves the current
amount as a new preset (disabled at 8-preset max or when
the amount already exists).
5. **Custom amount field** — inline OutlinedTextField, only
visible when the Custom chip is selected. Digit-only.
6. **Message field** — single-line OutlinedTextField with
"Message (optional)" placeholder. Preset taps auto-fill
their default message only when the field is blank, so a
mid-type tap doesn't clobber what the user wrote.
7. **Privacy dropdown** — single-row pill with eye / eye-slash
/ lock icons and helper subtext. Material3 DropdownMenu
opens on tap. Hidden when `forcePrivate` is on.
8. **Instant zaps toggle** — bound to the existing
`interfacePrefs.isQuickZapEnabled` setting (and therefore
to the NIP-78 sync). Flipping it here propagates without
re-opening Interface settings.
9. **Zap button** — full-width, accent fill, white bolt + sats
copy. Disabled when amount is 0 or over the 1M hard cap. At
>10K it routes through the existing soft-confirmation alert.
Stripped:
• LightningBackground (decorative animated dots).
• AnimatedBoltHeader (centered pulsing bolt).
• drawMiniBolt + bespoke ZapPresetChip / ZapChipButton scaffolding.
None of these matched the iOS reference; the new layout is simpler
and reads cleaner at a glance.
Signature kept compatible — added two optional params
(`recipientPubkey`, `profileLookup`); existing callers keep
working, the recipient row just hides. Wired the FeedScreen and
thread Navigation.kt call sites to pass profile data; remaining
call sites (groups, DM, profile, hashtag, set-feed, article,
notifications) still compile but won't show the recipient row
until they're updated.
…ry call site
Two follow-ups to the ZapSheet rewrite:
1. **Zap button no longer hides behind the keyboard.** The previous
layout put the Zap button at the end of a single Column inside
the bottom sheet, so when the amount field focused, the
keyboard pushed the whole content up and the button went
off-screen with no scroll to reach it.
Restructured the sheet body into two rows: a scrollable upper
region (toolbar, recipient, hero, presets, message, privacy,
instant-zaps toggle) holding `weight(1f, fill = false)` and a
pinned lower region with the cap warning + Zap button. The
outer Column gets `imePadding()` so the whole stack floats
above the IME — button stays visible, upper region scrolls if
the keyboard cuts into it.
2. **Recipient row now renders on every call site.** Only 3 of
~12 ZapDialog call sites were passing `recipientPubkey` +
`profileLookup` in the rewrite commit — the row hid silently
on the other 9. Wired the rest:
• Navigation.kt — search, hashtag feed, set feed, article,
live stream (uses streamer override pubkey when set),
notifications (post + DM target).
• FeedScreen — zap-poll target.
• UserProfileScreen — post zap (eventRepo lookup) AND
profile-direct zap (embedded profile shortcut).
• DmConversationScreen — uses the peerProfile already in
scope.
…fields The Interface settings screen's scroll column had no IME inset handling, so any text field that takes focus (most visibly the new instant-zap Amount / Fiat / Message inputs in the Zaps section, but also any future input on this screen) got covered by the soft keyboard. Adds Modifier.imePadding() between .padding(padding) and the verticalScroll. With the IME inset accounted for, the scroll viewport shrinks to fit above the keyboard and the focused field auto-scrolls into view as Compose normally handles.
…ts, long-press instant zap Ports the iOS one-tap-zap composer + preset model to Android. ZapDialog redesign - Hero amount is the editable input (BasicTextField with a thousands- separator VisualTransformation). First-keystroke-replaces-seed is preserved via TextRange(0, n); LocalTextSelectionColors overrides the selection background to transparent so the seeded select-all doesn't paint an ugly box over the orange number. - Removed the redundant inline "Custom (sats)" OutlinedTextField — the hero IS the input now, matching iOS. - Sheet locks to fillMaxSize so it opens at full height immediately; prevents the stagger where the sheet jumped taller 450ms in when auto-focus raised the keyboard. - EditPresetsSheet replaces SaveZapPresetDialog. Modal bottom sheet titled "Edit Presets" with a Done pill, per-row swipe-to-delete via SwipeToDismissBox (iOS-red #FF3B30 panel, trailing edge only), and a "+ Add preset" row that disables itself while a blank entry exists. Per-account preset routing - ZapDialog now requires zapPrefsRepo: ZapPreferences. The previous in-dialog ZapPreferences(context) wrote to the un-scoped "zap_prefs" file, while AppSettingsRepository read from "zap_prefs_<pubkey>" — preset writes from the "+" chip never reached the NIP-78 publish path and restored presets never showed up in the dialog. All 14 callsites (Navigation x9, FeedScreen x2, UserProfileScreen x2, DmConversationScreen x1) now pass feedViewModel.zapPrefs. - One-shot migration in ZapPreferences copies any pre-existing global zap_prefs entries into the per-account file on first read, marked with migrated_from_global_v1 so it never repeats. Long-press instant zap - ActionBar's zap glyph uses combinedClickable: onClick opens the composer, onLongClick fires the instant zap. LocalHapticFeedback.performHapticFeedback(LongPress) on the long-press latch so the user feels confirmation before the network round-trip. - NoteActions gains onZapInstant (defaults to onZap so existing callers fall through to the composer). Plumbed through PostCard's onZapLongPress at every callsite — Navigation, FeedScreen (FeedItem also picked up onZapLongPress), ThreadScreen, NotificationsScreen, ArticleScreen, SearchScreen, UserProfileScreen. Each instant callback reads interfacePrefs and fires sendZap immediately when isQuickZapEnabled, else falls through to opening the composer.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Final feature carve-out in the #556 series. Single commit cherry-pick of `0be27d4`. Ports the iOS one-tap-zap composer + preset model to Android and lights up the long-press instant-zap that was scaffolded in #574.
This PR's branch was cut from `feat/zap-sheet-and-instant-zap` (PR #574). The diff against `main` therefore includes #574's commits; review it after / together with #574.
When #574 merges, this PR's diff against `main` will shrink to just the `0be27d4` commit. Until then, please mentally subtract #574's set or use the per-commit view.
What this PR adds on top of #574
ZapDialog redesign
Per-account preset routing
Long-press instant zap (lights up the gesture infrastructure from #574)
Conflicts resolved during cherry-pick
Dropped from the original (NIP-78 sync)
Test plan (verified on-device)
Carve-out context — series complete
PRs in the slim-PR series superseding #556:
Follow-up: per-account scoping of quick-zap defaults (commit `b8cb061`, matches barrydeen/wisp-ios#195). Will land as its own standalone PR after #574 lands.