mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-11-11 13:26:39 +01:00
Refactoring some names for the parsers.
This commit is contained in:
@@ -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) {
|
||||||
|
|||||||
@@ -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,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 =
|
||||||
"That’s it ! That’s the #note https://cdn.nostr.build/i/1dc0726b6cb0f94a92bd66765ffb90f6c67e90c17bb957fc3d5d4782cbd73de7.jpg "
|
"That’s it ! That’s 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 =
|
||||||
"That’s it! https://cdn.nostr.build/i/1dc0726b6cb0f94a92bd66765ffb90f6c67e90c17bb957fc3d5d4782cbd73de7.jpg That’s the #note"
|
"That’s it! https://cdn.nostr.build/i/1dc0726b6cb0f94a92bd66765ffb90f6c67e90c17bb957fc3d5d4782cbd73de7.jpg That’s 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 = "That’s it! http://vitorpamplona.com/. That’s the note"
|
val text = "That’s it! http://vitorpamplona.com/. That’s 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(
|
||||||
@@ -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 }
|
||||||
@@ -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)
|
||||||
@@ -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()
|
||||||
|
|||||||
@@ -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>,
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user