Commit Graph
13953 Commits
Author SHA1 Message Date
Claude 85dfe93ea7 feat: on-chain handoff from the custom-zap dialog
Adds a "Send on-chain instead" link button at the bottom of
ZapCustomDialog. Tapping it opens OnchainZapSendDialog with the entered
amount and message prefilled, the note as zappedEvent, and the same
split-detection / dust-preview / fee-tier picker that the Reactions
zap chip uses.

This also fixes the participant zap path in nests audio rooms
(ParticipantHostActionsSheet long-press → "Zap") since that menu item
opens ZapCustomDialog under the hood — fixing the custom dialog covers
both entry points transitively.

OnchainZapSendDialog gains a `prefillComment: String = ""` parameter so
the message field carries over from the LN dialog.

Poll-note zaps (FilteredZapAmountChoicePopup) intentionally stay
Lightning-only — the poll_option vote is encoded in the kind:9734 zap
request and counted via kind:9735 receipts; kind:8333 on-chain
receipts have no poll_option analog and the count infrastructure
doesn't index them. This is a protocol design constraint, not a UI
oversight.

Coverage audit summary — every user-facing zap initiation point now
has on-chain support except the poll-voting path:
- ZapReaction (reactions row) 
- ZapAmountChoicePopup (popup chips) 
- ZapCustomDialog (custom amount + message)  (this commit)
- ReusableZapButton (Zap the Devs + DVM buttons) 
- NestActionBar (audio room) 
- ParticipantHostActionsSheet (audio room participant)  (transitive)
- ChatMessageCompose / live-activity headers (use ZapReaction) 
- FilteredZapAmountChoicePopup (poll votes) ⚠️ LN-only by protocol
2026-05-20 20:35:01 +00:00
Claude a90dd47ed4 feat: on-chain option on the Zap the Devs button
ReusableZapButton now passes the user's on-chain zap amount choices to
ZapAmountChoicePopup and renders OnchainZapSendDialog when a chip is
tapped. The dialog receives the release note as the zappedEvent, so
the existing split detection picks up the kind:1 release notes' zap
splits and pays the dev team via one Bitcoin tx with N receipts.

The donation card is the canonical multi-recipient on-chain zap flow —
release notes are tagged with weighted splits across the team, the
sender's own pubkey gets filtered out, and the per-recipient share
preview shows live as the amount is typed.

Other callers of ReusableZapButton (DVM zap buttons, etc.) get the
on-chain row automatically since it's driven by the user's settings;
empty on-chain amounts list hides the row, matching prior behavior.
2026-05-20 20:20:21 +00:00
Claude 45aa6044b7 fix: on-chain zap splits — drop sender from splits, merge duplicates, gate Send on dust
Audit findings from an independent code review:

- HIGH: When the user zaps their own post (a common flow), every split
  that included the post author put the sender on the recipient list,
  and OnchainZapBuilder.buildSplit refused the whole tx with "cannot
  zap yourself". Fix: new OnchainZapSplitter.prepare() filters the
  sender's pubkey out of the splits before they reach the builder.
- HIGH: NIP-57 lets the same pubkey appear in zap-split tags more than
  once (additive weights). buildSplit rejected duplicate recipients.
  Same prepare() helper merges duplicates by summing weights, in
  first-seen order.
- HIGH: The dialog's live preview only showed amounts for recipients
  whose share was BELOW dust (because DustRecipientException only
  carries belowDust). Fix: parent composable computes shares with a
  zero dust threshold for the preview, gating the Send button on a
  separate belowDustShares check so the user can see all amounts and
  can't tap Send into a guaranteed BUILDING-stage failure.
- MEDIUM: OnchainZapSendResult.Failure didn't carry the ids of
  receipts that successfully published before a partial-publish
  failure. Added publishedReceiptEventIds: List<HexKey>.
- LOW: useSplits state was keyed by zappedEvent reference; re-emitted
  bundles would silently reset the toggle. Now keyed on the event id.

