Refactoring some names for the parsers.

This commit is contained in:
Vitor Pamplona
2024-02-21 16:22:17 -05:00
parent 90175198f0
commit 96f29fc5ca
12 changed files with 154 additions and 134 deletions

View File

@@ -43,7 +43,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.ExpandableTextParser import com.vitorpamplona.amethyst.commons.ExpandableTextCutOffCalculator
import com.vitorpamplona.amethyst.ui.note.getGradient import com.vitorpamplona.amethyst.ui.note.getGradient
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
@@ -63,7 +63,7 @@ fun ExpandableRichTextViewer(
) { ) {
var showFullText by remember { mutableStateOf(false) } var showFullText by remember { mutableStateOf(false) }
val whereToCut = remember(content) { ExpandableTextParser.computeWhereToCutIfPostIsTooLong(content) } val whereToCut = remember(content) { ExpandableTextCutOffCalculator.indexToCutOff(content) }
val text by val text by
remember(content) { remember(content) {

View File

@@ -29,8 +29,8 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import com.vitorpamplona.amethyst.commons.ZoomableUrlImage import com.vitorpamplona.amethyst.commons.MediaUrlImage
import com.vitorpamplona.amethyst.commons.ZoomableUrlVideo import com.vitorpamplona.amethyst.commons.MediaUrlVideo
import com.vitorpamplona.amethyst.model.UrlCachedPreviewer import com.vitorpamplona.amethyst.model.UrlCachedPreviewer
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.HalfVertPadding import com.vitorpamplona.amethyst.ui.theme.HalfVertPadding
@@ -69,7 +69,7 @@ fun LoadUrlPreview(
if (state.previewInfo.mimeType.type == "image") { if (state.previewInfo.mimeType.type == "image") {
Box(modifier = HalfVertPadding) { Box(modifier = HalfVertPadding) {
ZoomableContentView( ZoomableContentView(
content = ZoomableUrlImage(url), content = MediaUrlImage(url),
roundedCorner = true, roundedCorner = true,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
) )
@@ -77,7 +77,7 @@ fun LoadUrlPreview(
} else if (state.previewInfo.mimeType.type == "video") { } else if (state.previewInfo.mimeType.type == "video") {
Box(modifier = HalfVertPadding) { Box(modifier = HalfVertPadding) {
ZoomableContentView( ZoomableContentView(
content = ZoomableUrlVideo(url), content = MediaUrlVideo(url),
roundedCorner = true, roundedCorner = true,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
) )

View File

@@ -78,6 +78,7 @@ import com.vitorpamplona.amethyst.commons.HashTagSegment
import com.vitorpamplona.amethyst.commons.ImageSegment import com.vitorpamplona.amethyst.commons.ImageSegment
import com.vitorpamplona.amethyst.commons.InvoiceSegment import com.vitorpamplona.amethyst.commons.InvoiceSegment
import com.vitorpamplona.amethyst.commons.LinkSegment import com.vitorpamplona.amethyst.commons.LinkSegment
import com.vitorpamplona.amethyst.commons.MediaUrlImage
import com.vitorpamplona.amethyst.commons.PhoneSegment import com.vitorpamplona.amethyst.commons.PhoneSegment
import com.vitorpamplona.amethyst.commons.RegularTextSegment import com.vitorpamplona.amethyst.commons.RegularTextSegment
import com.vitorpamplona.amethyst.commons.RichTextParser import com.vitorpamplona.amethyst.commons.RichTextParser
@@ -85,7 +86,6 @@ import com.vitorpamplona.amethyst.commons.RichTextViewerState
import com.vitorpamplona.amethyst.commons.SchemelessUrlSegment import com.vitorpamplona.amethyst.commons.SchemelessUrlSegment
import com.vitorpamplona.amethyst.commons.Segment import com.vitorpamplona.amethyst.commons.Segment
import com.vitorpamplona.amethyst.commons.WithdrawSegment import com.vitorpamplona.amethyst.commons.WithdrawSegment
import com.vitorpamplona.amethyst.commons.ZoomableUrlImage
import com.vitorpamplona.amethyst.model.HashtagIcon import com.vitorpamplona.amethyst.model.HashtagIcon
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.User
@@ -417,7 +417,7 @@ private fun RenderContentAsMarkdown(
ZoomableContentView( ZoomableContentView(
content = content =
remember(destination, tags) { remember(destination, tags) {
RichTextParser().parseMediaUrl(destination, tags ?: EmptyTagList) ?: ZoomableUrlImage(url = destination) RichTextParser().parseMediaUrl(destination, tags ?: EmptyTagList) ?: MediaUrlImage(url = destination)
}, },
roundedCorner = true, roundedCorner = true,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,

View File

@@ -106,13 +106,13 @@ import coil.compose.AsyncImage
import coil.compose.AsyncImagePainter import coil.compose.AsyncImagePainter
import coil.imageLoader import coil.imageLoader
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.ZoomableContent import com.vitorpamplona.amethyst.commons.BaseMediaContent
import com.vitorpamplona.amethyst.commons.ZoomableLocalImage import com.vitorpamplona.amethyst.commons.MediaLocalImage
import com.vitorpamplona.amethyst.commons.ZoomableLocalVideo import com.vitorpamplona.amethyst.commons.MediaLocalVideo
import com.vitorpamplona.amethyst.commons.ZoomablePreloadedContent import com.vitorpamplona.amethyst.commons.MediaPreloadedContent
import com.vitorpamplona.amethyst.commons.ZoomableUrlContent import com.vitorpamplona.amethyst.commons.MediaUrlContent
import com.vitorpamplona.amethyst.commons.ZoomableUrlImage import com.vitorpamplona.amethyst.commons.MediaUrlImage
import com.vitorpamplona.amethyst.commons.ZoomableUrlVideo import com.vitorpamplona.amethyst.commons.MediaUrlVideo
import com.vitorpamplona.amethyst.service.BlurHashRequester import com.vitorpamplona.amethyst.service.BlurHashRequester
import com.vitorpamplona.amethyst.ui.actions.CloseButton import com.vitorpamplona.amethyst.ui.actions.CloseButton
import com.vitorpamplona.amethyst.ui.actions.InformationDialog import com.vitorpamplona.amethyst.ui.actions.InformationDialog
@@ -147,8 +147,8 @@ import net.engawapg.lib.zoomable.zoomable
@Composable @Composable
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
fun ZoomableContentView( fun ZoomableContentView(
content: ZoomableContent, content: BaseMediaContent,
images: ImmutableList<ZoomableContent> = remember(content) { listOf(content).toImmutableList() }, images: ImmutableList<BaseMediaContent> = remember(content) { listOf(content).toImmutableList() },
roundedCorner: Boolean, roundedCorner: Boolean,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
) { ) {
@@ -169,13 +169,13 @@ fun ZoomableContentView(
Modifier.fillMaxWidth() Modifier.fillMaxWidth()
} }
if (content is ZoomableUrlContent) { if (content is MediaUrlContent) {
mainImageModifier = mainImageModifier =
mainImageModifier.combinedClickable( mainImageModifier.combinedClickable(
onClick = { dialogOpen = true }, onClick = { dialogOpen = true },
onLongClick = { shareOpen.value = true }, onLongClick = { shareOpen.value = true },
) )
} else if (content is ZoomablePreloadedContent) { } else if (content is MediaPreloadedContent) {
mainImageModifier = mainImageModifier =
mainImageModifier.combinedClickable( mainImageModifier.combinedClickable(
onClick = { dialogOpen = true }, onClick = { dialogOpen = true },
@@ -186,11 +186,11 @@ fun ZoomableContentView(
} }
when (content) { when (content) {
is ZoomableUrlImage -> is MediaUrlImage ->
SensitivityWarning(content.contentWarning != null, accountViewModel) { SensitivityWarning(content.contentWarning != null, accountViewModel) {
UrlImageView(content, mainImageModifier, accountViewModel = accountViewModel) UrlImageView(content, mainImageModifier, accountViewModel = accountViewModel)
} }
is ZoomableUrlVideo -> is MediaUrlVideo ->
SensitivityWarning(content.contentWarning != null, accountViewModel) { SensitivityWarning(content.contentWarning != null, accountViewModel) {
VideoView( VideoView(
videoUri = content.url, videoUri = content.url,
@@ -205,9 +205,9 @@ fun ZoomableContentView(
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
) )
} }
is ZoomableLocalImage -> is MediaLocalImage ->
LocalImageView(content, mainImageModifier, accountViewModel = accountViewModel) LocalImageView(content, mainImageModifier, accountViewModel = accountViewModel)
is ZoomableLocalVideo -> is MediaLocalVideo ->
content.localFile?.let { content.localFile?.let {
VideoView( VideoView(
videoUri = it.toUri().toString(), videoUri = it.toUri().toString(),
@@ -229,7 +229,7 @@ fun ZoomableContentView(
@Composable @Composable
private fun LocalImageView( private fun LocalImageView(
content: ZoomableLocalImage, content: MediaLocalImage,
mainImageModifier: Modifier, mainImageModifier: Modifier,
topPaddingForControllers: Dp = Dp.Unspecified, topPaddingForControllers: Dp = Dp.Unspecified,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
@@ -296,7 +296,7 @@ private fun LocalImageView(
@Composable @Composable
private fun UrlImageView( private fun UrlImageView(
content: ZoomableUrlImage, content: MediaUrlImage,
mainImageModifier: Modifier, mainImageModifier: Modifier,
topPaddingForControllers: Dp = Dp.Unspecified, topPaddingForControllers: Dp = Dp.Unspecified,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
@@ -419,7 +419,7 @@ private fun InlineDownloadIcon(showImage: MutableState<Boolean>) =
@OptIn(ExperimentalLayoutApi::class) @OptIn(ExperimentalLayoutApi::class)
private fun AddedImageFeatures( private fun AddedImageFeatures(
painter: MutableState<AsyncImagePainter.State?>, painter: MutableState<AsyncImagePainter.State?>,
content: ZoomableLocalImage, content: MediaLocalImage,
contentScale: ContentScale, contentScale: ContentScale,
myModifier: Modifier, myModifier: Modifier,
verifiedModifier: Modifier, verifiedModifier: Modifier,
@@ -481,7 +481,7 @@ private fun AddedImageFeatures(
@OptIn(ExperimentalLayoutApi::class) @OptIn(ExperimentalLayoutApi::class)
private fun AddedImageFeatures( private fun AddedImageFeatures(
painter: MutableState<AsyncImagePainter.State?>, painter: MutableState<AsyncImagePainter.State?>,
content: ZoomableUrlImage, content: MediaUrlImage,
contentScale: ContentScale, contentScale: ContentScale,
myModifier: Modifier, myModifier: Modifier,
verifiedModifier: Modifier, verifiedModifier: Modifier,
@@ -576,8 +576,8 @@ fun aspectRatio(dim: String?): Float? {
} }
@Composable @Composable
private fun DisplayUrlWithLoadingSymbol(content: ZoomableContent) { private fun DisplayUrlWithLoadingSymbol(content: BaseMediaContent) {
var cnt by remember { mutableStateOf<ZoomableContent?>(null) } var cnt by remember { mutableStateOf<BaseMediaContent?>(null) }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
launch(Dispatchers.IO) { launch(Dispatchers.IO) {
@@ -590,7 +590,7 @@ private fun DisplayUrlWithLoadingSymbol(content: ZoomableContent) {
} }
@Composable @Composable
private fun DisplayUrlWithLoadingSymbolWait(content: ZoomableContent) { private fun DisplayUrlWithLoadingSymbolWait(content: BaseMediaContent) {
val uri = LocalUriHandler.current val uri = LocalUriHandler.current
val primary = MaterialTheme.colorScheme.primary val primary = MaterialTheme.colorScheme.primary
@@ -602,7 +602,7 @@ private fun DisplayUrlWithLoadingSymbolWait(content: ZoomableContent) {
val annotatedTermsString = val annotatedTermsString =
remember { remember {
buildAnnotatedString { buildAnnotatedString {
if (content is ZoomableUrlContent) { if (content is MediaUrlContent) {
withStyle(clickableTextStyle) { withStyle(clickableTextStyle) {
pushStringAnnotation("routeToImage", "") pushStringAnnotation("routeToImage", "")
append(content.url + " ") append(content.url + " ")
@@ -624,7 +624,7 @@ private fun DisplayUrlWithLoadingSymbolWait(content: ZoomableContent) {
val pressIndicator = val pressIndicator =
remember { remember {
if (content is ZoomableUrlContent) { if (content is MediaUrlContent) {
Modifier.clickable { runCatching { uri.openUri(content.url) } } Modifier.clickable { runCatching { uri.openUri(content.url) } }
} else { } else {
Modifier Modifier
@@ -676,8 +676,8 @@ fun DisplayBlurHash(
@Composable @Composable
fun ZoomableImageDialog( fun ZoomableImageDialog(
imageUrl: ZoomableContent, imageUrl: BaseMediaContent,
allImages: ImmutableList<ZoomableContent> = listOf(imageUrl).toImmutableList(), allImages: ImmutableList<BaseMediaContent> = listOf(imageUrl).toImmutableList(),
onDismiss: () -> Unit, onDismiss: () -> Unit,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
) { ) {
@@ -740,8 +740,8 @@ fun ZoomableImageDialog(
@Composable @Composable
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
private fun DialogContent( private fun DialogContent(
allImages: ImmutableList<ZoomableContent>, allImages: ImmutableList<BaseMediaContent>,
imageUrl: ZoomableContent, imageUrl: BaseMediaContent,
onDismiss: () -> Unit, onDismiss: () -> Unit,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
) { ) {
@@ -801,13 +801,13 @@ private fun DialogContent(
CloseButton(onPress = onDismiss) CloseButton(onPress = onDismiss)
allImages.getOrNull(pagerState.currentPage)?.let { myContent -> allImages.getOrNull(pagerState.currentPage)?.let { myContent ->
if (myContent is ZoomableUrlContent) { if (myContent is MediaUrlContent) {
Row { Row {
CopyToClipboard(content = myContent) CopyToClipboard(content = myContent)
Spacer(modifier = StdHorzSpacer) Spacer(modifier = StdHorzSpacer)
SaveToGallery(url = myContent.url) SaveToGallery(url = myContent.url)
} }
} else if (myContent is ZoomableLocalImage && myContent.localFileExists()) { } else if (myContent is MediaLocalImage && myContent.localFileExists()) {
SaveToGallery( SaveToGallery(
localFile = myContent.localFile!!, localFile = myContent.localFile!!,
mimeType = myContent.mimeType, mimeType = myContent.mimeType,
@@ -857,7 +857,7 @@ fun InlineCarrousel(
} }
@Composable @Composable
private fun CopyToClipboard(content: ZoomableContent) { private fun CopyToClipboard(content: BaseMediaContent) {
val popupExpanded = remember { mutableStateOf(false) } val popupExpanded = remember { mutableStateOf(false) }
OutlinedButton( OutlinedButton(
@@ -877,7 +877,7 @@ private fun CopyToClipboard(content: ZoomableContent) {
@Composable @Composable
private fun ShareImageAction( private fun ShareImageAction(
popupExpanded: MutableState<Boolean>, popupExpanded: MutableState<Boolean>,
content: ZoomableContent, content: BaseMediaContent,
onDismiss: () -> Unit, onDismiss: () -> Unit,
) { ) {
DropdownMenu( DropdownMenu(
@@ -886,7 +886,7 @@ private fun ShareImageAction(
) { ) {
val clipboardManager = LocalClipboardManager.current val clipboardManager = LocalClipboardManager.current
if (content is ZoomableUrlContent) { if (content is MediaUrlContent) {
DropdownMenuItem( DropdownMenuItem(
text = { Text(stringResource(R.string.copy_url_to_clipboard)) }, text = { Text(stringResource(R.string.copy_url_to_clipboard)) },
onClick = { onClick = {
@@ -905,7 +905,7 @@ private fun ShareImageAction(
} }
} }
if (content is ZoomablePreloadedContent) { if (content is MediaPreloadedContent) {
DropdownMenuItem( DropdownMenuItem(
text = { Text(stringResource(R.string.copy_the_note_id_to_the_clipboard)) }, text = { Text(stringResource(R.string.copy_the_note_id_to_the_clipboard)) },
onClick = { onClick = {
@@ -919,7 +919,7 @@ private fun ShareImageAction(
@Composable @Composable
private fun RenderImageOrVideo( private fun RenderImageOrVideo(
content: ZoomableContent, content: BaseMediaContent,
roundedCorner: Boolean, roundedCorner: Boolean,
topPaddingForControllers: Dp = Dp.Unspecified, topPaddingForControllers: Dp = Dp.Unspecified,
onControllerVisibilityChanged: ((Boolean) -> Unit)? = null, onControllerVisibilityChanged: ((Boolean) -> Unit)? = null,
@@ -928,7 +928,7 @@ private fun RenderImageOrVideo(
) { ) {
val automaticallyStartPlayback = remember { mutableStateOf<Boolean>(true) } val automaticallyStartPlayback = remember { mutableStateOf<Boolean>(true) }
if (content is ZoomableUrlImage) { if (content is MediaUrlImage) {
val mainModifier = val mainModifier =
Modifier.fillMaxSize() Modifier.fillMaxSize()
.zoomable( .zoomable(
@@ -947,7 +947,7 @@ private fun RenderImageOrVideo(
accountViewModel, accountViewModel,
alwayShowImage = true, alwayShowImage = true,
) )
} else if (content is ZoomableUrlVideo) { } else if (content is MediaUrlVideo) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxSize(1f)) { Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxSize(1f)) {
VideoViewInner( VideoViewInner(
videoUri = content.url, videoUri = content.url,
@@ -960,7 +960,7 @@ private fun RenderImageOrVideo(
onControllerVisibilityChanged = onControllerVisibilityChanged, onControllerVisibilityChanged = onControllerVisibilityChanged,
) )
} }
} else if (content is ZoomableLocalImage) { } else if (content is MediaLocalImage) {
val mainModifier = val mainModifier =
Modifier.fillMaxSize() Modifier.fillMaxSize()
.zoomable( .zoomable(
@@ -979,7 +979,7 @@ private fun RenderImageOrVideo(
accountViewModel, accountViewModel,
alwayShowImage = true, alwayShowImage = true,
) )
} else if (content is ZoomableLocalVideo) { } else if (content is MediaLocalVideo) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxSize(1f)) { Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxSize(1f)) {
content.localFile?.let { content.localFile?.let {
VideoViewInner( VideoViewInner(
@@ -999,7 +999,7 @@ private fun RenderImageOrVideo(
@OptIn(ExperimentalCoilApi::class) @OptIn(ExperimentalCoilApi::class)
private fun verifyHash( private fun verifyHash(
content: ZoomableUrlContent, content: MediaUrlContent,
context: Context, context: Context,
): Boolean? { ): Boolean? {
if (content.hash == null) return null if (content.hash == null) return null

View File

@@ -95,12 +95,12 @@ import coil.request.SuccessResult
import com.fonfon.kgeohash.GeoHash import com.fonfon.kgeohash.GeoHash
import com.fonfon.kgeohash.toGeoHash import com.fonfon.kgeohash.toGeoHash
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.BaseMediaContent
import com.vitorpamplona.amethyst.commons.MediaLocalImage
import com.vitorpamplona.amethyst.commons.MediaLocalVideo
import com.vitorpamplona.amethyst.commons.MediaUrlImage
import com.vitorpamplona.amethyst.commons.MediaUrlVideo
import com.vitorpamplona.amethyst.commons.RichTextParser import com.vitorpamplona.amethyst.commons.RichTextParser
import com.vitorpamplona.amethyst.commons.ZoomableContent
import com.vitorpamplona.amethyst.commons.ZoomableLocalImage
import com.vitorpamplona.amethyst.commons.ZoomableLocalVideo
import com.vitorpamplona.amethyst.commons.ZoomableUrlImage
import com.vitorpamplona.amethyst.commons.ZoomableUrlVideo
import com.vitorpamplona.amethyst.model.AddressableNote import com.vitorpamplona.amethyst.model.AddressableNote
import com.vitorpamplona.amethyst.model.Channel import com.vitorpamplona.amethyst.model.Channel
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
@@ -3065,9 +3065,9 @@ fun FileHeaderDisplay(
val isImage = RichTextParser.isImageUrl(fullUrl) val isImage = RichTextParser.isImageUrl(fullUrl)
val uri = note.toNostrUri() val uri = note.toNostrUri()
mutableStateOf<ZoomableContent>( mutableStateOf<BaseMediaContent>(
if (isImage) { if (isImage) {
ZoomableUrlImage( MediaUrlImage(
url = fullUrl, url = fullUrl,
description = description, description = description,
hash = hash, hash = hash,
@@ -3076,7 +3076,7 @@ fun FileHeaderDisplay(
uri = uri, uri = uri,
) )
} else { } else {
ZoomableUrlVideo( MediaUrlVideo(
url = fullUrl, url = fullUrl,
description = description, description = description,
hash = hash, hash = hash,
@@ -3124,9 +3124,9 @@ fun VideoDisplay(
val isImage = RichTextParser.isImageUrl(fullUrl) val isImage = RichTextParser.isImageUrl(fullUrl)
val uri = note.toNostrUri() val uri = note.toNostrUri()
mutableStateOf<ZoomableContent>( mutableStateOf<BaseMediaContent>(
if (isImage) { if (isImage) {
ZoomableUrlImage( MediaUrlImage(
url = fullUrl, url = fullUrl,
description = description, description = description,
hash = hash, hash = hash,
@@ -3135,7 +3135,7 @@ fun VideoDisplay(
uri = uri, uri = uri,
) )
} else { } else {
ZoomableUrlVideo( MediaUrlVideo(
url = fullUrl, url = fullUrl,
description = description, description = description,
hash = hash, hash = hash,
@@ -3265,17 +3265,17 @@ private fun ObserverAndRenderNIP95(
val newContent = val newContent =
if (mimeType?.startsWith("image") == true) { if (mimeType?.startsWith("image") == true) {
ZoomableLocalImage( MediaLocalImage(
localFile = localDir, localFile = localDir,
mimeType = mimeType, mimeType = mimeType,
description = description, description = description,
blurhash = blurHash,
dim = dimensions, dim = dimensions,
blurhash = blurHash,
isVerified = true, isVerified = true,
uri = uri, uri = uri,
) )
} else { } else {
ZoomableLocalVideo( MediaLocalVideo(
localFile = localDir, localFile = localDir,
mimeType = mimeType, mimeType = mimeType,
description = description, description = description,
@@ -3286,7 +3286,7 @@ private fun ObserverAndRenderNIP95(
) )
} }
mutableStateOf<ZoomableContent?>(newContent) mutableStateOf<BaseMediaContent?>(newContent)
} }
Crossfade(targetState = content) { Crossfade(targetState = content) {

View File

@@ -101,7 +101,7 @@ import androidx.lifecycle.distinctUntilChanged
import androidx.lifecycle.map import androidx.lifecycle.map
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.ZoomableUrlVideo import com.vitorpamplona.amethyst.commons.MediaUrlVideo
import com.vitorpamplona.amethyst.model.AddressableNote import com.vitorpamplona.amethyst.model.AddressableNote
import com.vitorpamplona.amethyst.model.Channel import com.vitorpamplona.amethyst.model.Channel
import com.vitorpamplona.amethyst.model.LiveActivitiesChannel import com.vitorpamplona.amethyst.model.LiveActivitiesChannel
@@ -681,7 +681,7 @@ fun ShowVideoStreaming(
) { ) {
val zoomableUrlVideo = val zoomableUrlVideo =
remember(it) { remember(it) {
ZoomableUrlVideo( MediaUrlVideo(
url = url, url = url,
description = title, description = title,
artworkUri = artworkUri, artworkUri = artworkUri,

View File

@@ -23,7 +23,7 @@ package com.vitorpamplona.amethyst.benchmark
import androidx.benchmark.junit4.BenchmarkRule import androidx.benchmark.junit4.BenchmarkRule
import androidx.benchmark.junit4.measureRepeated import androidx.benchmark.junit4.measureRepeated
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.vitorpamplona.amethyst.commons.ExpandableTextParser import com.vitorpamplona.amethyst.commons.ExpandableTextCutOffCalculator
import com.vitorpamplona.amethyst.commons.nthIndexOf import com.vitorpamplona.amethyst.commons.nthIndexOf
import junit.framework.TestCase import junit.framework.TestCase
import org.junit.Rule import org.junit.Rule
@@ -60,7 +60,7 @@ class ExpandableViewComputationBenchmark {
benchmarkRule.measureRepeated { benchmarkRule.measureRepeated {
TestCase.assertEquals( TestCase.assertEquals(
293, 293,
ExpandableTextParser.computeWhereToCutIfPostIsTooLong(testCase1), ExpandableTextCutOffCalculator.indexToCutOff(testCase1),
) )
} }
} }
@@ -70,7 +70,7 @@ class ExpandableViewComputationBenchmark {
benchmarkRule.measureRepeated { benchmarkRule.measureRepeated {
TestCase.assertEquals( TestCase.assertEquals(
355, 355,
ExpandableTextParser.computeWhereToCutIfPostIsTooLong(testCase2), ExpandableTextCutOffCalculator.indexToCutOff(testCase2),
) )
} }
} }
@@ -80,7 +80,7 @@ class ExpandableViewComputationBenchmark {
benchmarkRule.measureRepeated { benchmarkRule.measureRepeated {
TestCase.assertEquals( TestCase.assertEquals(
65, 65,
ExpandableTextParser.computeWhereToCutIfPostIsTooLong(testCase3), ExpandableTextCutOffCalculator.indexToCutOff(testCase3),
) )
} }
} }

View File

@@ -18,13 +18,10 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst package com.vitorpamplona.amethyst.commons
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.vitorpamplona.amethyst.commons.RichTextParser
import com.vitorpamplona.amethyst.commons.RichTextViewerState
import com.vitorpamplona.quartz.events.EmptyTagList import com.vitorpamplona.quartz.events.EmptyTagList
import org.junit.Assert
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@@ -688,8 +685,10 @@ class RichTextParserTest {
@Test @Test
fun testTextToParse() { fun testTextToParse() {
val state = RichTextParser().parseText(textToParse, EmptyTagList) val state =
Assert.assertEquals( com.vitorpamplona.amethyst.commons.RichTextParser()
.parseText(textToParse, EmptyTagList)
org.junit.Assert.assertEquals(
"relay.shitforce.one, relayable.org, universe.nostrich.land, nos.lol, universe.nostrich.land?lang=zh, universe.nostrich.land?lang=en, relay.damus.io, relay.nostr.wirednet.jp, offchain.pub, nostr.rocks, relay.wellorder.net, nostr.oxtr.dev, universe.nostrich.land?lang=ja, relay.mostr.pub, nostr.bitcoiner.social, Nostr-Check.com, MR.Rabbit, Ancap.su, zapper.lol, smies.me, baller.hodl", "relay.shitforce.one, relayable.org, universe.nostrich.land, nos.lol, universe.nostrich.land?lang=zh, universe.nostrich.land?lang=en, relay.damus.io, relay.nostr.wirednet.jp, offchain.pub, nostr.rocks, relay.wellorder.net, nostr.oxtr.dev, universe.nostrich.land?lang=ja, relay.mostr.pub, nostr.bitcoiner.social, Nostr-Check.com, MR.Rabbit, Ancap.su, zapper.lol, smies.me, baller.hodl",
state.urlSet.joinToString(", "), state.urlSet.joinToString(", "),
) )
@@ -4021,26 +4020,28 @@ class RichTextParserTest {
.map { it.words } .map { it.words }
.flatten() .flatten()
.forEachIndexed { index, seg -> .forEachIndexed { index, seg ->
Assert.assertEquals( org.junit.Assert.assertEquals(
expectedResult[index], expectedResult[index],
"${seg.javaClass.simpleName.replace("Segment", "")}(${seg.segmentText})", "${seg.javaClass.simpleName.replace("Segment", "")}(${seg.segmentText})",
) )
} }
Assert.assertTrue(state.imagesForPager.isEmpty()) org.junit.Assert.assertTrue(state.imagesForPager.isEmpty())
Assert.assertTrue(state.imageList.isEmpty()) org.junit.Assert.assertTrue(state.imageList.isEmpty())
Assert.assertTrue(state.customEmoji.isEmpty()) org.junit.Assert.assertTrue(state.customEmoji.isEmpty())
Assert.assertEquals(651, state.paragraphs.size) org.junit.Assert.assertEquals(651, state.paragraphs.size)
} }
@Test @Test
fun testShortTextToParse() { fun testShortTextToParse() {
val state = RichTextParser().parseText("Hi, how are you doing? ", EmptyTagList) val state =
Assert.assertTrue(state.urlSet.isEmpty()) com.vitorpamplona.amethyst.commons.RichTextParser()
Assert.assertTrue(state.imagesForPager.isEmpty()) .parseText("Hi, how are you doing? ", EmptyTagList)
Assert.assertTrue(state.imageList.isEmpty()) org.junit.Assert.assertTrue(state.urlSet.isEmpty())
Assert.assertTrue(state.customEmoji.isEmpty()) org.junit.Assert.assertTrue(state.imagesForPager.isEmpty())
Assert.assertEquals( org.junit.Assert.assertTrue(state.imageList.isEmpty())
org.junit.Assert.assertTrue(state.customEmoji.isEmpty())
org.junit.Assert.assertEquals(
"Hi, how are you doing? ", "Hi, how are you doing? ",
state.paragraphs.firstOrNull()?.words?.firstOrNull()?.segmentText, state.paragraphs.firstOrNull()?.words?.firstOrNull()?.segmentText,
) )
@@ -4048,12 +4049,14 @@ class RichTextParserTest {
@Test @Test
fun testShortNewLinesTextToParse() { fun testShortNewLinesTextToParse() {
val state = RichTextParser().parseText("\nHi, \nhow\n\n\n are you doing? \n", EmptyTagList) val state =
Assert.assertTrue(state.urlSet.isEmpty()) com.vitorpamplona.amethyst.commons.RichTextParser()
Assert.assertTrue(state.imagesForPager.isEmpty()) .parseText("\nHi, \nhow\n\n\n are you doing? \n", EmptyTagList)
Assert.assertTrue(state.imageList.isEmpty()) org.junit.Assert.assertTrue(state.urlSet.isEmpty())
Assert.assertTrue(state.customEmoji.isEmpty()) org.junit.Assert.assertTrue(state.imagesForPager.isEmpty())
Assert.assertEquals( org.junit.Assert.assertTrue(state.imageList.isEmpty())
org.junit.Assert.assertTrue(state.customEmoji.isEmpty())
org.junit.Assert.assertEquals(
"\nHi, \nhow\n\n\n are you doing? \n", "\nHi, \nhow\n\n\n are you doing? \n",
state.paragraphs.joinToString("\n") { it.words.joinToString(" ") { it.segmentText } }, state.paragraphs.joinToString("\n") { it.words.joinToString(" ") { it.segmentText } },
) )
@@ -4071,17 +4074,22 @@ class RichTextParserTest {
""" """
.trimIndent() .trimIndent()
val state = RichTextParser().parseText(text, EmptyTagList) val state =
Assert.assertEquals("https://lnshort.it/live-stream-embeds/", state.urlSet.firstOrNull()) com.vitorpamplona.amethyst.commons.RichTextParser()
Assert.assertEquals( .parseText(text, EmptyTagList)
org.junit.Assert.assertEquals(
"https://lnshort.it/live-stream-embeds/",
state.urlSet.firstOrNull(),
)
org.junit.Assert.assertEquals(
"https://nostr.build/i/fd53fcf5ad950fbe45127e4bcee1b59e8301d41de6beee211f45e344db214e8a.jpg", "https://nostr.build/i/fd53fcf5ad950fbe45127e4bcee1b59e8301d41de6beee211f45e344db214e8a.jpg",
state.imagesForPager.keys.firstOrNull(), state.imagesForPager.keys.firstOrNull(),
) )
Assert.assertEquals( org.junit.Assert.assertEquals(
"https://nostr.build/i/fd53fcf5ad950fbe45127e4bcee1b59e8301d41de6beee211f45e344db214e8a.jpg", "https://nostr.build/i/fd53fcf5ad950fbe45127e4bcee1b59e8301d41de6beee211f45e344db214e8a.jpg",
state.imageList.firstOrNull()?.url, state.imageList.firstOrNull()?.url,
) )
Assert.assertTrue(state.customEmoji.isEmpty()) org.junit.Assert.assertTrue(state.customEmoji.isEmpty())
printStateForDebug(state) printStateForDebug(state)
@@ -4131,7 +4139,7 @@ class RichTextParserTest {
.map { it.words } .map { it.words }
.flatten() .flatten()
.forEachIndexed { index, seg -> .forEachIndexed { index, seg ->
Assert.assertEquals( org.junit.Assert.assertEquals(
expectedResult[index], expectedResult[index],
"${seg.javaClass.simpleName.replace("Segment", "")}(${seg.segmentText})", "${seg.javaClass.simpleName.replace("Segment", "")}(${seg.segmentText})",
) )
@@ -4143,7 +4151,9 @@ class RichTextParserTest {
val text = val text =
"Thats it ! Thats the #note https://cdn.nostr.build/i/1dc0726b6cb0f94a92bd66765ffb90f6c67e90c17bb957fc3d5d4782cbd73de7.jpg " "Thats it ! Thats the #note https://cdn.nostr.build/i/1dc0726b6cb0f94a92bd66765ffb90f6c67e90c17bb957fc3d5d4782cbd73de7.jpg "
val state = RichTextParser().parseText(text, EmptyTagList) val state =
com.vitorpamplona.amethyst.commons.RichTextParser()
.parseText(text, EmptyTagList)
printStateForDebug(state) printStateForDebug(state)
@@ -4162,7 +4172,7 @@ class RichTextParserTest {
.map { it.words } .map { it.words }
.flatten() .flatten()
.forEachIndexed { index, seg -> .forEachIndexed { index, seg ->
Assert.assertEquals( org.junit.Assert.assertEquals(
expectedResult[index], expectedResult[index],
"${seg.javaClass.simpleName.replace("Segment", "")}(${seg.segmentText})", "${seg.javaClass.simpleName.replace("Segment", "")}(${seg.segmentText})",
) )
@@ -4174,7 +4184,9 @@ class RichTextParserTest {
val text = val text =
"Thats it! https://cdn.nostr.build/i/1dc0726b6cb0f94a92bd66765ffb90f6c67e90c17bb957fc3d5d4782cbd73de7.jpg Thats the #note" "Thats it! https://cdn.nostr.build/i/1dc0726b6cb0f94a92bd66765ffb90f6c67e90c17bb957fc3d5d4782cbd73de7.jpg Thats the #note"
val state = RichTextParser().parseText(text, EmptyTagList) val state =
com.vitorpamplona.amethyst.commons.RichTextParser()
.parseText(text, EmptyTagList)
printStateForDebug(state) printStateForDebug(state)
@@ -4192,7 +4204,7 @@ class RichTextParserTest {
.map { it.words } .map { it.words }
.flatten() .flatten()
.forEachIndexed { index, seg -> .forEachIndexed { index, seg ->
Assert.assertEquals( org.junit.Assert.assertEquals(
expectedResult[index], expectedResult[index],
"${seg.javaClass.simpleName.replace("Segment", "")}(${seg.segmentText})", "${seg.javaClass.simpleName.replace("Segment", "")}(${seg.segmentText})",
) )
@@ -4203,7 +4215,9 @@ class RichTextParserTest {
fun testUrlsEndingInPeriod() { fun testUrlsEndingInPeriod() {
val text = "Thats it! http://vitorpamplona.com/. Thats the note" val text = "Thats it! http://vitorpamplona.com/. Thats the note"
val state = RichTextParser().parseText(text, EmptyTagList) val state =
com.vitorpamplona.amethyst.commons.RichTextParser()
.parseText(text, EmptyTagList)
printStateForDebug(state) printStateForDebug(state)
@@ -4221,14 +4235,14 @@ class RichTextParserTest {
.map { it.words } .map { it.words }
.flatten() .flatten()
.forEachIndexed { index, seg -> .forEachIndexed { index, seg ->
Assert.assertEquals( org.junit.Assert.assertEquals(
expectedResult[index], expectedResult[index],
"${seg.javaClass.simpleName.replace("Segment", "")}(${seg.segmentText})", "${seg.javaClass.simpleName.replace("Segment", "")}(${seg.segmentText})",
) )
} }
} }
private fun printStateForDebug(state: RichTextViewerState) { private fun printStateForDebug(state: com.vitorpamplona.amethyst.commons.RichTextViewerState) {
state.paragraphs.forEach { paragraph -> state.paragraphs.forEach { paragraph ->
paragraph.words.forEach { seg -> paragraph.words.forEach { seg ->
println( println(

View File

@@ -20,12 +20,12 @@
*/ */
package com.vitorpamplona.amethyst.commons package com.vitorpamplona.amethyst.commons
class ExpandableTextParser { class ExpandableTextCutOffCalculator {
companion object { companion object {
private const val SHORT_TEXT_LENGTH = 350 private const val SHORT_TEXT_LENGTH = 350
private const val SHORTEN_AFTER_LINES = 10 private const val SHORTEN_AFTER_LINES = 10
fun computeWhereToCutIfPostIsTooLong(content: String): Int { fun indexToCutOff(content: String): Int {
// Cuts the text in the first space or new line after SHORT_TEXT_LENGTH characters // Cuts the text in the first space or new line after SHORT_TEXT_LENGTH characters
val firstSpaceAfterCut = val firstSpaceAfterCut =
content.indexOf(' ', SHORT_TEXT_LENGTH).let { if (it < 0) content.length else it } content.indexOf(' ', SHORT_TEXT_LENGTH).let { if (it < 0) content.length else it }

View File

@@ -24,33 +24,35 @@ import androidx.compose.runtime.Immutable
import java.io.File import java.io.File
@Immutable @Immutable
abstract class ZoomableContent( abstract class BaseMediaContent(
val description: String? = null, val description: String? = null,
val dim: String? = null, val dim: String? = null,
val blurhash: String? = null,
) )
@Immutable @Immutable
abstract class ZoomableUrlContent( abstract class MediaUrlContent(
val url: String, val url: String,
description: String? = null, description: String? = null,
val hash: String? = null, val hash: String? = null,
dim: String? = null, dim: String? = null,
blurhash: String? = null,
val uri: String? = null, val uri: String? = null,
) : ZoomableContent(description, dim) ) : BaseMediaContent(description, dim, blurhash)
@Immutable @Immutable
class ZoomableUrlImage( class MediaUrlImage(
url: String, url: String,
description: String? = null, description: String? = null,
hash: String? = null, hash: String? = null,
val blurhash: String? = null, blurhash: String? = null,
dim: String? = null, dim: String? = null,
uri: String? = null, uri: String? = null,
val contentWarning: String? = null, val contentWarning: String? = null,
) : ZoomableUrlContent(url, description, hash, dim, uri) ) : MediaUrlContent(url, description, hash, dim, blurhash, uri)
@Immutable @Immutable
class ZoomableUrlVideo( class MediaUrlVideo(
url: String, url: String,
description: String? = null, description: String? = null,
hash: String? = null, hash: String? = null,
@@ -58,41 +60,43 @@ class ZoomableUrlVideo(
uri: String? = null, uri: String? = null,
val artworkUri: String? = null, val artworkUri: String? = null,
val authorName: String? = null, val authorName: String? = null,
val blurhash: String? = null, blurhash: String? = null,
val contentWarning: String? = null, val contentWarning: String? = null,
) : ZoomableUrlContent(url, description, hash, dim, uri) ) : MediaUrlContent(url, description, hash, dim, blurhash, uri)
@Immutable @Immutable
abstract class ZoomablePreloadedContent( abstract class MediaPreloadedContent(
val localFile: File?, val localFile: File?,
description: String? = null, description: String? = null,
val mimeType: String? = null, val mimeType: String? = null,
val isVerified: Boolean? = null, val isVerified: Boolean? = null,
dim: String? = null, dim: String? = null,
blurhash: String? = null,
val uri: String, val uri: String,
) : ZoomableContent(description, dim) { ) : BaseMediaContent(description, dim, blurhash) {
fun localFileExists() = localFile != null && localFile.exists() fun localFileExists() = localFile != null && localFile.exists()
} }
@Immutable @Immutable
class ZoomableLocalImage( class MediaLocalImage(
localFile: File?, localFile: File?,
mimeType: String? = null, mimeType: String? = null,
description: String? = null, description: String? = null,
val blurhash: String? = null,
dim: String? = null, dim: String? = null,
blurhash: String? = null,
isVerified: Boolean? = null, isVerified: Boolean? = null,
uri: String, uri: String,
) : ZoomablePreloadedContent(localFile, description, mimeType, isVerified, dim, uri) ) : MediaPreloadedContent(localFile, description, mimeType, isVerified, dim, blurhash, uri)
@Immutable @Immutable
class ZoomableLocalVideo( class MediaLocalVideo(
localFile: File?, localFile: File?,
mimeType: String? = null, mimeType: String? = null,
description: String? = null, description: String? = null,
dim: String? = null, dim: String? = null,
blurhash: String? = null,
isVerified: Boolean? = null, isVerified: Boolean? = null,
uri: String, uri: String,
val artworkUri: String? = null, val artworkUri: String? = null,
val authorName: String? = null, val authorName: String? = null,
) : ZoomablePreloadedContent(localFile, description, mimeType, isVerified, dim, uri) ) : MediaPreloadedContent(localFile, description, mimeType, isVerified, dim, blurhash, uri)

View File

@@ -44,13 +44,13 @@ class RichTextParser() {
fun parseMediaUrl( fun parseMediaUrl(
fullUrl: String, fullUrl: String,
eventTags: ImmutableListOfLists<String>, eventTags: ImmutableListOfLists<String>,
): ZoomableUrlContent? { ): MediaUrlContent? {
val removedParamsFromUrl = removeQueryParamsForExtensionComparison(fullUrl) val removedParamsFromUrl = removeQueryParamsForExtensionComparison(fullUrl)
return if (imageExtensions.any { removedParamsFromUrl.endsWith(it) }) { return if (imageExtensions.any { removedParamsFromUrl.endsWith(it) }) {
val frags = Nip54().parse(fullUrl) val frags = Nip54().parse(fullUrl)
val tags = Nip92().parse(fullUrl, eventTags.lists) val tags = Nip92().parse(fullUrl, eventTags.lists)
ZoomableUrlImage( MediaUrlImage(
url = fullUrl, url = fullUrl,
description = frags[FileHeaderEvent.ALT] ?: tags[FileHeaderEvent.ALT], description = frags[FileHeaderEvent.ALT] ?: tags[FileHeaderEvent.ALT],
hash = frags[FileHeaderEvent.HASH] ?: tags[FileHeaderEvent.HASH], hash = frags[FileHeaderEvent.HASH] ?: tags[FileHeaderEvent.HASH],
@@ -61,7 +61,7 @@ class RichTextParser() {
} else if (videoExtensions.any { removedParamsFromUrl.endsWith(it) }) { } else if (videoExtensions.any { removedParamsFromUrl.endsWith(it) }) {
val frags = Nip54().parse(fullUrl) val frags = Nip54().parse(fullUrl)
val tags = Nip92().parse(fullUrl, eventTags.lists) val tags = Nip92().parse(fullUrl, eventTags.lists)
ZoomableUrlVideo( MediaUrlVideo(
url = fullUrl, url = fullUrl,
description = frags[FileHeaderEvent.ALT] ?: tags[FileHeaderEvent.ALT], description = frags[FileHeaderEvent.ALT] ?: tags[FileHeaderEvent.ALT],
hash = frags[FileHeaderEvent.HASH] ?: tags[FileHeaderEvent.HASH], hash = frags[FileHeaderEvent.HASH] ?: tags[FileHeaderEvent.HASH],
@@ -289,7 +289,9 @@ class RichTextParser() {
val hashTagsPattern: Pattern = val hashTagsPattern: Pattern =
Pattern.compile("#([^\\s!@#\$%^&*()=+./,\\[{\\]};:'\"?><]+)(.*)", Pattern.CASE_INSENSITIVE) Pattern.compile("#([^\\s!@#\$%^&*()=+./,\\[{\\]};:'\"?><]+)(.*)", Pattern.CASE_INSENSITIVE)
fun removeQueryParamsForExtensionComparison(fullUrl: String): String { val acceptedNIP19schemes = listOf("npub1", "naddr1", "note1", "nprofile1", "nevent1")
private fun removeQueryParamsForExtensionComparison(fullUrl: String): String {
return if (fullUrl.contains("?")) { return if (fullUrl.contains("?")) {
fullUrl.split("?")[0].lowercase() fullUrl.split("?")[0].lowercase()
} else if (fullUrl.contains("#")) { } else if (fullUrl.contains("#")) {
@@ -327,24 +329,24 @@ class RichTextParser() {
} }
} }
fun parseImageOrVideo(fullUrl: String): ZoomableContent { fun parseImageOrVideo(fullUrl: String): BaseMediaContent {
val removedParamsFromUrl = removeQueryParamsForExtensionComparison(fullUrl) val removedParamsFromUrl = removeQueryParamsForExtensionComparison(fullUrl)
val isImage = imageExtensions.any { removedParamsFromUrl.endsWith(it) } val isImage = imageExtensions.any { removedParamsFromUrl.endsWith(it) }
val isVideo = videoExtensions.any { removedParamsFromUrl.endsWith(it) } val isVideo = videoExtensions.any { removedParamsFromUrl.endsWith(it) }
return if (isImage) { return if (isImage) {
ZoomableUrlImage(fullUrl) MediaUrlImage(fullUrl)
} else if (isVideo) { } else if (isVideo) {
ZoomableUrlVideo(fullUrl) MediaUrlVideo(fullUrl)
} else { } else {
ZoomableUrlImage(fullUrl) MediaUrlImage(fullUrl)
} }
} }
fun startsWithNIP19Scheme(word: String): Boolean { fun startsWithNIP19Scheme(word: String): Boolean {
val cleaned = word.lowercase().removePrefix("@").removePrefix("nostr:").removePrefix("@") val cleaned = word.lowercase().removePrefix("@").removePrefix("nostr:").removePrefix("@")
return listOf("npub1", "naddr1", "note1", "nprofile1", "nevent1").any { cleaned.startsWith(it) } return acceptedNIP19schemes.any { cleaned.startsWith(it) }
} }
fun isUrlWithoutScheme(url: String) = noProtocolUrlValidator.matcher(url).matches() fun isUrlWithoutScheme(url: String) = noProtocolUrlValidator.matcher(url).matches()

View File

@@ -28,8 +28,8 @@ import kotlinx.collections.immutable.ImmutableSet
@Immutable @Immutable
data class RichTextViewerState( data class RichTextViewerState(
val urlSet: ImmutableSet<String>, val urlSet: ImmutableSet<String>,
val imagesForPager: ImmutableMap<String, ZoomableUrlContent>, val imagesForPager: ImmutableMap<String, MediaUrlContent>,
val imageList: ImmutableList<ZoomableUrlContent>, val imageList: ImmutableList<MediaUrlContent>,
val customEmoji: ImmutableMap<String, String>, val customEmoji: ImmutableMap<String, String>,
val paragraphs: ImmutableList<ParagraphState>, val paragraphs: ImmutableList<ParagraphState>,
) )