From bed9a2ee2141156832b71f0026e031ca0587f4a4 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 26 Apr 2026 23:51:25 +0000 Subject: [PATCH] feat(audio-rooms): zap participant via ZapCustomDialog (T2 #3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a "Send zap" row to the participant context sheet. Reuses the standard ZapCustomDialog targeting the user's kind-0 metadata addressable note (the canonical "zap a user" base note used throughout amethyst, e.g. profile screen). Single-payable invoices hand off to a wallet via payViaIntent — the same path ReactionsRow uses for in-feed zaps. Split-payee zaps (uncommon for a personal LN address but theoretically possible if the metadata has a zap-split tag) toast a "use the profile screen" message rather than wire a Compose nav host into ModalBottomSheet — the audio-room activity has no nav host of its own and routing back to MainActivity for the rare case isn't worth the plumbing. --- .../room/ParticipantHostActionsSheet.kt | 61 +++++++++++++++++++ amethyst/src/main/res/values/strings.xml | 2 + 2 files changed, 63 insertions(+) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/audiorooms/room/ParticipantHostActionsSheet.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/audiorooms/room/ParticipantHostActionsSheet.kt index 0057e307c..4f0a2d0fc 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/audiorooms/room/ParticipantHostActionsSheet.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/audiorooms/room/ParticipantHostActionsSheet.kt @@ -33,16 +33,24 @@ import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Text import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.MainActivity +import com.vitorpamplona.amethyst.ui.components.toasts.multiline.UserBasedErrorMessage +import com.vitorpamplona.amethyst.ui.note.ZapCustomDialog +import com.vitorpamplona.amethyst.ui.note.payViaIntent import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.quartz.experimental.audiorooms.admin.AdminCommandEvent +import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent import com.vitorpamplona.quartz.nip01Core.tags.aTag.ATag import com.vitorpamplona.quartz.nip19Bech32.entities.NPub import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent @@ -143,6 +151,59 @@ internal fun ParticipantHostActionsSheet( } onDismiss() } + // Zap — opens the standard ZapCustomDialog targeting the + // user's kind-0 metadata note (the canonical "zap a user" + // base note throughout amethyst). Single-payable invoices + // hand off to a wallet via payViaIntent; split-zap targets + // (uncommon for personal LN addresses) are routed back to + // the profile screen rather than wired into a non-nav-host + // ModalBottomSheet. + var showZapDialog by rememberSaveable { mutableStateOf(false) } + ActionRow(stringRes(R.string.audio_room_participant_zap)) { + showZapDialog = true + } + if (showZapDialog) { + val targetMetadataNote = + remember(target) { + com.vitorpamplona.amethyst.model.LocalCache + .getOrCreateAddressableNote(MetadataEvent.createAddress(target)) + } + // Pre-resolved at composition because callbacks below run + // outside a @Composable scope (stringRes is composable-only). + val splitUnsupportedMsg = stringRes(R.string.audio_room_participant_zap_split_unsupported) + ZapCustomDialog( + onZapStarts = {}, + onClose = { + showZapDialog = false + onDismiss() + }, + onError = { _, message, user -> + accountViewModel.toastManager.toast( + R.string.error_dialog_zap_error, + UserBasedErrorMessage(message, user), + ) + }, + onProgress = { /* no progress UI inside the sheet — single-payable handoff is fast */ }, + onPayViaIntent = { payables -> + if (payables.size == 1) { + val payable = payables.first() + payViaIntent(payable.invoice, context, { }) { error -> + accountViewModel.toastManager.toast( + R.string.error_dialog_zap_error, + UserBasedErrorMessage(error, payable.info.user), + ) + } + } else { + accountViewModel.toastManager.toast( + R.string.error_dialog_zap_error, + UserBasedErrorMessage(splitUnsupportedMsg, null), + ) + } + }, + accountViewModel = accountViewModel, + baseNote = targetMetadataNote, + ) + } ActionRow( text = stringRes( diff --git a/amethyst/src/main/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml index 0a1301931..b7057d255 100644 --- a/amethyst/src/main/res/values/strings.xml +++ b/amethyst/src/main/res/values/strings.xml @@ -540,6 +540,8 @@ Demote to listener Kick View profile + Send zap + Split zaps are not supported from inside an audio room. Open the profile screen to send. Follow Unfollow Mute