mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-05 19:28:25 +00:00
feat(desktop): blur NIP-36 content-warning notes with tap-to-reveal
Generalize SpamCheckedNoteRender to also gate sensitive content: a note tagged content-warning / NSFW renders behind a collapsed scrim (reason + 'Show') unless the account opted into 'always show sensitive content'. Reuses the same per-note rememberSaveable reveal + forceReveal as the spam collapse. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
d571906dd0
commit
f4435bfe42
+81
-16
@@ -20,6 +20,15 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.desktop.ui.note
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -27,6 +36,9 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.moderation.HashtagSpamCheck
|
||||
import com.vitorpamplona.amethyst.commons.moderation.LocalHashtagSpamSettings
|
||||
@@ -34,8 +46,12 @@ import com.vitorpamplona.amethyst.commons.moderation.LocalSpamExemptKeys
|
||||
import com.vitorpamplona.amethyst.commons.moderation.displayedEvent
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.CollapsedSpamNote
|
||||
import com.vitorpamplona.amethyst.desktop.cache.DesktopLocalCache
|
||||
import com.vitorpamplona.amethyst.desktop.model.LocalDesktopIAccount
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.countHashtags
|
||||
import com.vitorpamplona.quartz.nip36SensitiveContent.contentWarningReason
|
||||
import com.vitorpamplona.quartz.nip36SensitiveContent.isSensitiveOrNSFW
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
/**
|
||||
* Wraps a note render in the hashtag-spam check.
|
||||
@@ -80,24 +96,73 @@ fun SpamCheckedNoteRender(
|
||||
)
|
||||
}
|
||||
|
||||
// NIP-36 content warning: blur sensitive content unless the account opted
|
||||
// into "always show sensitive content".
|
||||
val account = LocalDesktopIAccount.current
|
||||
val showSensitiveFlow = remember(account) { account?.showSensitiveContentSetting ?: MutableStateFlow(null) }
|
||||
val showSensitive by showSensitiveFlow.collectAsState()
|
||||
val isSensitive = remember(noteIdHex, displayedEvent) { displayedEvent?.isSensitiveOrNSFW() == true }
|
||||
|
||||
var revealed by rememberSaveable(noteIdHex) { mutableStateOf(false) }
|
||||
|
||||
if (!forceReveal && isSpam && !revealed && displayedEvent != null) {
|
||||
val authorPubkey = displayedEvent.pubKey
|
||||
val author = localCache?.getUserIfExists(authorPubkey)
|
||||
val displayName = author?.toBestDisplayName() ?: authorPubkey.take(8)
|
||||
val avatarUrl = author?.profilePicture()
|
||||
val hashtagCount = displayedEvent.tags.countHashtags()
|
||||
CollapsedSpamNote(
|
||||
authorPubkeyHex = authorPubkey,
|
||||
authorDisplayName = displayName,
|
||||
authorAvatarUrl = avatarUrl,
|
||||
hashtagCount = hashtagCount,
|
||||
threshold = threshold,
|
||||
onReveal = { revealed = true },
|
||||
)
|
||||
} else {
|
||||
normal()
|
||||
when {
|
||||
forceReveal || revealed -> normal()
|
||||
isSpam && displayedEvent != null -> {
|
||||
val authorPubkey = displayedEvent.pubKey
|
||||
val author = localCache?.getUserIfExists(authorPubkey)
|
||||
val displayName = author?.toBestDisplayName() ?: authorPubkey.take(8)
|
||||
val avatarUrl = author?.profilePicture()
|
||||
val hashtagCount = displayedEvent.tags.countHashtags()
|
||||
CollapsedSpamNote(
|
||||
authorPubkeyHex = authorPubkey,
|
||||
authorDisplayName = displayName,
|
||||
authorAvatarUrl = avatarUrl,
|
||||
hashtagCount = hashtagCount,
|
||||
threshold = threshold,
|
||||
onReveal = { revealed = true },
|
||||
)
|
||||
}
|
||||
isSensitive && showSensitive != true && displayedEvent != null -> {
|
||||
CollapsedContentWarning(
|
||||
reason = displayedEvent.contentWarningReason(),
|
||||
onReveal = { revealed = true },
|
||||
)
|
||||
}
|
||||
else -> normal()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapsed placeholder for a NIP-36 content-warning note. Mirrors the
|
||||
* spam-collapse reveal affordance: the body stays hidden behind a scrim with the
|
||||
* warning reason until the user taps "Show".
|
||||
*/
|
||||
@Composable
|
||||
private fun CollapsedContentWarning(
|
||||
reason: String?,
|
||||
onReveal: () -> Unit,
|
||||
) {
|
||||
Surface(
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
color = MaterialTheme.colorScheme.surfaceVariant,
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp),
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth().padding(12.dp),
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text("Sensitive content", style = MaterialTheme.typography.titleSmall)
|
||||
if (!reason.isNullOrBlank()) {
|
||||
Text(
|
||||
reason,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
TextButton(onClick = onReveal) { Text("Show") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user