refactor(feeds): push content warning into ZoomableContentView per media
Move the blurhash-aware sensitivity warning from feed card wrappers into
ZoomableContentView so every media item shows its own warning sized to
its own aspect ratio over its own blurhash. Grid posts now warn per
image instead of once over the whole grid.
- Add isSensitive to MediaUrl{Image,Video} (defaults to contentWarning != null)
- Populate isSensitive and contentWarning when building media in the
picture, video, and file-header feeds plus their display variants
- Drop the outer SensitivityWarning wrappers now handled inside
ZoomableContentView via SensitivityWarningOverBlurhash
This commit is contained in:
+32
-51
@@ -76,31 +76,6 @@ fun SensitivityWarning(
|
|||||||
note.event?.let { SensitivityWarning(it, accountViewModel, content) }
|
note.event?.let { SensitivityWarning(it, accountViewModel, content) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun SensitivityWarning(
|
|
||||||
note: Note,
|
|
||||||
blurhash: String?,
|
|
||||||
ratio: Float?,
|
|
||||||
description: String?,
|
|
||||||
accountViewModel: AccountViewModel,
|
|
||||||
content: @Composable () -> Unit,
|
|
||||||
) {
|
|
||||||
val event = note.event
|
|
||||||
if (event == null) {
|
|
||||||
content()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val hasSensitiveContent = remember(event) { event.isSensitiveOrNSFW() }
|
|
||||||
|
|
||||||
if (hasSensitiveContent) {
|
|
||||||
val reason = remember(event) { event.contentWarningReason() }
|
|
||||||
ObserveSensitivityWarningOverBlurhash(reason, blurhash, ratio, description, accountViewModel, content)
|
|
||||||
} else {
|
|
||||||
content()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SensitivityWarning(
|
fun SensitivityWarning(
|
||||||
event: Event,
|
event: Event,
|
||||||
@@ -130,6 +105,38 @@ fun SensitivityWarning(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SensitivityWarningOverBlurhash(
|
||||||
|
isSensitive: Boolean,
|
||||||
|
reason: String?,
|
||||||
|
blurhash: String?,
|
||||||
|
ratio: Float?,
|
||||||
|
description: String?,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
content: @Composable () -> Unit,
|
||||||
|
) {
|
||||||
|
if (!isSensitive) {
|
||||||
|
content()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val accountState = accountViewModel.showSensitiveContent().collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
var showContentWarningNote by remember(accountState) { mutableStateOf(accountState.value != true) }
|
||||||
|
|
||||||
|
CrossfadeIfEnabled(targetState = showContentWarningNote, accountViewModel = accountViewModel) {
|
||||||
|
if (it) {
|
||||||
|
if (blurhash != null) {
|
||||||
|
ContentWarningOverBlurhash(reason, blurhash, ratio, description) { showContentWarningNote = false }
|
||||||
|
} else {
|
||||||
|
ContentWarningNote(reason) { showContentWarningNote = false }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ObserveSensitivityWarning(
|
fun ObserveSensitivityWarning(
|
||||||
reason: String?,
|
reason: String?,
|
||||||
@@ -149,32 +156,6 @@ fun ObserveSensitivityWarning(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ObserveSensitivityWarningOverBlurhash(
|
|
||||||
reason: String?,
|
|
||||||
blurhash: String?,
|
|
||||||
ratio: Float?,
|
|
||||||
description: String?,
|
|
||||||
accountViewModel: AccountViewModel,
|
|
||||||
content: @Composable () -> Unit,
|
|
||||||
) {
|
|
||||||
val accountState = accountViewModel.showSensitiveContent().collectAsStateWithLifecycle()
|
|
||||||
|
|
||||||
var showContentWarningNote by remember(accountState) { mutableStateOf(accountState.value != true) }
|
|
||||||
|
|
||||||
CrossfadeIfEnabled(targetState = showContentWarningNote, accountViewModel = accountViewModel) {
|
|
||||||
if (it) {
|
|
||||||
if (blurhash != null) {
|
|
||||||
ContentWarningOverBlurhash(reason, blurhash, ratio, description) { showContentWarningNote = false }
|
|
||||||
} else {
|
|
||||||
ContentWarningNote(reason) { showContentWarningNote = false }
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
content()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun ContentWarningNotePreview() {
|
fun ContentWarningNotePreview() {
|
||||||
|
|||||||
+18
-2
@@ -150,7 +150,15 @@ fun ZoomableContentView(
|
|||||||
|
|
||||||
when (content) {
|
when (content) {
|
||||||
is MediaUrlImage -> {
|
is MediaUrlImage -> {
|
||||||
SensitivityWarning(content.contentWarning, accountViewModel) {
|
val ratio = content.dim?.aspectRatio() ?: MediaAspectRatioCache.get(content.url)
|
||||||
|
SensitivityWarningOverBlurhash(
|
||||||
|
isSensitive = content.isSensitive,
|
||||||
|
reason = content.contentWarning,
|
||||||
|
blurhash = content.blurhash,
|
||||||
|
ratio = ratio,
|
||||||
|
description = content.description,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
) {
|
||||||
TwoSecondController(content) { controllerVisible ->
|
TwoSecondController(content) { controllerVisible ->
|
||||||
val mainImageModifier =
|
val mainImageModifier =
|
||||||
Modifier
|
Modifier
|
||||||
@@ -164,7 +172,15 @@ fun ZoomableContentView(
|
|||||||
}
|
}
|
||||||
|
|
||||||
is MediaUrlVideo -> {
|
is MediaUrlVideo -> {
|
||||||
SensitivityWarning(content.contentWarning, accountViewModel) {
|
val ratio = content.dim?.aspectRatio() ?: MediaAspectRatioCache.get(content.url)
|
||||||
|
SensitivityWarningOverBlurhash(
|
||||||
|
isSensitive = content.isSensitive,
|
||||||
|
reason = content.contentWarning,
|
||||||
|
blurhash = content.blurhash,
|
||||||
|
ratio = ratio,
|
||||||
|
description = content.description,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.fillMaxWidth().then(boundsTrackingModifier),
|
modifier = Modifier.fillMaxWidth().then(boundsTrackingModifier),
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
|
|||||||
@@ -39,12 +39,13 @@ import com.vitorpamplona.amethyst.commons.model.EmptyTagList
|
|||||||
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage
|
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.ui.components.AutoNonlazyGrid
|
import com.vitorpamplona.amethyst.ui.components.AutoNonlazyGrid
|
||||||
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||||
import com.vitorpamplona.amethyst.ui.components.ZoomableContentView
|
import com.vitorpamplona.amethyst.ui.components.ZoomableContentView
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||||
|
import com.vitorpamplona.quartz.nip36SensitiveContent.contentWarningReason
|
||||||
|
import com.vitorpamplona.quartz.nip36SensitiveContent.isSensitiveOrNSFW
|
||||||
import com.vitorpamplona.quartz.nip68Picture.PictureEvent
|
import com.vitorpamplona.quartz.nip68Picture.PictureEvent
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
@@ -60,6 +61,8 @@ fun PictureDisplay(
|
|||||||
) {
|
) {
|
||||||
val event = (note.event as? PictureEvent) ?: return
|
val event = (note.event as? PictureEvent) ?: return
|
||||||
val uri = note.toNostrUri()
|
val uri = note.toNostrUri()
|
||||||
|
val isSensitive = event.isSensitiveOrNSFW()
|
||||||
|
val contentWarning = event.contentWarningReason()
|
||||||
|
|
||||||
val images by
|
val images by
|
||||||
remember(note) {
|
remember(note) {
|
||||||
@@ -74,6 +77,8 @@ fun PictureDisplay(
|
|||||||
blurhash = it.blurhash,
|
blurhash = it.blurhash,
|
||||||
dim = it.dimension,
|
dim = it.dimension,
|
||||||
uri = uri,
|
uri = uri,
|
||||||
|
contentWarning = contentWarning,
|
||||||
|
isSensitive = isSensitive,
|
||||||
mimeType = it.mimeType,
|
mimeType = it.mimeType,
|
||||||
)
|
)
|
||||||
}.toImmutableList(),
|
}.toImmutableList(),
|
||||||
@@ -85,53 +90,51 @@ fun PictureDisplay(
|
|||||||
if (first != null) {
|
if (first != null) {
|
||||||
val title = event.title()
|
val title = event.title()
|
||||||
|
|
||||||
SensitivityWarning(note = note, accountViewModel = accountViewModel) {
|
Column {
|
||||||
Column {
|
if (title != null) {
|
||||||
if (title != null) {
|
Text(
|
||||||
Text(
|
modifier = Modifier.padding(padding),
|
||||||
modifier = Modifier.padding(padding),
|
text = title,
|
||||||
text = title,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
maxLines = 1,
|
||||||
maxLines = 1,
|
overflow = TextOverflow.Ellipsis,
|
||||||
overflow = TextOverflow.Ellipsis,
|
)
|
||||||
)
|
} else {
|
||||||
} else {
|
Spacer(StdVertSpacer)
|
||||||
Spacer(StdVertSpacer)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (images.size == 1) {
|
if (images.size == 1) {
|
||||||
|
ZoomableContentView(
|
||||||
|
content = images.first(),
|
||||||
|
images = images,
|
||||||
|
roundedCorner = roundedCorner,
|
||||||
|
contentScale = ContentScale.FillWidth,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
AutoNonlazyGrid(images.size) {
|
||||||
ZoomableContentView(
|
ZoomableContentView(
|
||||||
content = images.first(),
|
content = images[it],
|
||||||
images = images,
|
images = images,
|
||||||
roundedCorner = roundedCorner,
|
roundedCorner = roundedCorner,
|
||||||
contentScale = ContentScale.FillWidth,
|
contentScale = ContentScale.Crop,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
AutoNonlazyGrid(images.size) {
|
|
||||||
ZoomableContentView(
|
|
||||||
content = images[it],
|
|
||||||
images = images,
|
|
||||||
roundedCorner = roundedCorner,
|
|
||||||
contentScale = ContentScale.Crop,
|
|
||||||
accountViewModel = accountViewModel,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TranslatableRichTextViewer(
|
|
||||||
content = event.content,
|
|
||||||
canPreview = false,
|
|
||||||
quotesLeft = 0,
|
|
||||||
modifier = Modifier.padding(padding),
|
|
||||||
tags = EmptyTagList,
|
|
||||||
backgroundColor = backgroundColor,
|
|
||||||
id = note.idHex,
|
|
||||||
callbackUri = uri,
|
|
||||||
accountViewModel = accountViewModel,
|
|
||||||
nav = nav,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TranslatableRichTextViewer(
|
||||||
|
content = event.content,
|
||||||
|
canPreview = false,
|
||||||
|
quotesLeft = 0,
|
||||||
|
modifier = Modifier.padding(padding),
|
||||||
|
tags = EmptyTagList,
|
||||||
|
backgroundColor = backgroundColor,
|
||||||
|
id = note.idHex,
|
||||||
|
callbackUri = uri,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,11 +30,12 @@ import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage
|
|||||||
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo
|
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo
|
||||||
import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
|
import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.ZoomableContentView
|
import com.vitorpamplona.amethyst.ui.components.ZoomableContentView
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
import com.vitorpamplona.quartz.nip31Alts.alt
|
import com.vitorpamplona.quartz.nip31Alts.alt
|
||||||
|
import com.vitorpamplona.quartz.nip36SensitiveContent.contentWarningReason
|
||||||
|
import com.vitorpamplona.quartz.nip36SensitiveContent.isSensitiveOrNSFW
|
||||||
import com.vitorpamplona.quartz.nip71Video.VideoEvent
|
import com.vitorpamplona.quartz.nip71Video.VideoEvent
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -53,6 +54,8 @@ fun JustVideoDisplay(
|
|||||||
remember(note) {
|
remember(note) {
|
||||||
val description = event.content.ifEmpty { null } ?: imeta.alt ?: event.alt()
|
val description = event.content.ifEmpty { null } ?: imeta.alt ?: event.alt()
|
||||||
val isImage = imeta.mimeType?.startsWith("image/") == true || RichTextParser.isImageUrl(imeta.url)
|
val isImage = imeta.mimeType?.startsWith("image/") == true || RichTextParser.isImageUrl(imeta.url)
|
||||||
|
val isSensitive = event.isSensitiveOrNSFW()
|
||||||
|
val contentWarning = event.contentWarningReason()
|
||||||
|
|
||||||
mutableStateOf<BaseMediaContent>(
|
mutableStateOf<BaseMediaContent>(
|
||||||
if (isImage) {
|
if (isImage) {
|
||||||
@@ -63,6 +66,8 @@ fun JustVideoDisplay(
|
|||||||
blurhash = imeta.blurhash,
|
blurhash = imeta.blurhash,
|
||||||
dim = imeta.dimension,
|
dim = imeta.dimension,
|
||||||
uri = note.toNostrUri(),
|
uri = note.toNostrUri(),
|
||||||
|
contentWarning = contentWarning,
|
||||||
|
isSensitive = isSensitive,
|
||||||
mimeType = imeta.mimeType,
|
mimeType = imeta.mimeType,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@@ -74,18 +79,18 @@ fun JustVideoDisplay(
|
|||||||
dim = imeta.dimension,
|
dim = imeta.dimension,
|
||||||
uri = note.toNostrUri(),
|
uri = note.toNostrUri(),
|
||||||
authorName = note.author?.toBestDisplayName(),
|
authorName = note.author?.toBestDisplayName(),
|
||||||
|
contentWarning = contentWarning,
|
||||||
|
isSensitive = isSensitive,
|
||||||
mimeType = imeta.mimeType,
|
mimeType = imeta.mimeType,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
SensitivityWarning(note = note, accountViewModel = accountViewModel) {
|
ZoomableContentView(
|
||||||
ZoomableContentView(
|
content = content,
|
||||||
content = content,
|
roundedCorner = roundedCorner,
|
||||||
roundedCorner = roundedCorner,
|
contentScale = contentScale,
|
||||||
contentScale = contentScale,
|
accountViewModel = accountViewModel,
|
||||||
accountViewModel = accountViewModel,
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-25
@@ -39,15 +39,15 @@ import androidx.compose.ui.text.font.FontWeight
|
|||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage
|
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage
|
||||||
import com.vitorpamplona.amethyst.model.MediaAspectRatioCache
|
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.ui.components.AutoNonlazyGrid
|
import com.vitorpamplona.amethyst.ui.components.AutoNonlazyGrid
|
||||||
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.ZoomableContentView
|
import com.vitorpamplona.amethyst.ui.components.ZoomableContentView
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.ui.note.ReactionsRow
|
import com.vitorpamplona.amethyst.ui.note.ReactionsRow
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.video.UserCardHeader
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.video.UserCardHeader
|
||||||
|
import com.vitorpamplona.quartz.nip36SensitiveContent.contentWarningReason
|
||||||
|
import com.vitorpamplona.quartz.nip36SensitiveContent.isSensitiveOrNSFW
|
||||||
import com.vitorpamplona.quartz.nip68Picture.PictureEvent
|
import com.vitorpamplona.quartz.nip68Picture.PictureEvent
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
@@ -92,6 +92,8 @@ private fun PictureCardImage(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
) {
|
) {
|
||||||
val uri = note.toNostrUri()
|
val uri = note.toNostrUri()
|
||||||
|
val isSensitive = event.isSensitiveOrNSFW()
|
||||||
|
val contentWarning = event.contentWarningReason()
|
||||||
|
|
||||||
val images by
|
val images by
|
||||||
remember(note) {
|
remember(note) {
|
||||||
@@ -106,6 +108,8 @@ private fun PictureCardImage(
|
|||||||
blurhash = it.blurhash,
|
blurhash = it.blurhash,
|
||||||
dim = it.dimension,
|
dim = it.dimension,
|
||||||
uri = uri,
|
uri = uri,
|
||||||
|
contentWarning = contentWarning,
|
||||||
|
isSensitive = isSensitive,
|
||||||
mimeType = it.mimeType,
|
mimeType = it.mimeType,
|
||||||
)
|
)
|
||||||
}.toImmutableList(),
|
}.toImmutableList(),
|
||||||
@@ -113,34 +117,23 @@ private fun PictureCardImage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (images.isNotEmpty()) {
|
if (images.isNotEmpty()) {
|
||||||
val first = images.first()
|
if (images.size == 1) {
|
||||||
val ratio = first.dim?.aspectRatio() ?: MediaAspectRatioCache.get(first.url)
|
ZoomableContentView(
|
||||||
|
content = images.first(),
|
||||||
SensitivityWarning(
|
images = images,
|
||||||
note = note,
|
roundedCorner = false,
|
||||||
blurhash = first.blurhash,
|
contentScale = ContentScale.FillWidth,
|
||||||
ratio = ratio,
|
accountViewModel = accountViewModel,
|
||||||
description = first.description,
|
)
|
||||||
accountViewModel = accountViewModel,
|
} else {
|
||||||
) {
|
AutoNonlazyGrid(images.size) {
|
||||||
if (images.size == 1) {
|
|
||||||
ZoomableContentView(
|
ZoomableContentView(
|
||||||
content = first,
|
content = images[it],
|
||||||
images = images,
|
images = images,
|
||||||
roundedCorner = false,
|
roundedCorner = false,
|
||||||
contentScale = ContentScale.FillWidth,
|
contentScale = ContentScale.Crop,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
AutoNonlazyGrid(images.size) {
|
|
||||||
ZoomableContentView(
|
|
||||||
content = images[it],
|
|
||||||
images = images,
|
|
||||||
roundedCorner = false,
|
|
||||||
contentScale = ContentScale.Crop,
|
|
||||||
accountViewModel = accountViewModel,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-17
@@ -42,9 +42,7 @@ import com.vitorpamplona.amethyst.commons.richtext.BaseMediaContent
|
|||||||
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage
|
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage
|
||||||
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo
|
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo
|
||||||
import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
|
import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
|
||||||
import com.vitorpamplona.amethyst.model.MediaAspectRatioCache
|
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.ZoomableContentView
|
import com.vitorpamplona.amethyst.ui.components.ZoomableContentView
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.ui.note.ReactionsRow
|
import com.vitorpamplona.amethyst.ui.note.ReactionsRow
|
||||||
@@ -52,6 +50,8 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.video.UserCardHeader
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.video.UserCardHeader
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
import com.vitorpamplona.quartz.nip31Alts.alt
|
import com.vitorpamplona.quartz.nip31Alts.alt
|
||||||
|
import com.vitorpamplona.quartz.nip36SensitiveContent.contentWarningReason
|
||||||
|
import com.vitorpamplona.quartz.nip36SensitiveContent.isSensitiveOrNSFW
|
||||||
import com.vitorpamplona.quartz.nip71Video.VideoEvent
|
import com.vitorpamplona.quartz.nip71Video.VideoEvent
|
||||||
import kotlin.text.ifEmpty
|
import kotlin.text.ifEmpty
|
||||||
|
|
||||||
@@ -104,6 +104,8 @@ private fun VideoCardImage(
|
|||||||
remember(note) {
|
remember(note) {
|
||||||
val description = event.content.ifEmpty { null } ?: imeta.alt ?: event.alt()
|
val description = event.content.ifEmpty { null } ?: imeta.alt ?: event.alt()
|
||||||
val isImage = imeta.mimeType?.startsWith("image/") == true || RichTextParser.isImageUrl(imeta.url)
|
val isImage = imeta.mimeType?.startsWith("image/") == true || RichTextParser.isImageUrl(imeta.url)
|
||||||
|
val isSensitive = event.isSensitiveOrNSFW()
|
||||||
|
val contentWarning = event.contentWarningReason()
|
||||||
|
|
||||||
mutableStateOf<BaseMediaContent>(
|
mutableStateOf<BaseMediaContent>(
|
||||||
if (isImage) {
|
if (isImage) {
|
||||||
@@ -114,6 +116,8 @@ private fun VideoCardImage(
|
|||||||
blurhash = imeta.blurhash,
|
blurhash = imeta.blurhash,
|
||||||
dim = imeta.dimension,
|
dim = imeta.dimension,
|
||||||
uri = note.toNostrUri(),
|
uri = note.toNostrUri(),
|
||||||
|
contentWarning = contentWarning,
|
||||||
|
isSensitive = isSensitive,
|
||||||
mimeType = imeta.mimeType,
|
mimeType = imeta.mimeType,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@@ -125,28 +129,20 @@ private fun VideoCardImage(
|
|||||||
dim = imeta.dimension,
|
dim = imeta.dimension,
|
||||||
uri = note.toNostrUri(),
|
uri = note.toNostrUri(),
|
||||||
authorName = note.author?.toBestDisplayName(),
|
authorName = note.author?.toBestDisplayName(),
|
||||||
|
contentWarning = contentWarning,
|
||||||
|
isSensitive = isSensitive,
|
||||||
mimeType = imeta.mimeType,
|
mimeType = imeta.mimeType,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val ratio = imeta.dimension?.aspectRatio() ?: MediaAspectRatioCache.get(imeta.url)
|
ZoomableContentView(
|
||||||
|
content = content,
|
||||||
SensitivityWarning(
|
roundedCorner = false,
|
||||||
note = note,
|
contentScale = ContentScale.FillWidth,
|
||||||
blurhash = imeta.blurhash,
|
|
||||||
ratio = ratio,
|
|
||||||
description = content.description,
|
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
) {
|
)
|
||||||
ZoomableContentView(
|
|
||||||
content = content,
|
|
||||||
roundedCorner = false,
|
|
||||||
contentScale = ContentScale.FillWidth,
|
|
||||||
accountViewModel = accountViewModel,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
+14
-9
@@ -43,13 +43,14 @@ import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage
|
|||||||
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo
|
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo
|
||||||
import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
|
import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.ZoomableContentView
|
import com.vitorpamplona.amethyst.ui.components.ZoomableContentView
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.ui.note.ReactionsRow
|
import com.vitorpamplona.amethyst.ui.note.ReactionsRow
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
import com.vitorpamplona.quartz.nip31Alts.alt
|
import com.vitorpamplona.quartz.nip31Alts.alt
|
||||||
|
import com.vitorpamplona.quartz.nip36SensitiveContent.contentWarningReason
|
||||||
|
import com.vitorpamplona.quartz.nip36SensitiveContent.isSensitiveOrNSFW
|
||||||
import com.vitorpamplona.quartz.nip94FileMetadata.FileHeaderEvent
|
import com.vitorpamplona.quartz.nip94FileMetadata.FileHeaderEvent
|
||||||
import kotlin.text.ifEmpty
|
import kotlin.text.ifEmpty
|
||||||
|
|
||||||
@@ -103,6 +104,8 @@ private fun FileHeaderCardImage(
|
|||||||
val isImage = event.mimeType()?.startsWith("image/") == true || RichTextParser.isImageUrl(fullUrl)
|
val isImage = event.mimeType()?.startsWith("image/") == true || RichTextParser.isImageUrl(fullUrl)
|
||||||
val uri = note.toNostrUri()
|
val uri = note.toNostrUri()
|
||||||
val mimeType = event.mimeType()
|
val mimeType = event.mimeType()
|
||||||
|
val isSensitive = event.isSensitiveOrNSFW()
|
||||||
|
val contentWarning = event.contentWarningReason()
|
||||||
|
|
||||||
mutableStateOf<BaseMediaContent>(
|
mutableStateOf<BaseMediaContent>(
|
||||||
if (isImage) {
|
if (isImage) {
|
||||||
@@ -113,6 +116,8 @@ private fun FileHeaderCardImage(
|
|||||||
blurhash = blurHash,
|
blurhash = blurHash,
|
||||||
dim = dimensions,
|
dim = dimensions,
|
||||||
uri = uri,
|
uri = uri,
|
||||||
|
contentWarning = contentWarning,
|
||||||
|
isSensitive = isSensitive,
|
||||||
mimeType = mimeType,
|
mimeType = mimeType,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@@ -124,20 +129,20 @@ private fun FileHeaderCardImage(
|
|||||||
dim = dimensions,
|
dim = dimensions,
|
||||||
uri = uri,
|
uri = uri,
|
||||||
authorName = note.author?.toBestDisplayName(),
|
authorName = note.author?.toBestDisplayName(),
|
||||||
|
contentWarning = contentWarning,
|
||||||
|
isSensitive = isSensitive,
|
||||||
mimeType = mimeType,
|
mimeType = mimeType,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
SensitivityWarning(note = note, accountViewModel = accountViewModel) {
|
ZoomableContentView(
|
||||||
ZoomableContentView(
|
content = content,
|
||||||
content = content,
|
roundedCorner = false,
|
||||||
roundedCorner = false,
|
contentScale = ContentScale.FillWidth,
|
||||||
contentScale = ContentScale.FillWidth,
|
accountViewModel = accountViewModel,
|
||||||
accountViewModel = accountViewModel,
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
+6
-2
@@ -51,6 +51,7 @@ open class MediaUrlImage(
|
|||||||
dim: DimensionTag? = null,
|
dim: DimensionTag? = null,
|
||||||
uri: String? = null,
|
uri: String? = null,
|
||||||
val contentWarning: String? = null,
|
val contentWarning: String? = null,
|
||||||
|
val isSensitive: Boolean = contentWarning != null,
|
||||||
mimeType: String? = null,
|
mimeType: String? = null,
|
||||||
) : MediaUrlContent(url, description, hash, dim, blurhash, uri, mimeType)
|
) : MediaUrlContent(url, description, hash, dim, blurhash, uri, mimeType)
|
||||||
|
|
||||||
@@ -62,11 +63,12 @@ class EncryptedMediaUrlImage(
|
|||||||
dim: DimensionTag? = null,
|
dim: DimensionTag? = null,
|
||||||
uri: String? = null,
|
uri: String? = null,
|
||||||
contentWarning: String? = null,
|
contentWarning: String? = null,
|
||||||
|
isSensitive: Boolean = contentWarning != null,
|
||||||
mimeType: String? = null,
|
mimeType: String? = null,
|
||||||
val encryptionAlgo: String,
|
val encryptionAlgo: String,
|
||||||
val encryptionKey: ByteArray,
|
val encryptionKey: ByteArray,
|
||||||
val encryptionNonce: ByteArray,
|
val encryptionNonce: ByteArray,
|
||||||
) : MediaUrlImage(url, description, hash, blurhash, dim, uri, contentWarning, mimeType)
|
) : MediaUrlImage(url, description, hash, blurhash, dim, uri, contentWarning, isSensitive, mimeType)
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
open class MediaUrlVideo(
|
open class MediaUrlVideo(
|
||||||
@@ -79,6 +81,7 @@ open class MediaUrlVideo(
|
|||||||
val authorName: String? = null,
|
val authorName: String? = null,
|
||||||
blurhash: String? = null,
|
blurhash: String? = null,
|
||||||
val contentWarning: String? = null,
|
val contentWarning: String? = null,
|
||||||
|
val isSensitive: Boolean = contentWarning != null,
|
||||||
mimeType: String? = null,
|
mimeType: String? = null,
|
||||||
) : MediaUrlContent(url, description, hash, dim, blurhash, uri, mimeType)
|
) : MediaUrlContent(url, description, hash, dim, blurhash, uri, mimeType)
|
||||||
|
|
||||||
@@ -93,11 +96,12 @@ class EncryptedMediaUrlVideo(
|
|||||||
authorName: String? = null,
|
authorName: String? = null,
|
||||||
blurhash: String? = null,
|
blurhash: String? = null,
|
||||||
contentWarning: String? = null,
|
contentWarning: String? = null,
|
||||||
|
isSensitive: Boolean = contentWarning != null,
|
||||||
mimeType: String? = null,
|
mimeType: String? = null,
|
||||||
val encryptionAlgo: String,
|
val encryptionAlgo: String,
|
||||||
val encryptionKey: ByteArray,
|
val encryptionKey: ByteArray,
|
||||||
val encryptionNonce: ByteArray,
|
val encryptionNonce: ByteArray,
|
||||||
) : MediaUrlVideo(url, description, hash, dim, uri, artworkUri, authorName, blurhash, contentWarning, mimeType)
|
) : MediaUrlVideo(url, description, hash, dim, uri, artworkUri, authorName, blurhash, contentWarning, isSensitive, mimeType)
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
abstract class MediaPreloadedContent(
|
abstract class MediaPreloadedContent(
|
||||||
|
|||||||
Reference in New Issue
Block a user