diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/SensitivityWarning.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/SensitivityWarning.kt index 2a6b2a491..85e57f6d4 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/SensitivityWarning.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/SensitivityWarning.kt @@ -117,15 +117,13 @@ fun SensitivityWarning( } @Composable -fun SensitivityWarningOverBlurhash( +fun ContentWarningGate( isSensitive: Boolean, reasons: Set, - blurhash: String?, - ratio: Float?, - description: String?, - contentScale: ContentScale, - preloadUrl: String?, + preloadUrls: List, accountViewModel: AccountViewModel, + modifier: Modifier = Modifier.fillMaxWidth(), + backdrop: (@Composable () -> Unit)? = null, content: @Composable () -> Unit, ) { if (!isSensitive) { @@ -137,51 +135,12 @@ fun SensitivityWarningOverBlurhash( var showContentWarningNote by remember(accountState) { mutableStateOf(accountState.value != true) } - if (showContentWarningNote && preloadUrl != null) { + if (showContentWarningNote && preloadUrls.isNotEmpty()) { val context = LocalContext.current - LaunchedEffect(preloadUrl) { - runCatching { - context.imageLoader.enqueue(ImageRequest.Builder(context).data(preloadUrl).build()) - } - } - } - - CrossfadeIfEnabled(targetState = showContentWarningNote, accountViewModel = accountViewModel) { - if (it) { - if (blurhash != null) { - ContentWarningOverBlurhash(reasons, blurhash, ratio, description, contentScale) { showContentWarningNote = false } - } else { - ContentWarningNote(reasons.firstOrNull()) { showContentWarningNote = false } - } - } else { - content() - } - } -} - -@Composable -fun SensitivityWarningOverBlurhashGrid( - isSensitive: Boolean, - reasons: Set, - media: List, - accountViewModel: AccountViewModel, - content: @Composable () -> Unit, -) { - if (!isSensitive || media.isEmpty()) { - content() - return - } - - val accountState = accountViewModel.showSensitiveContent().collectAsStateWithLifecycle() - - var showContentWarningNote by remember(accountState) { mutableStateOf(accountState.value != true) } - - if (showContentWarningNote) { - val context = LocalContext.current - LaunchedEffect(media) { - media.forEach { item -> + LaunchedEffect(preloadUrls) { + preloadUrls.forEach { url -> runCatching { - context.imageLoader.enqueue(ImageRequest.Builder(context).data(item.url).build()) + context.imageLoader.enqueue(ImageRequest.Builder(context).data(url).build()) } } } @@ -189,7 +148,14 @@ fun SensitivityWarningOverBlurhashGrid( CrossfadeIfEnabled(targetState = showContentWarningNote, accountViewModel = accountViewModel) { if (it) { - ContentWarningOverBlurhashGrid(reasons, media) { showContentWarningNote = false } + if (backdrop != null) { + Box(modifier = modifier.clipToBounds()) { + backdrop() + ContentWarningOverlayBody(reasons) { showContentWarningNote = false } + } + } else { + ContentWarningNote(reasons.firstOrNull()) { showContentWarningNote = false } + } } else { content() } @@ -239,85 +205,44 @@ fun ContentWarningNoteWithBigReasonPreview() { } } -@Preview @Composable -fun ContentWarningOverBlurhashPreview() { - ThemeComparisonColumn { - ContentWarningOverBlurhash( - reasons = setOf("Spoilers"), - blurhash = "LEHV6nWB2yk8pyo0adR*.7kCMdnj", - ratio = 16f / 9f, - description = null, - contentScale = ContentScale.FillWidth, - onDismiss = {}, - ) - } -} - -@Preview -@Composable -fun ContentWarningOverBlurhashMultiReasonPreview() { - ThemeComparisonColumn { - ContentWarningOverBlurhash( - reasons = setOf("Spoilers", "Violence", "Nudity"), - blurhash = "LEHV6nWB2yk8pyo0adR*.7kCMdnj", - ratio = 16f / 9f, - description = null, - contentScale = ContentScale.FillWidth, - onDismiss = {}, - ) - } -} - -@Composable -fun ContentWarningOverBlurhash( - reasons: Set, +fun BlurhashBackdrop( blurhash: String, - ratio: Float?, description: String?, - contentScale: ContentScale, - onDismiss: () -> Unit, ) { - val sizingModifier = - when { - contentScale == ContentScale.Crop -> Modifier.fillMaxSize() - ratio != null -> Modifier.fillMaxWidth().aspectRatio(ratio) - else -> Modifier.fillMaxWidth() - } - - Box(modifier = sizingModifier.clipToBounds()) { - DisplayBlurHash( - blurhash = blurhash, - description = description, - contentScale = ContentScale.Crop, - modifier = Modifier.fillMaxSize(), - ) - ContentWarningOverlayBody(reasons, onDismiss) - } + DisplayBlurHash( + blurhash = blurhash, + description = description, + contentScale = ContentScale.Crop, + modifier = Modifier.fillMaxSize(), + ) } @Composable -fun ContentWarningOverBlurhashGrid( - reasons: Set, - media: List, - onDismiss: () -> Unit, -) { - Box(modifier = Modifier.fillMaxWidth().aspectRatio(1f).clipToBounds()) { - AutoNonlazyGrid(media.size) { idx -> - val item = media[idx] - if (item.blurhash != null) { - DisplayBlurHash( - blurhash = item.blurhash, - description = item.description, - contentScale = ContentScale.Crop, - modifier = Modifier.fillMaxSize(), - ) - } +fun BlurhashGridBackdrop(media: List) { + AutoNonlazyGrid(media.size) { idx -> + val item = media[idx] + if (item.blurhash != null) { + DisplayBlurHash( + blurhash = item.blurhash, + description = item.description, + contentScale = ContentScale.Crop, + modifier = Modifier.fillMaxSize(), + ) } - ContentWarningOverlayBody(reasons, onDismiss) } } +fun mediaSizingModifier( + ratio: Float?, + contentScale: ContentScale, +): Modifier = + when { + contentScale == ContentScale.Crop -> Modifier.fillMaxSize() + ratio != null -> Modifier.fillMaxWidth().aspectRatio(ratio) + else -> Modifier.fillMaxWidth() + } + @Composable private fun ContentWarningOverlayBody( reasons: Set, @@ -487,7 +412,7 @@ fun ContentWarningNote( } } -fun collectPictureReasons(event: Event): Set { +fun collectContentWarningReasons(event: Event): Set { val reasons = linkedSetOf() event.contentWarningReason()?.takeIf { it.isNotBlank() }?.let { reasons.add(it) } event.imetas().forEach { iMeta -> diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt index e101247aa..a90440d19 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt @@ -151,15 +151,13 @@ fun ZoomableContentView( when (content) { is MediaUrlImage -> { val ratio = content.dim?.aspectRatio() ?: MediaAspectRatioCache.get(content.url) - SensitivityWarningOverBlurhash( - isSensitive = content.isSensitive, + ContentWarningGate( + isSensitive = content.contentWarning != null, reasons = setOfNotNull(content.contentWarning), - blurhash = content.blurhash, - ratio = ratio, - description = content.description, - contentScale = contentScale, - preloadUrl = content.url, + preloadUrls = listOf(content.url), accountViewModel = accountViewModel, + modifier = mediaSizingModifier(ratio, contentScale), + backdrop = content.blurhash?.let { blurhash -> { BlurhashBackdrop(blurhash, content.description) } }, ) { TwoSecondController(content) { controllerVisible -> val mainImageModifier = @@ -175,15 +173,13 @@ fun ZoomableContentView( is MediaUrlVideo -> { val ratio = content.dim?.aspectRatio() ?: MediaAspectRatioCache.get(content.url) - SensitivityWarningOverBlurhash( - isSensitive = content.isSensitive, + ContentWarningGate( + isSensitive = content.contentWarning != null, reasons = setOfNotNull(content.contentWarning), - blurhash = content.blurhash, - ratio = ratio, - description = content.description, - contentScale = contentScale, - preloadUrl = null, + preloadUrls = emptyList(), accountViewModel = accountViewModel, + modifier = mediaSizingModifier(ratio, contentScale), + backdrop = content.blurhash?.let { blurhash -> { BlurhashBackdrop(blurhash, content.description) } }, ) { Box( modifier = Modifier.fillMaxWidth().then(boundsTrackingModifier), diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/PictureDisplay.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/PictureDisplay.kt index f08939a36..466764b6a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/PictureDisplay.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/PictureDisplay.kt @@ -23,6 +23,8 @@ package com.vitorpamplona.amethyst.ui.note.types import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -37,12 +39,16 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.text.style.TextOverflow import com.vitorpamplona.amethyst.commons.model.EmptyTagList import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage +import com.vitorpamplona.amethyst.model.MediaAspectRatioCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.ui.components.AutoNonlazyGrid -import com.vitorpamplona.amethyst.ui.components.SensitivityWarningOverBlurhashGrid +import com.vitorpamplona.amethyst.ui.components.BlurhashBackdrop +import com.vitorpamplona.amethyst.ui.components.BlurhashGridBackdrop +import com.vitorpamplona.amethyst.ui.components.ContentWarningGate import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer import com.vitorpamplona.amethyst.ui.components.ZoomableContentView -import com.vitorpamplona.amethyst.ui.components.collectPictureReasons +import com.vitorpamplona.amethyst.ui.components.collectContentWarningReasons +import com.vitorpamplona.amethyst.ui.components.mediaSizingModifier import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer @@ -63,8 +69,7 @@ fun PictureDisplay( val event = (note.event as? PictureEvent) ?: return val uri = note.toNostrUri() val isSensitive = event.isSensitiveOrNSFW() - val reasons = collectPictureReasons(event) - val inGrid = event.imetaTags().size > 1 + val reasons = collectContentWarningReasons(event) val images by remember(note) { @@ -79,8 +84,6 @@ fun PictureDisplay( blurhash = it.blurhash, dim = it.dimension, uri = uri, - contentWarning = reasons.firstOrNull(), - isSensitive = isSensitive && !inGrid, mimeType = it.mimeType, ) }.toImmutableList(), @@ -106,19 +109,31 @@ fun PictureDisplay( } if (images.size == 1) { - ZoomableContentView( - content = images.first(), - images = images, - roundedCorner = roundedCorner, - contentScale = ContentScale.FillWidth, - accountViewModel = accountViewModel, - ) - } else { - SensitivityWarningOverBlurhashGrid( + val ratio = first.dim?.aspectRatio() ?: MediaAspectRatioCache.get(first.url) + ContentWarningGate( isSensitive = isSensitive, reasons = reasons, - media = images, + preloadUrls = listOf(first.url), accountViewModel = accountViewModel, + modifier = mediaSizingModifier(ratio, ContentScale.FillWidth), + backdrop = first.blurhash?.let { blurhash -> { BlurhashBackdrop(blurhash, first.description) } }, + ) { + ZoomableContentView( + content = first, + images = images, + roundedCorner = roundedCorner, + contentScale = ContentScale.FillWidth, + accountViewModel = accountViewModel, + ) + } + } else { + ContentWarningGate( + isSensitive = isSensitive, + reasons = reasons, + preloadUrls = images.map { it.url }, + accountViewModel = accountViewModel, + modifier = Modifier.fillMaxWidth().aspectRatio(1f), + backdrop = { BlurhashGridBackdrop(images) }, ) { AutoNonlazyGrid(images.size) { ZoomableContentView( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/VideoDisplay.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/VideoDisplay.kt index a5b73df9e..50041196f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/VideoDisplay.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/VideoDisplay.kt @@ -29,12 +29,16 @@ import com.vitorpamplona.amethyst.commons.richtext.BaseMediaContent import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo import com.vitorpamplona.amethyst.commons.richtext.RichTextParser +import com.vitorpamplona.amethyst.model.MediaAspectRatioCache import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.ui.components.BlurhashBackdrop +import com.vitorpamplona.amethyst.ui.components.ContentWarningGate import com.vitorpamplona.amethyst.ui.components.ZoomableContentView +import com.vitorpamplona.amethyst.ui.components.collectContentWarningReasons +import com.vitorpamplona.amethyst.ui.components.mediaSizingModifier import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip31Alts.alt -import com.vitorpamplona.quartz.nip36SensitiveContent.contentWarningReason import com.vitorpamplona.quartz.nip36SensitiveContent.isSensitiveOrNSFW import com.vitorpamplona.quartz.nip71Video.VideoEvent @@ -49,13 +53,13 @@ fun JustVideoDisplay( val event = (videoEvent as? Event) ?: return val imeta = videoEvent.imetaTags().getOrNull(0) ?: return + val isSensitive = event.isSensitiveOrNSFW() + val reasons = collectContentWarningReasons(event) + val isImage = imeta.mimeType?.startsWith("image/") == true || RichTextParser.isImageUrl(imeta.url) val content by remember(note) { val description = event.content.ifEmpty { null } ?: imeta.alt ?: event.alt() - val isImage = imeta.mimeType?.startsWith("image/") == true || RichTextParser.isImageUrl(imeta.url) - val isSensitive = event.isSensitiveOrNSFW() - val contentWarning = event.contentWarningReason() mutableStateOf( if (isImage) { @@ -66,8 +70,6 @@ fun JustVideoDisplay( blurhash = imeta.blurhash, dim = imeta.dimension, uri = note.toNostrUri(), - contentWarning = contentWarning, - isSensitive = isSensitive, mimeType = imeta.mimeType, ) } else { @@ -79,18 +81,27 @@ fun JustVideoDisplay( dim = imeta.dimension, uri = note.toNostrUri(), authorName = note.author?.toBestDisplayName(), - contentWarning = contentWarning, - isSensitive = isSensitive, mimeType = imeta.mimeType, ) }, ) } - ZoomableContentView( - content = content, - roundedCorner = roundedCorner, - contentScale = contentScale, + val ratio = imeta.dimension?.aspectRatio() ?: MediaAspectRatioCache.get(imeta.url) + + ContentWarningGate( + isSensitive = isSensitive, + reasons = reasons, + preloadUrls = if (isImage) listOf(imeta.url) else emptyList(), accountViewModel = accountViewModel, - ) + modifier = mediaSizingModifier(ratio, contentScale), + backdrop = imeta.blurhash?.let { blurhash -> { BlurhashBackdrop(blurhash, content.description) } }, + ) { + ZoomableContentView( + content = content, + roundedCorner = roundedCorner, + contentScale = contentScale, + accountViewModel = accountViewModel, + ) + } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/pictures/PictureCardCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/pictures/PictureCardCompose.kt index 23031d6bc..d6c210eda 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/pictures/PictureCardCompose.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/pictures/PictureCardCompose.kt @@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.pictures import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -39,11 +40,15 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage +import com.vitorpamplona.amethyst.model.MediaAspectRatioCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.ui.components.AutoNonlazyGrid -import com.vitorpamplona.amethyst.ui.components.SensitivityWarningOverBlurhashGrid +import com.vitorpamplona.amethyst.ui.components.BlurhashBackdrop +import com.vitorpamplona.amethyst.ui.components.BlurhashGridBackdrop +import com.vitorpamplona.amethyst.ui.components.ContentWarningGate import com.vitorpamplona.amethyst.ui.components.ZoomableContentView -import com.vitorpamplona.amethyst.ui.components.collectPictureReasons +import com.vitorpamplona.amethyst.ui.components.collectContentWarningReasons +import com.vitorpamplona.amethyst.ui.components.mediaSizingModifier import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.note.ReactionsRow import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @@ -94,8 +99,7 @@ private fun PictureCardImage( ) { val uri = note.toNostrUri() val isSensitive = event.isSensitiveOrNSFW() - val reasons = collectPictureReasons(event) - val inGrid = event.imetaTags().size > 1 + val reasons = collectContentWarningReasons(event) val images by remember(note) { @@ -110,41 +114,50 @@ private fun PictureCardImage( blurhash = it.blurhash, dim = it.dimension, uri = uri, - contentWarning = reasons.firstOrNull(), - // In grid mode the warning is handled at the grid level to avoid - // one overlay per image. Single-image posts still warn per media. - isSensitive = isSensitive && !inGrid, mimeType = it.mimeType, ) }.toImmutableList(), ) } - if (images.isNotEmpty()) { - if (images.size == 1) { + if (images.isEmpty()) return + + if (images.size == 1) { + val single = images.first() + val ratio = single.dim?.aspectRatio() ?: MediaAspectRatioCache.get(single.url) + ContentWarningGate( + isSensitive = isSensitive, + reasons = reasons, + preloadUrls = listOf(single.url), + accountViewModel = accountViewModel, + modifier = mediaSizingModifier(ratio, ContentScale.FillWidth), + backdrop = single.blurhash?.let { blurhash -> { BlurhashBackdrop(blurhash, single.description) } }, + ) { ZoomableContentView( - content = images.first(), + content = single, images = images, roundedCorner = false, contentScale = ContentScale.FillWidth, accountViewModel = accountViewModel, ) - } else { - SensitivityWarningOverBlurhashGrid( - isSensitive = isSensitive, - reasons = reasons, - media = images, - accountViewModel = accountViewModel, - ) { - AutoNonlazyGrid(images.size) { - ZoomableContentView( - content = images[it], - images = images, - roundedCorner = false, - contentScale = ContentScale.Crop, - accountViewModel = accountViewModel, - ) - } + } + } else { + ContentWarningGate( + isSensitive = isSensitive, + reasons = reasons, + preloadUrls = images.map { it.url }, + accountViewModel = accountViewModel, + modifier = Modifier.fillMaxWidth().aspectRatio(1f), + backdrop = { BlurhashGridBackdrop(images) }, + ) { + AutoNonlazyGrid(images.size) { + ZoomableContentView( + content = images[it], + images = images, + roundedCorner = false, + contentScale = ContentScale.Crop, + accountViewModel = accountViewModel, + ) } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/shorts/VideoCardCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/shorts/VideoCardCompose.kt index 21cab732e..5b0cace57 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/shorts/VideoCardCompose.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/shorts/VideoCardCompose.kt @@ -42,15 +42,19 @@ import com.vitorpamplona.amethyst.commons.richtext.BaseMediaContent import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo import com.vitorpamplona.amethyst.commons.richtext.RichTextParser +import com.vitorpamplona.amethyst.model.MediaAspectRatioCache import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.ui.components.BlurhashBackdrop +import com.vitorpamplona.amethyst.ui.components.ContentWarningGate import com.vitorpamplona.amethyst.ui.components.ZoomableContentView +import com.vitorpamplona.amethyst.ui.components.collectContentWarningReasons +import com.vitorpamplona.amethyst.ui.components.mediaSizingModifier import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.note.ReactionsRow import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.video.UserCardHeader import com.vitorpamplona.quartz.nip01Core.core.Event 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 kotlin.text.ifEmpty @@ -99,13 +103,13 @@ private fun VideoCardImage( val event = (event as? Event) ?: return val imeta = videoEvent.imetaTags().getOrNull(0) ?: return + val isSensitive = event.isSensitiveOrNSFW() + val reasons = collectContentWarningReasons(event) + val isImage = imeta.mimeType?.startsWith("image/") == true || RichTextParser.isImageUrl(imeta.url) val content by remember(note) { val description = event.content.ifEmpty { null } ?: imeta.alt ?: event.alt() - val isImage = imeta.mimeType?.startsWith("image/") == true || RichTextParser.isImageUrl(imeta.url) - val isSensitive = event.isSensitiveOrNSFW() - val contentWarning = event.contentWarningReason() mutableStateOf( if (isImage) { @@ -116,8 +120,6 @@ private fun VideoCardImage( blurhash = imeta.blurhash, dim = imeta.dimension, uri = note.toNostrUri(), - contentWarning = contentWarning, - isSensitive = isSensitive, mimeType = imeta.mimeType, ) } else { @@ -129,20 +131,29 @@ private fun VideoCardImage( dim = imeta.dimension, uri = note.toNostrUri(), authorName = note.author?.toBestDisplayName(), - contentWarning = contentWarning, - isSensitive = isSensitive, mimeType = imeta.mimeType, ) }, ) } - ZoomableContentView( - content = content, - roundedCorner = false, - contentScale = ContentScale.FillWidth, + val ratio = imeta.dimension?.aspectRatio() ?: MediaAspectRatioCache.get(imeta.url) + + ContentWarningGate( + isSensitive = isSensitive, + reasons = reasons, + preloadUrls = if (isImage) listOf(imeta.url) else emptyList(), accountViewModel = accountViewModel, - ) + modifier = mediaSizingModifier(ratio, ContentScale.FillWidth), + backdrop = imeta.blurhash?.let { blurhash -> { BlurhashBackdrop(blurhash, content.description) } }, + ) { + ZoomableContentView( + content = content, + roundedCorner = false, + contentScale = ContentScale.FillWidth, + accountViewModel = accountViewModel, + ) + } } @Composable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/FileHeaderCardCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/FileHeaderCardCompose.kt index c117b8eba..8d90d907d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/FileHeaderCardCompose.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/FileHeaderCardCompose.kt @@ -42,14 +42,18 @@ import com.vitorpamplona.amethyst.commons.richtext.BaseMediaContent import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo import com.vitorpamplona.amethyst.commons.richtext.RichTextParser +import com.vitorpamplona.amethyst.model.MediaAspectRatioCache import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.ui.components.BlurhashBackdrop +import com.vitorpamplona.amethyst.ui.components.ContentWarningGate import com.vitorpamplona.amethyst.ui.components.ZoomableContentView +import com.vitorpamplona.amethyst.ui.components.collectContentWarningReasons +import com.vitorpamplona.amethyst.ui.components.mediaSizingModifier import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.note.ReactionsRow import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.quartz.nip01Core.core.Event 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 kotlin.text.ifEmpty @@ -96,16 +100,17 @@ private fun FileHeaderCardImage( ) { val fullUrl = event.url() ?: return + val isSensitive = event.isSensitiveOrNSFW() + val reasons = collectContentWarningReasons(event) + val isImage = event.mimeType()?.startsWith("image/") == true || RichTextParser.isImageUrl(fullUrl) + val blurHash = event.blurhash() + val dimensions = event.dimensions() + val content by remember(note) { - val blurHash = event.blurhash() val hash = event.hash() - val dimensions = event.dimensions() val description = event.content.ifEmpty { null } ?: event.alt() - val isImage = event.mimeType()?.startsWith("image/") == true || RichTextParser.isImageUrl(fullUrl) val uri = note.toNostrUri() val mimeType = event.mimeType() - val isSensitive = event.isSensitiveOrNSFW() - val contentWarning = event.contentWarningReason() mutableStateOf( if (isImage) { @@ -116,8 +121,6 @@ private fun FileHeaderCardImage( blurhash = blurHash, dim = dimensions, uri = uri, - contentWarning = contentWarning, - isSensitive = isSensitive, mimeType = mimeType, ) } else { @@ -129,20 +132,29 @@ private fun FileHeaderCardImage( dim = dimensions, uri = uri, authorName = note.author?.toBestDisplayName(), - contentWarning = contentWarning, - isSensitive = isSensitive, mimeType = mimeType, ) }, ) } - ZoomableContentView( - content = content, - roundedCorner = false, - contentScale = ContentScale.FillWidth, + val ratio = dimensions?.aspectRatio() ?: MediaAspectRatioCache.get(fullUrl) + + ContentWarningGate( + isSensitive = isSensitive, + reasons = reasons, + preloadUrls = if (isImage) listOf(fullUrl) else emptyList(), accountViewModel = accountViewModel, - ) + modifier = mediaSizingModifier(ratio, ContentScale.FillWidth), + backdrop = blurHash?.let { blurhash -> { BlurhashBackdrop(blurhash, content.description) } }, + ) { + ZoomableContentView( + content = content, + roundedCorner = false, + contentScale = ContentScale.FillWidth, + accountViewModel = accountViewModel, + ) + } } @Composable diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/richtext/MediaContentModels.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/richtext/MediaContentModels.kt index 5f9d30c06..d390a1834 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/richtext/MediaContentModels.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/richtext/MediaContentModels.kt @@ -51,7 +51,6 @@ open class MediaUrlImage( dim: DimensionTag? = null, uri: String? = null, val contentWarning: String? = null, - val isSensitive: Boolean = contentWarning != null, mimeType: String? = null, ) : MediaUrlContent(url, description, hash, dim, blurhash, uri, mimeType) @@ -63,12 +62,11 @@ class EncryptedMediaUrlImage( dim: DimensionTag? = null, uri: String? = null, contentWarning: String? = null, - isSensitive: Boolean = contentWarning != null, mimeType: String? = null, val encryptionAlgo: String, val encryptionKey: ByteArray, val encryptionNonce: ByteArray, -) : MediaUrlImage(url, description, hash, blurhash, dim, uri, contentWarning, isSensitive, mimeType) +) : MediaUrlImage(url, description, hash, blurhash, dim, uri, contentWarning, mimeType) @Immutable open class MediaUrlVideo( @@ -81,7 +79,6 @@ open class MediaUrlVideo( val authorName: String? = null, blurhash: String? = null, val contentWarning: String? = null, - val isSensitive: Boolean = contentWarning != null, mimeType: String? = null, ) : MediaUrlContent(url, description, hash, dim, blurhash, uri, mimeType) @@ -96,12 +93,11 @@ class EncryptedMediaUrlVideo( authorName: String? = null, blurhash: String? = null, contentWarning: String? = null, - isSensitive: Boolean = contentWarning != null, mimeType: String? = null, val encryptionAlgo: String, val encryptionKey: ByteArray, val encryptionNonce: ByteArray, -) : MediaUrlVideo(url, description, hash, dim, uri, artworkUri, authorName, blurhash, contentWarning, isSensitive, mimeType) +) : MediaUrlVideo(url, description, hash, dim, uri, artworkUri, authorName, blurhash, contentWarning, mimeType) @Immutable abstract class MediaPreloadedContent(