From 11666541493f1c9e35b23ddec5110d79fbe3f1f8 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 18 Apr 2026 18:51:17 +0000 Subject: [PATCH] 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 --- .../ui/components/SensitivityWarning.kt | 83 +++++++------------ .../ui/components/ZoomableContentView.kt | 20 ++++- .../amethyst/ui/note/types/PictureDisplay.kt | 83 ++++++++++--------- .../amethyst/ui/note/types/VideoDisplay.kt | 23 +++-- .../loggedIn/pictures/PictureCardCompose.kt | 43 ++++------ .../loggedIn/shorts/VideoCardCompose.kt | 30 +++---- .../loggedIn/video/FileHeaderCardCompose.kt | 23 +++-- .../commons/richtext/MediaContentModels.kt | 8 +- 8 files changed, 158 insertions(+), 155 deletions(-) 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 387c6c99c..0375cfa1a 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 @@ -76,31 +76,6 @@ fun SensitivityWarning( 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 fun SensitivityWarning( 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 fun ObserveSensitivityWarning( 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 @Composable fun ContentWarningNotePreview() { 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 08f3ccc1c..1de17f9ea 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 @@ -150,7 +150,15 @@ fun ZoomableContentView( when (content) { 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 -> val mainImageModifier = Modifier @@ -164,7 +172,15 @@ fun ZoomableContentView( } 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( modifier = Modifier.fillMaxWidth().then(boundsTrackingModifier), contentAlignment = Alignment.Center, 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 825a9d9f4..f6ac70130 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 @@ -39,12 +39,13 @@ import com.vitorpamplona.amethyst.commons.model.EmptyTagList import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage import com.vitorpamplona.amethyst.model.Note 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.ZoomableContentView import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel 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 kotlinx.collections.immutable.toImmutableList @@ -60,6 +61,8 @@ fun PictureDisplay( ) { val event = (note.event as? PictureEvent) ?: return val uri = note.toNostrUri() + val isSensitive = event.isSensitiveOrNSFW() + val contentWarning = event.contentWarningReason() val images by remember(note) { @@ -74,6 +77,8 @@ fun PictureDisplay( blurhash = it.blurhash, dim = it.dimension, uri = uri, + contentWarning = contentWarning, + isSensitive = isSensitive, mimeType = it.mimeType, ) }.toImmutableList(), @@ -85,53 +90,51 @@ fun PictureDisplay( if (first != null) { val title = event.title() - SensitivityWarning(note = note, accountViewModel = accountViewModel) { - Column { - if (title != null) { - Text( - modifier = Modifier.padding(padding), - text = title, - style = MaterialTheme.typography.bodyLarge, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - } else { - Spacer(StdVertSpacer) - } + Column { + if (title != null) { + Text( + modifier = Modifier.padding(padding), + text = title, + style = MaterialTheme.typography.bodyLarge, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } else { + 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( - content = images.first(), + content = images[it], images = images, roundedCorner = roundedCorner, - contentScale = ContentScale.FillWidth, + contentScale = ContentScale.Crop, 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, + ) } } } 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 581ef978b..a5b73df9e 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 @@ -30,11 +30,12 @@ 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.Note -import com.vitorpamplona.amethyst.ui.components.SensitivityWarning import com.vitorpamplona.amethyst.ui.components.ZoomableContentView 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 @Composable @@ -53,6 +54,8 @@ fun JustVideoDisplay( 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) { @@ -63,6 +66,8 @@ fun JustVideoDisplay( blurhash = imeta.blurhash, dim = imeta.dimension, uri = note.toNostrUri(), + contentWarning = contentWarning, + isSensitive = isSensitive, mimeType = imeta.mimeType, ) } else { @@ -74,18 +79,18 @@ fun JustVideoDisplay( dim = imeta.dimension, uri = note.toNostrUri(), authorName = note.author?.toBestDisplayName(), + contentWarning = contentWarning, + isSensitive = isSensitive, mimeType = imeta.mimeType, ) }, ) } - SensitivityWarning(note = note, accountViewModel = accountViewModel) { - ZoomableContentView( - content = content, - roundedCorner = roundedCorner, - contentScale = contentScale, - accountViewModel = accountViewModel, - ) - } + 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 325e3313b..8cb831ca8 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 @@ -39,15 +39,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.SensitivityWarning import com.vitorpamplona.amethyst.ui.components.ZoomableContentView 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.nip36SensitiveContent.contentWarningReason +import com.vitorpamplona.quartz.nip36SensitiveContent.isSensitiveOrNSFW import com.vitorpamplona.quartz.nip68Picture.PictureEvent import kotlinx.collections.immutable.toImmutableList @@ -92,6 +92,8 @@ private fun PictureCardImage( accountViewModel: AccountViewModel, ) { val uri = note.toNostrUri() + val isSensitive = event.isSensitiveOrNSFW() + val contentWarning = event.contentWarningReason() val images by remember(note) { @@ -106,6 +108,8 @@ private fun PictureCardImage( blurhash = it.blurhash, dim = it.dimension, uri = uri, + contentWarning = contentWarning, + isSensitive = isSensitive, mimeType = it.mimeType, ) }.toImmutableList(), @@ -113,34 +117,23 @@ private fun PictureCardImage( } if (images.isNotEmpty()) { - val first = images.first() - val ratio = first.dim?.aspectRatio() ?: MediaAspectRatioCache.get(first.url) - - SensitivityWarning( - note = note, - blurhash = first.blurhash, - ratio = ratio, - description = first.description, - accountViewModel = accountViewModel, - ) { - if (images.size == 1) { + if (images.size == 1) { + ZoomableContentView( + content = images.first(), + images = images, + roundedCorner = false, + contentScale = ContentScale.FillWidth, + accountViewModel = accountViewModel, + ) + } else { + AutoNonlazyGrid(images.size) { ZoomableContentView( - content = first, + content = images[it], images = images, roundedCorner = false, - contentScale = ContentScale.FillWidth, + contentScale = ContentScale.Crop, accountViewModel = accountViewModel, ) - } else { - 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 18dbd921d..21cab732e 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,9 +42,7 @@ 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.SensitivityWarning import com.vitorpamplona.amethyst.ui.components.ZoomableContentView import com.vitorpamplona.amethyst.ui.navigation.navs.INav 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.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 @@ -104,6 +104,8 @@ private fun VideoCardImage( 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) { @@ -114,6 +116,8 @@ private fun VideoCardImage( blurhash = imeta.blurhash, dim = imeta.dimension, uri = note.toNostrUri(), + contentWarning = contentWarning, + isSensitive = isSensitive, mimeType = imeta.mimeType, ) } else { @@ -125,28 +129,20 @@ private fun VideoCardImage( dim = imeta.dimension, uri = note.toNostrUri(), authorName = note.author?.toBestDisplayName(), + contentWarning = contentWarning, + isSensitive = isSensitive, mimeType = imeta.mimeType, ) }, ) } - val ratio = imeta.dimension?.aspectRatio() ?: MediaAspectRatioCache.get(imeta.url) - - SensitivityWarning( - note = note, - blurhash = imeta.blurhash, - ratio = ratio, - description = content.description, + ZoomableContentView( + content = content, + roundedCorner = false, + contentScale = ContentScale.FillWidth, accountViewModel = accountViewModel, - ) { - 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 3b6dca98a..c117b8eba 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 @@ -43,13 +43,14 @@ 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.Note -import com.vitorpamplona.amethyst.ui.components.SensitivityWarning import com.vitorpamplona.amethyst.ui.components.ZoomableContentView 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 @@ -103,6 +104,8 @@ private fun FileHeaderCardImage( 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) { @@ -113,6 +116,8 @@ private fun FileHeaderCardImage( blurhash = blurHash, dim = dimensions, uri = uri, + contentWarning = contentWarning, + isSensitive = isSensitive, mimeType = mimeType, ) } else { @@ -124,20 +129,20 @@ private fun FileHeaderCardImage( dim = dimensions, uri = uri, authorName = note.author?.toBestDisplayName(), + contentWarning = contentWarning, + isSensitive = isSensitive, mimeType = mimeType, ) }, ) } - SensitivityWarning(note = note, accountViewModel = accountViewModel) { - ZoomableContentView( - content = content, - roundedCorner = false, - contentScale = ContentScale.FillWidth, - accountViewModel = accountViewModel, - ) - } + 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 d390a1834..5f9d30c06 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,6 +51,7 @@ 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) @@ -62,11 +63,12 @@ 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, mimeType) +) : MediaUrlImage(url, description, hash, blurhash, dim, uri, contentWarning, isSensitive, mimeType) @Immutable open class MediaUrlVideo( @@ -79,6 +81,7 @@ 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) @@ -93,11 +96,12 @@ 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, mimeType) +) : MediaUrlVideo(url, description, hash, dim, uri, artworkUri, authorName, blurhash, contentWarning, isSensitive, mimeType) @Immutable abstract class MediaPreloadedContent(