From 2b0518a675da046fe1c507aead4e22b6529f1979 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 11 Mar 2026 16:51:37 +0000 Subject: [PATCH] feat: allow users to add a description to the sensitive content warning - Add contentWarningReason() helper to TagArrayExt and EventExt for reading the reason from existing events - Add contentWarningDescription state to ShortNotePostViewModel, wired into event building and draft load/save/reset - Update ContentSensitivityExplainer to accept description state and render an OutlinedTextField for user input - Pass description state from ShortNotePostScreen to ContentSensitivityExplainer - Add "Reason (optional)" placeholder string resource https://claude.ai/code/session_01P2nkYdNiWXiAcfMPTRTVEW --- .../ContentSensitivityExplainer.kt | 19 ++++++++++++++++++- .../loggedIn/home/ShortNotePostScreen.kt | 5 ++++- .../loggedIn/home/ShortNotePostViewModel.kt | 7 ++++++- amethyst/src/main/res/values/strings.xml | 1 + .../quartz/nip36SensitiveContent/EventExt.kt | 2 ++ .../nip36SensitiveContent/TagArrayExt.kt | 2 ++ 6 files changed, 33 insertions(+), 3 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/contentWarning/ContentSensitivityExplainer.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/contentWarning/ContentSensitivityExplainer.kt index 8014c4c2a..77dbf33fc 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/contentWarning/ContentSensitivityExplainer.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/contentWarning/ContentSensitivityExplainer.kt @@ -34,6 +34,7 @@ import androidx.compose.material.icons.rounded.Warning import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -48,7 +49,10 @@ import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.placeholderText @Composable -fun ContentSensitivityExplainer() { +fun ContentSensitivityExplainer( + description: String, + onDescriptionChange: (String) -> Unit, +) { Column(Modifier.fillMaxWidth()) { Row( verticalAlignment = Alignment.CenterVertically, @@ -97,5 +101,18 @@ fun ContentSensitivityExplainer() { color = MaterialTheme.colorScheme.placeholderText, modifier = Modifier.padding(vertical = 10.dp), ) + + OutlinedTextField( + value = description, + onValueChange = onDescriptionChange, + modifier = Modifier.fillMaxWidth(), + placeholder = { + Text( + text = stringRes(R.string.add_sensitive_content_description_placeholder), + color = MaterialTheme.colorScheme.placeholderText, + ) + }, + singleLine = true, + ) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt index d1651dacf..99f8a6e98 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt @@ -304,7 +304,10 @@ private fun NewPostScreenBody( verticalAlignment = CenterVertically, modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp), ) { - ContentSensitivityExplainer() + ContentSensitivityExplainer( + description = postViewModel.contentWarningDescription, + onDescriptionChange = { postViewModel.contentWarningDescription = it }, + ) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostViewModel.kt index e6af5671a..5eb51fd1a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostViewModel.kt @@ -94,6 +94,7 @@ import com.vitorpamplona.quartz.nip30CustomEmoji.CustomEmoji import com.vitorpamplona.quartz.nip30CustomEmoji.EmojiUrlTag import com.vitorpamplona.quartz.nip30CustomEmoji.emojis import com.vitorpamplona.quartz.nip36SensitiveContent.contentWarning +import com.vitorpamplona.quartz.nip36SensitiveContent.contentWarningReason import com.vitorpamplona.quartz.nip36SensitiveContent.isSensitive import com.vitorpamplona.quartz.nip36SensitiveContent.isSensitiveOrNSFW import com.vitorpamplona.quartz.nip37Drafts.DraftWrapEvent @@ -236,6 +237,7 @@ open class ShortNotePostViewModel : // NSFW, Sensitive var wantsToMarkAsSensitive by mutableStateOf(false) + var contentWarningDescription by mutableStateOf("") // GeoHash var wantsToAddGeoHash by mutableStateOf(false) @@ -423,6 +425,7 @@ open class ShortNotePostViewModel : wantsForwardZapTo = localForwardZapTo.isNotEmpty() wantsToMarkAsSensitive = draftEvent.isSensitive() + contentWarningDescription = draftEvent.contentWarningReason() ?: "" val geohash = draftEvent.getGeoHash() wantsToAddGeoHash = geohash != null @@ -502,6 +505,7 @@ open class ShortNotePostViewModel : wantsForwardZapTo = localForwardZapTo.isNotEmpty() wantsToMarkAsSensitive = draftEvent.isSensitive() + contentWarningDescription = draftEvent.contentWarningReason() ?: "" val geohash = draftEvent.getGeoHash() wantsToAddGeoHash = geohash != null @@ -685,7 +689,7 @@ open class ShortNotePostViewModel : val urls = findURLs(tagger.message) val usedAttachments = iMetaAttachments.filterIsIn(urls.toSet()) - val contentWarningReason = if (wantsToMarkAsSensitive) "" else null + val contentWarningReason = if (wantsToMarkAsSensitive) contentWarningDescription else null return if (wantsPoll) { val options = pollOptions.map { it.value } @@ -896,6 +900,7 @@ open class ShortNotePostViewModel : wantsForwardZapTo = false wantsToMarkAsSensitive = false + contentWarningDescription = "" wantsToAddGeoHash = false wantsExclusiveGeoPost = false wantsSecretEmoji = false diff --git a/amethyst/src/main/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml index 5a84a6aaa..c4b7440bf 100644 --- a/amethyst/src/main/res/values/strings.xml +++ b/amethyst/src/main/res/values/strings.xml @@ -962,6 +962,7 @@ No Location Permissions Adds sensitive content warning before showing your content. This is ideal for any NSFW content or content some people may find offensive or disturbing + Reason (optional) New Feature Activating this mode requires Amethyst to send a NIP-17 message (GiftWrapped, Sealed Direct and Group Messages). NIP-17 is new and most clients have not implemented it yet. Make sure the receiver is using a compatible client. diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip36SensitiveContent/EventExt.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip36SensitiveContent/EventExt.kt index e2d6cd42d..a025a0cb0 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip36SensitiveContent/EventExt.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip36SensitiveContent/EventExt.kt @@ -25,3 +25,5 @@ import com.vitorpamplona.quartz.nip01Core.core.Event fun Event.isSensitive() = tags.isSensitive() fun Event.isSensitiveOrNSFW() = tags.isSensitiveOrNSFW() + +fun Event.contentWarningReason() = tags.contentWarningReason() diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip36SensitiveContent/TagArrayExt.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip36SensitiveContent/TagArrayExt.kt index 7d2c54f68..a44200487 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip36SensitiveContent/TagArrayExt.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip36SensitiveContent/TagArrayExt.kt @@ -28,3 +28,5 @@ val nsfwTags = setOf("nsfw", "nude", "NSFW", "NUDE", "Nsfw", "Nude") fun TagArray.isSensitive() = this.any(ContentWarningTag::isTag) fun TagArray.isSensitiveOrNSFW() = this.any { ContentWarningTag.isTag(it) || HashtagTag.isAnyTagged(it, nsfwTags) } + +fun TagArray.contentWarningReason() = this.firstOrNull(ContentWarningTag::isTag)?.getOrNull(1)