Tests added:
- splitter: prepare() drops sender, merges duplicates, filters
  non-positive weights; floating-point weights (0.1 + 0.2) sum exactly
- builder: buildSplit produces N recipient outputs + 1 change at index
  N, conserves sats, rejects duplicates and below-dust shares
- sender: sendSplit publishes one receipt per recipient sharing the
  txid with correct per-recipient amount; partial-publish failure
  carries the broadcast txid and the ids of receipts that did publish
2026-05-20 20:14:44 +00:00
Claude 3ed2245d8c feat: on-chain zap splits
Extends NIP-BC onchain zaps to honor a note's NIP-57 zap-split tags: one
Bitcoin transaction pays every pubkey-based recipient atomically, and
one kind:8333 receipt is published per recipient (each receipt carries
the recipient's pubkey + sat share and shares the same i:<txid>).

quartz / OnchainZapBuilder
- new buildSplit(recipients = listOf(pubkey to sats), ...) produces a
  PSBT with one output per recipient + optional change output
- existing build(...) now delegates to buildSplit; coin selection and
  change-vs-dust logic are unchanged for the single-recipient path

commons / new OnchainZapSplitter
- distribute(totalSats, splits, dustThreshold) does the weighted
  integer-math allocation, dropping the rounding remainder onto the
  largest-weight recipient first so the per-recipient sats sum exactly
  to totalSats
- throws DustRecipientException if any share lands below dust; the
  caller surfaces that as a build-stage failure before the tx is built
- unit tests cover equal weights, fractional weights, remainder
  distribution, dust rejection, and input-order preservation

commons / OnchainZapSender.sendSplit
- mirrors send() but takes the precomputed shares, builds via
  buildSplit, and publishes N receipts using the same txid; if one
  receipt publish fails the broadcast txid + already-published receipt
  ids are surfaced in the Failure result

amethyst / Account.sendOnchainZapWithSplits
- thin wrapper that hands off to OnchainZapSender.sendSplit using the
  signer's pubkey

amethyst / OnchainZapSendDialog
- detects pubkey-based zap splits on the zappedEvent and, when present,
  defaults to split mode: a SplitsRecipientSection renders one row per
  recipient with weight % and live per-recipient sats preview
- lnAddress-only splits are filtered out (no pubkey -> no Taproot
  address); a short note tells the user how many recipients were
  skipped
- the send button label switches to "Send X sats, N ways"; an opt-out
  button lets the user fall back to single-recipient mode
- on send: shares are recomputed via OnchainZapSplitter; below-dust
  configurations surface as a BUILDING-stage failure before signing
2026-05-20 19:55:15 +00:00
Claude 360dea9de9 feat: surface on-chain zaps from the reactions zap button
Adds a second row of bitcoin-orange chips to the zap amount popup that
opens the existing OnchainZapSendDialog prefilled with the chosen amount
and the note's author + zappedEvent. The on-chain stack (kind 8333,
OnchainZapSender, Account.sendOnchainZap, OnchainZapSendDialog) already
existed end-to-end; only the entry point from the reactions row and the
on-chain preset amounts were missing.

Settings:
- AccountZapPreferencesInternal: new onchainZapAmountChoices (defaults
  10k/50k/250k sats); back-compat via kotlinx.serialization defaults
- AccountSyncedSettings / AccountSettings / Account.updateZapAmounts:
  wired through end-to-end alongside the existing Lightning list
- UpdateZapAmountDialog: new "Quick On-chain Zap Amounts" section with
  its own chip list and add-amount field; the existing zap-privacy
  dropdown stays Lightning-only since on-chain has no zap type

Reactions UI:
- ZapAmountChoicePopup now collects both choice lists and offers an
  on-chain row in addition to the Lightning one
- New on-chain callback opens OnchainZapSendDialog with the prefilled
  amount; the existing dialog gains a prefillAmountSats parameter and
  now reads the on-chain preset list for its own quick-pick chips
- Other zap entry points (ReusableZapButton, NestActionBar,
  FilteredZapAmountChoicePopup) are unchanged: on-chain row defaults
  off so they keep their existing Lightning-only behavior
