From 85dfe93ea75cf1e3c3f3a30359def5ad74ae1faa Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 20 May 2026 20:35:01 +0000 Subject: [PATCH] feat: on-chain handoff from the custom-zap dialog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../amethyst/ui/note/ZapCustomDialog.kt | 37 +++++++++++++++++++ .../loggedIn/wallet/OnchainZapSendDialog.kt | 3 +- amethyst/src/main/res/values/strings.xml | 1 + 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt index 6044bec697..befdaa60e5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt @@ -48,6 +48,7 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.SuggestionChip import androidx.compose.material3.Surface import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -81,6 +82,7 @@ import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarWithBackButton import com.vitorpamplona.amethyst.ui.note.buttons.CloseButton import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.wallet.OnchainZapSendDialog import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer @@ -89,6 +91,7 @@ import com.vitorpamplona.amethyst.ui.theme.Size55dp import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer import com.vitorpamplona.amethyst.ui.theme.ZeroPadding import com.vitorpamplona.amethyst.ui.theme.placeholderText +import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent import kotlinx.collections.immutable.ImmutableList import kotlinx.coroutines.CancellationException @@ -157,6 +160,11 @@ fun ZapCustomDialog( val presetAmounts = remember(accountViewModel) { accountViewModel.zapAmountChoices() } + // True while the on-chain hand-off sheet is open. Dismissing the on-chain + // sheet closes the whole zap flow — the user has already committed to a + // payment method, no "go back to Lightning" is offered here. + var sendOnchain by remember { mutableStateOf(false) } + Dialog( onDismissRequest = { onClose() }, properties = @@ -326,9 +334,38 @@ fun ZapCustomDialog( ) onClose() } + + // Hand off to the on-chain dialog with the entered amount + + // message prefilled. Disabled while the amount is empty so the + // user can't open a sheet that immediately gates on its own + // empty field. + TextButton( + onClick = { sendOnchain = true }, + enabled = postViewModel.canSend() && !baseNote.isDraft(), + modifier = + Modifier + .fillMaxWidth() + .padding(top = 4.dp), + ) { + Text(text = stringRes(id = R.string.send_onchain_instead)) + } } } } + + if (sendOnchain) { + OnchainZapSendDialog( + accountViewModel = accountViewModel, + onDismiss = { + sendOnchain = false + onClose() + }, + recipientPubKey = baseNote.author?.pubkeyHex, + zappedEvent = baseNote.toEventHint(), + prefillAmountSats = postViewModel.value(), + prefillComment = postViewModel.customMessage.text, + ) + } } @Composable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainZapSendDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainZapSendDialog.kt index 6471888782..656df4d944 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainZapSendDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainZapSendDialog.kt @@ -137,6 +137,7 @@ fun OnchainZapSendDialog( recipientPubKey: HexKey? = null, zappedEvent: EventHintBundle? = null, prefillAmountSats: Long? = null, + prefillComment: String = "", ) { val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) val scope = rememberCoroutineScope() @@ -152,7 +153,7 @@ fun OnchainZapSendDialog( var searchInput by remember { mutableStateOf("") } var selectedUser by remember { mutableStateOf(null) } var amountInput by remember { mutableStateOf(prefillAmountSats?.toString().orEmpty()) } - var comment by remember { mutableStateOf("") } + var comment by remember { mutableStateOf(prefillComment) } var feeTier by remember { mutableStateOf(FeeTier.NORMAL) } var fees by remember { mutableStateOf(null) } diff --git a/amethyst/src/main/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml index b66a7902a7..bcd67af071 100644 --- a/amethyst/src/main/res/values/strings.xml +++ b/amethyst/src/main/res/values/strings.xml @@ -824,6 +824,7 @@ Quick On-chain Zap Amounts On-chain zaps pay miner fees, so amounts are usually larger than Lightning. Tap an amount to remove it. New on-chain amount in sats + Send on-chain instead Zap Privacy Controls how your identity is shown when you send a zap. Connect Wallet