fix: drop reactions, quick actions and the options menu from consent previews

The signer consent dialog (NIP-46 bunker and napplet requests, single and
batched) previewed the event to sign through NoteCompose(isQuotedNote).
That still carries the reaction row, the long-press quick-action popup
and the ⋮ options menu, so a user could like, zap or broadcast an event
that does not exist yet from a consent prompt.

The preview now renders through NoteBody: author line and content only,
with an empty moreOptions slot so the ⋮ menu is gone. A reply names its
parent in one line (ReplyRenderType.LINE) instead of embedding the
parent as a full NoteCompose with its own reaction row, and quotes stay
unexpanded (quotesLeft = 0) for the same reason.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GSwjzewTFcwzqAKVJ1WJ9B
This commit is contained in:
Claude
2026-10-02 19:56:44 +00:00
parent 5bfe049db3
commit 67c1b171ba
@@ -72,6 +72,7 @@ import com.vitorpamplona.amethyst.commons.favorites.FavoriteApp
import com.vitorpamplona.amethyst.commons.favorites.FavoriteAppIcon
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
import com.vitorpamplona.amethyst.commons.model.Note
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
import com.vitorpamplona.amethyst.commons.resources.Res
import com.vitorpamplona.amethyst.commons.resources.napplet_consent_allow_always
@@ -98,9 +99,13 @@ import com.vitorpamplona.amethyst.commons.resources.nip46_signer_messages_with
import com.vitorpamplona.amethyst.commons.service.call.CallSessionBridge
import com.vitorpamplona.amethyst.commons.ui.components.RobohashFallbackAsyncImage
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.EmptyNav
import com.vitorpamplona.amethyst.commons.ui.note.NoteCompose
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.commons.ui.note.NoteBody
import com.vitorpamplona.amethyst.commons.ui.note.observeEdits
import com.vitorpamplona.amethyst.commons.ui.note.types.ReplyRenderType
import com.vitorpamplona.amethyst.commons.ui.pluralStringRes
import com.vitorpamplona.amethyst.commons.ui.stringRes
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.AmethystTheme
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip59Giftwrap.rumors.RumorAssembler
@@ -347,11 +352,11 @@ private fun SignerConsentDialog(
}
/**
* The "what you're acting on" block: the unsigned event rendered as a real NoteCompose (what it will
* look like once signed) with a JSON toggle for sign/publish, or the raw content / decrypted plaintext
* The "what you're acting on" block: the unsigned event rendered as a note (what it will look like
* once signed, see [UnsignedNotePreview]) with a JSON toggle for sign/publish, or the raw content / decrypted plaintext
* for encrypt/decrypt. Shared by the single-request dialog and each expanded batch row so a user can
* always inspect exactly what they are signing/encrypting/decrypting. Best-effort: if the main Activity
* is gone (only the foreground signer service alive) the NoteCompose is skipped and the JSON stands in.
* is gone (only the foreground signer service alive) the note is skipped and the JSON stands in.
*/
@Composable
private fun SignerConsentPreview(info: SignerConsentInfo) {
@@ -381,13 +386,7 @@ private fun SignerConsentPreview(info: SignerConsentInfo) {
) {
Column(modifier = Modifier.padding(12.dp)) {
if (previewNote != null && accountViewModel != null) {
NoteCompose(
baseNote = previewNote,
isQuotedNote = true,
quotesLeft = 0,
accountViewModel = accountViewModel,
nav = previewNav,
)
UnsignedNotePreview(previewNote, accountViewModel, previewNav)
} else if (info.contentPreview.isNotBlank()) {
Text("“${info.contentPreview}”", style = MaterialTheme.typography.bodySmall)
}
@@ -423,6 +422,42 @@ private fun SignerConsentPreview(info: SignerConsentInfo) {
}
}
/**
* The event as it will read once signed: author line and content, nothing else. Deliberately not
* [com.vitorpamplona.amethyst.commons.ui.note.NoteCompose], which wraps a note in a reaction row,
* a long-press quick-action popup and a ⋮ options menu — controls that make no sense on a consent
* prompt, and would react to, zap or broadcast an event that does not exist yet. For the same
* reason a reply names its parent in one line ([ReplyRenderType.LINE]) instead of embedding it as
* a full note, and quotes are not expanded (`quotesLeft = 0`).
*/
@Composable
private fun UnsignedNotePreview(
note: Note,
accountViewModel: AccountViewModel,
nav: INav,
) {
// Renderers fade long content into this color, so it must match the preview Surface.
val surface = MaterialTheme.colorScheme.surfaceVariant
val backgroundColor = remember(surface) { mutableStateOf(surface) }
val editState = observeEdits(note, accountViewModel)
Column {
NoteBody(
baseNote = note,
showAuthorPicture = true,
unPackReply = ReplyRenderType.LINE,
showSecondRow = false,
quotesLeft = 0,
backgroundColor = backgroundColor,
editState = editState,
accountViewModel = accountViewModel,
nav = nav,
// An empty slot replaces the default ⋮ options menu.
moreOptions = {},
)
}
}
/**
* Shown when more than one request is awaiting consent at once (the signer services requests
* concurrently). Lists each with a checkbox — all selected by default — and resolves the selected