2026-05-20 19:24:10 +00:00
Vitor Pamplona 872bb4f245 Add new information icon 2026-05-20 14:44:59 -04:00
Vitor PamplonaandGitHub 99e98e0213 Merge pull request #3012 from vitorpamplona/claude/social-wallet-info-popup-4O6s6
Add public wallet warning chip and dialog to onchain section
2026-05-20 13:32:50 -04:00
Vitor PamplonaandGitHub 0c2a5720c1 Merge pull request #3007 from vitorpamplona/claude/gradle-to-kotlin-dsl-sOUlg
Migrate build scripts from Groovy to Kotlin DSL
2026-05-20 13:30:58 -04:00
Vitor PamplonaandGitHub e0f29aefb0 Merge pull request #3011 from greenart7c3/claude/update-payment-modal-ui-cFqYN
Refactor payment targets UI
2026-05-20 13:30:34 -04:00
Claude 89b47da42a feat: extract onchain Public chip strings to resources
Move the chip label, dialog title/body, and confirm button text out of
the composable and into strings.xml under wallet_onchain_public_* so they
can be translated via Crowdin.
2026-05-20 17:20:16 +00:00
Claude 339ad6dae7 feat: clarify privacy guidance on onchain wallet popup
Replace the vague "private channels" wording with concrete advice: fund
and drain via non-private accounts (e.g. exchanges), never mix with cold
wallets, and treat the balance as funds anyone with the nsec can spend.
2026-05-20 17:10:02 +00:00
Claude 7232456e9e fix: share payment targets dialog with reactions row, restore QR glyph
- Extract PaymentTargetsDialog from PaymentButton so the reactions row
  can render the same QR/Copy/Pay layout instead of the legacy two-row
  M3ActionRow per target.
- PayReaction now subscribes via EventFinderFilterAssemblerSubscription
  and observes the PaymentTargetsEvent reactively, so targets actually
  load when the wallet icon is tapped.
- Correct the QrCode2 codepoint (U+E00A) and re-subset the bundled
  Material Symbols Outlined font so the glyph is no longer a tofu box.

https://claude.ai/code/session_01JtJuvSYMusKiDMWo7N8Aj8
2026-05-20 16:53:11 +00:00
Claude fc8f057e3e feat: add Public chip to onchain wallet card
The onchain wallet's Taproot address is derived from the account's Nostr
pubkey, so anyone with the npub can see its balance and transaction
history on-chain. Surface that fact directly on the card with a tappable
"(i) Public" chip that opens a dialog explaining the privacy implication
and recommending private (non-Nostr) channels for funding and draining.
2026-05-20 16:39:34 +00:00
Claude ec28d4eb4c feat: redesign payment targets modal with QR, copy and pay buttons
Each target now renders in a single row with the payment method and
address on the left and three trailing icon buttons: QR (opens a dialog
showing the raw address as a QR code), Copy (clipboard + toast), and
Pay (existing payto:// intent). Adds QrCode2 to MaterialSymbols.

https://claude.ai/code/session_01JtJuvSYMusKiDMWo7N8Aj8
2026-05-20 16:35:47 +00:00
Vitor PamplonaandGitHub baf043343d Merge pull request #3009 from greenart7c3/claude/fix-reaction-payment-targets-UhsYP
fix: surface Pay row in Reaction Settings for existing accounts
2026-05-20 11:59:29 -04:00
Claude 59fd1f2244 build: use directories.add for benchmark androidTest resources
srcDirs(vararg) on AGP 9 source-set Resources is deprecated; switch to
the directories MutableSet API. Preserves the original behavior of
including quartz/src/androidDeviceTest/resources in :benchmark's
androidTest java resources (verified bip39.vectors.json shows up in
processBenchmarkAndroidTestJavaRes output).
2026-05-20 15:56:53 +00:00
Claude ebfd066d5c build: convert remaining .gradle files to Kotlin DSL
Converts the last four Groovy build scripts to Kotlin DSL so the entire
build is consistent with the rest of the modules (commons, quartz, etc.).

- settings.gradle -> settings.gradle.kts
- build.gradle (root) -> build.gradle.kts
- amethyst/build.gradle -> amethyst/build.gradle.kts
- benchmark/build.gradle -> benchmark/build.gradle.kts

Notes:
- Switched the per-subproject spotless format from groovyGradle to
  kotlinGradle now that the only gradle scripts are .gradle.kts.
- benchmark module: moved targetSdk from defaultConfig to testOptions —
  AGP 9 removed targetSdk from library defaultConfig.
- Reimplemented getCurrentBranch() with ProcessBuilder (Groovy's
  String.execute() isn't available in Kotlin DSL).
2026-05-20 14:17:47 +00:00
Claude 5841ca652c fix: surface Pay row in Reaction Settings for existing accounts
Accounts whose reactionRowItems were persisted before ReactionRowAction.Pay
existed still load a 5-item list, so Pay never appears in Reaction Settings.
Add mergeWithDefaultReactionRowItems mirroring mergeWithDefaultVideoPlayerButtons
and apply it where reaction rows enter the StateFlow (constructor + updateFrom),
appending any default actions missing from the saved list while preserving
the user's existing order and toggles.
2026-05-20 14:13:03 +00:00
Vitor PamplonaandGitHub fb4dfcefec Merge pull request #3006 from vitorpamplona/claude/add-thread-name-crashes-HsFy0
Include thread name in crash reports
2026-05-20 10:10:44 -04:00
Claude c0a22e5c0b feat: include crashing thread name in crash report
Capture the crashing thread's name from the UncaughtExceptionHandler and
surface it in the report (both in the device-info table and at the top of
the stack-trace block), so main-thread crashes can be distinguished from
coroutine-worker crashes at a glance.
2026-05-20 13:53:11 +00:00
Vitor PamplonaandGitHub a4be1d21fe Merge pull request #3003 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-05-20 09:49:27 -04:00
Crowdin Bot ce9a7da265 New Crowdin translations by GitHub Action 2026-05-20 13:46:35 +00:00
Vitor PamplonaandGitHub 33e1790348 Merge pull request #3005 from vitorpamplona/claude/fix-motorola-runtime-exception-Grtj3
Handle ForegroundServiceStartNotAllowedException in PlaybackService
2026-05-20 09:44:56 -04:00
Vitor PamplonaandGitHub 071b88c7eb Merge pull request #3004 from vitorpamplona/claude/modernize-zap-popup-QZWMV
Refactor ZapAmountChoicePopup UI with extracted components
2026-05-20 09:44:47 -04:00
Claude 754e03767a Revert "feat: replace zap button long-press-to-edit with long-press-to-custom"
This reverts commit 86a8fb0743.
2026-05-20 13:37:18 +00:00
Claude 86a8fb0743 feat: replace zap button long-press-to-edit with long-press-to-custom
The new Tune button inside the zap popup already opens the quick-zap
amounts editor, so the long-press shortcut from the zap button itself
(and from each chip inside the popup) is now redundant. Drop those
gestures and replace them with the custom-amount dialog that used to
live behind double-click:

  - ZapReaction's button: long-press now opens ZapCustomDialog;
    double-click is gone.
  - Each chip in the popup: long-press now opens ZapCustomDialog
    (via a new onCustomAmount callback threaded through the popup
    overloads).
  - NestActionBar's zap button wires onCustomAmount to its existing
    wantsToSetCustomZap state.
  - ReusableZapButton has no custom-zap dialog, so chip long-press
    just dismisses the popup there.
2026-05-20 13:33:01 +00:00
Claude 7403ccc825 fix: shrink Tune settings icon in zap popup to match chip size
The Tune button used the 40dp reactionBox with a 28dp icon, which was
visibly larger than the surrounding bolt chips. Use a compact 32dp box
with a 18dp icon so it lines up with the bolt glyph inside each chip.
2026-05-20 13:18:58 +00:00
Claude 6be8f2db9e fix: vertically center items in zap amount popup row
The bolt-icon pill chips and the Tune settings button have different
intrinsic heights, so FlowRow's default top alignment leaves the
settings button visually higher than the chips. Set itemVerticalAlignment
so every item is centered within the row.
2026-05-20 13:17:37 +00:00
Claude dde539f0fd fix: catch ForegroundServiceStartNotAllowedException in PlaybackService
Media3's MediaSessionService.onStartCommand calls stopSelfSafely() when
delivered an intent (e.g. MEDIA_BUTTON from a headset) while no playback
is active. stopSelfSafely() invokes startForeground() to detach the
notification before stopping, which Android 12+ rejects when the app is
backgrounded with ForegroundServiceStartNotAllowedException.

Swallow that case in onStartCommand and stopSelf, matching the pattern
already used in NotificationRelayService.

https://claude.ai/code/session_01KTZDxK7zacrhFfqMHqt8dm
2026-05-20 13:06:35 +00:00
davotoula e131b0fec2 Show on chain zaps even if only reaction 2026-05-20 08:23:10 +02:00
Vitor PamplonaandGitHub 4f094929d6 Merge pull request #3002 from vitorpamplona/claude/fix-locale-observable-by5fc
fix: observe locale in CalendarDateTimePickerButton
2026-05-19 20:10:17 -04:00
Claude 446345b35e feat: modernize zap amount choice popup to match reactions popup style
Wrap the zap-amount choices in the same elevated card / surfaceVariant
container used by the reactions popup, replace the flat primary-colored
material Buttons with pill-shaped Bitcoin-orange chips that pair a bolt
icon with the amount, and add a Tune action at the end of the row (the
zap equivalent of the AddReaction button in the reactions popup) so the
user can jump straight to editing their quick-zap amounts.
2026-05-20 00:01:17 +00:00
Vitor PamplonaandGitHub d4f7f9f178 Merge pull request #3001 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-05-19 19:46:46 -04:00
Crowdin Bot d4f7f745af New Crowdin translations by GitHub Action 2026-05-19 23:34:03 +00:00
Vitor PamplonaandGitHub 3a07b1879e Merge pull request #2996 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-05-19 19:32:34 -04:00
Crowdin Bot 9099fd4b84 New Crowdin translations by GitHub Action 2026-05-19 23:27:44 +00:00
Vitor PamplonaandGitHub 6f4978f149 Merge pull request #3000 from vitorpamplona/claude/revert-commit-32f7bda-ObZBH
Remove I2P support and privacy routing abstraction
2026-05-19 19:25:50 -04:00
Claude ec3be14cf8 fix: observe locale in CalendarDateTimePickerButton
Use LocalConfiguration.current.locales[0] so the date formatter recomposes
when the device locale changes, satisfying the NonObservableLocale lint
check.
2026-05-19 23:24:57 +00:00
Vitor PamplonaandGitHub 2ff67479a3 Merge pull request #2999 from vitorpamplona/claude/fix-notification-feed-bug-YNke0
Fix notification feed composition issue by removing CrossfadeIfEnabled
2026-05-19 19:16:13 -04:00
Claude 6a0801427b Revert "Merge pull request #2990 from vitorpamplona/claude/add-i2p-privacy-option-nK2X7"
This reverts commit d42482ff56, reversing
changes made to a8b6766f49.
2026-05-19 23:10:56 +00:00
Claude 9b94874d01 fix(notifications): drop crossfade around the card feed
The Notifications feed could occasionally render as two stacked
LazyColumns — visible as ghosted text and "only the top one scrolls"
because both LazyColumns share the same `listState`. Reproduced by
double-tapping the bottom-nav Notifications button (each tap calls
`invalidateDataAndSendToTop(true)` → `clear()` + `refreshSuspended()`).

`RenderCardFeed` wrapped its `when (feedState)` in `CrossfadeIfEnabled`
→ `MyCrossfade`. `CardFeedState.Loaded` is a plain class with identity
equality, so back-to-back refreshes that transit through `Empty` or
`Loading` and back to `Loaded` inside the 100 ms animation can leave
two `Loaded` instances pinned in `currentlyVisible`. Each one composes
its own `FeedLoaded { LazyColumn(...) }`, both bound to the same
`LazyListState`, and only the topmost gets scroll input.

Switch directly on `feedState` with no crossfade — the animation was
only 100 ms and barely perceptible. Other CrossfadeIfEnabled callers
are untouched.
2026-05-19 23:00:50 +00:00
Vitor PamplonaandGitHub 679c5a89e0 Merge pull request #2994 from vitorpamplona/claude/nip52-calendar-screens-yQc3j
Add NIP-52 calendar events support with UI and reminder system
2026-05-19 18:30:59 -04:00
Vitor PamplonaandGitHub 297c2adffa Merge pull request #2998 from vitorpamplona/claude/fix-remember-return-type-l5SvB
Fix PrivacyOptionsScreen initialization with LaunchedEffect
2026-05-19 18:28:42 -04:00
Claude 952aa24001 fix(privacy): use LaunchedEffect instead of remember for VM reset
The remember(...) call returned Unit, which tripped the
RememberReturnType lint. Use LaunchedEffect, which is the
idiomatic Compose API for keyed side effects.
2026-05-19 22:27:13 +00:00
Vitor PamplonaandGitHub bef3bd61c5 Merge pull request #2997 from vitorpamplona/claude/fix-completion-handler-exception-6WASO
Simplify Nip11Retriever by removing nested withContext
2026-05-19 18:23:42 -04:00
Claude 97a6d4bcbd fix(calendars): wire feeds into updateFeedsWith so new events stream in live + drop redundant Calendar Lists label
Two things turned out to be the same bug:

1. New calendar events arriving from relays didn't show up in either
   feed without a manual pull-to-refresh. The screen was stuck on
   whatever the last full refresh saw.
2. After a top-nav filter switch the feed looked like it was reflecting
   "a previous state" — same root cause: filter switch ran a full
   refresh from LocalCache, but events that the new subscription
   subsequently delivered were dropped on the floor.

AccountFeedContentStates.updateFeedsWith / deleteNotes had calls for
every other feed but neither calendarAppointmentsFeed nor
calendarCollectionsFeed — so LocalCache.live.newEventBundles flowed
past them. Added both calls (and the matching deleteFromFeed entries)
so new appointments/collections insert into the visible feed live.

Also dropped the redundant "Calendar Lists" label above the top-nav
filter spinner on the collections screen — it duplicated the screen
title shown by the navigation chrome.
2026-05-19 22:21:23 +00:00
Claude 62da33f02b fix(relay-info): switch to Dispatchers.IO around the whole executeAsync call
When loadRelayInfo was invoked from a main-thread Compose coroutine and
the call got cancelled, okhttp's executeAsync cancellation handler
closes the Response on the resuming dispatcher (AndroidUiDispatcher).
Closing the response drains any unread bytes from the SSL socket, which
triggers a blocking read on the main thread and throws
NetworkOnMainThreadException — surfaced to the user as
CompletionHandlerException.

The previous withContext(Dispatchers.IO) was nested inside the .use {}
block, so neither the initial suspension at executeAsync() nor the
implicit response close in the cancellation handler ran on IO. Moving
the dispatcher switch to wrap the entire function ensures the
continuation is dispatched on IO and the cancellation/close path runs
there too.
2026-05-19 22:13:58 +00:00
Crowdin Bot 5045ef9dcf New Crowdin translations by GitHub Action 2026-05-19 22:08:49 +00:00
David KasparandGitHub 3a84aa0168 Merge pull request #2991 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-05-20 00:07:19 +02:00
Vitor PamplonaandGitHub 1909fe7e4c Merge pull request #2995 from vitorpamplona/claude/test-mls-reply-notifications-SnaIM
Add reply support for Marmot/MLS group messages
2026-05-19 18:07:17 -04:00