diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/CachedRichTextParser.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/CachedRichTextParser.kt index 336a3a226..5493ff66f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/CachedRichTextParser.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/CachedRichTextParser.kt @@ -11,7 +11,7 @@ import com.vitorpamplona.amethyst.ui.components.ZoomableUrlImage import com.vitorpamplona.amethyst.ui.components.ZoomableUrlVideo import com.vitorpamplona.amethyst.ui.components.hashTagsPattern import com.vitorpamplona.amethyst.ui.components.imageExtensions -import com.vitorpamplona.amethyst.ui.components.removeQueryParams +import com.vitorpamplona.amethyst.ui.components.removeQueryParamsForExtensionComparison import com.vitorpamplona.amethyst.ui.components.tagIndex import com.vitorpamplona.amethyst.ui.components.videoExtensions import com.vitorpamplona.quartz.events.ImmutableListOfLists @@ -88,7 +88,7 @@ class RichTextParser() { } val imagesForPager = urlSet.mapNotNull { fullUrl -> - val removedParamsFromUrl = removeQueryParams(fullUrl) + val removedParamsFromUrl = removeQueryParamsForExtensionComparison(fullUrl) if (imageExtensions.any { removedParamsFromUrl.endsWith(it) }) { val frags = URI(fullUrl).fragments() ZoomableUrlImage( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/MarkdownParser.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/MarkdownParser.kt index bdc25ab82..3877cb7a8 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/MarkdownParser.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/MarkdownParser.kt @@ -93,7 +93,7 @@ class MarkdownParser { content.split('\n').forEach { paragraph -> paragraph.split(' ').forEach { word: String -> if (isValidURL(word)) { - val removedParamsFromUrl = removeQueryParams(word) + val removedParamsFromUrl = removeQueryParamsForExtensionComparison(word) if (imageExtensions.any { removedParamsFromUrl.endsWith(it) }) { returnContent += "![]($word) " } else { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt index 80d11c27f..bb258d640 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt @@ -30,17 +30,18 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.takeOrElse -import androidx.compose.ui.layout.SubcomposeLayout +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalFontFamilyResolver import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.Placeholder import androidx.compose.ui.text.PlaceholderVerticalAlign import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.TextMeasurer import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.withStyle -import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.em @@ -95,7 +96,7 @@ val videoExtensions = listOf("mp4", "avi", "wmv", "mpg", "amv", "webm", "mov", " val tagIndex = Pattern.compile("\\#\\[([0-9]+)\\](.*)") val hashTagsPattern: Pattern = Pattern.compile("#([^\\s!@#\$%^&*()=+./,\\[{\\]};:'\"?><]+)(.*)", Pattern.CASE_INSENSITIVE) -fun removeQueryParams(fullUrl: String): String { +fun removeQueryParamsForExtensionComparison(fullUrl: String): String { return if (fullUrl.contains("?")) { fullUrl.split("?")[0].lowercase() } else if (fullUrl.contains("#")) { @@ -170,59 +171,59 @@ private fun RenderRegular( ) } - MeasureSpaceWidth() { spaceWidth -> - Column() { - if (canPreview) { - // FlowRow doesn't work well with paragraphs. So we need to split them - state.paragraphs.forEach { paragraph -> - val direction = if (paragraph.isRTL) { - LayoutDirection.Rtl - } else { - LayoutDirection.Ltr - } + val spaceWidth = measureSpaceWidth(textStyle) - CompositionLocalProvider(LocalLayoutDirection provides direction) { - FlowRow( - modifier = Modifier.align(if (paragraph.isRTL) Alignment.End else Alignment.Start), - horizontalArrangement = Arrangement.spacedBy(spaceWidth) - ) { - paragraph.words.forEach { word -> - RenderWordWithPreview( - word, - state, - backgroundColor, - textStyle, - accountViewModel, - nav - ) - } + Column() { + if (canPreview) { + // FlowRow doesn't work well with paragraphs. So we need to split them + state.paragraphs.forEach { paragraph -> + val direction = if (paragraph.isRTL) { + LayoutDirection.Rtl + } else { + LayoutDirection.Ltr + } + + CompositionLocalProvider(LocalLayoutDirection provides direction) { + FlowRow( + modifier = Modifier.align(if (paragraph.isRTL) Alignment.End else Alignment.Start), + horizontalArrangement = Arrangement.spacedBy(spaceWidth) + ) { + paragraph.words.forEach { word -> + RenderWordWithPreview( + word, + state, + backgroundColor, + textStyle, + accountViewModel, + nav + ) } } } - } else { - // FlowRow doesn't work well with paragraphs. So we need to split them - state.paragraphs.forEach { paragraph -> - val direction = if (paragraph.isRTL) { - LayoutDirection.Rtl - } else { - LayoutDirection.Ltr - } + } + } else { + // FlowRow doesn't work well with paragraphs. So we need to split them + state.paragraphs.forEach { paragraph -> + val direction = if (paragraph.isRTL) { + LayoutDirection.Rtl + } else { + LayoutDirection.Ltr + } - CompositionLocalProvider(LocalLayoutDirection provides direction) { - FlowRow( - horizontalArrangement = Arrangement.spacedBy(spaceWidth), - modifier = Modifier.align(if (paragraph.isRTL) Alignment.End else Alignment.Start) - ) { - paragraph.words.forEach { word -> - RenderWordWithoutPreview( - word, - state, - backgroundColor, - textStyle, - accountViewModel, - nav - ) - } + CompositionLocalProvider(LocalLayoutDirection provides direction) { + FlowRow( + horizontalArrangement = Arrangement.spacedBy(spaceWidth), + modifier = Modifier.align(if (paragraph.isRTL) Alignment.End else Alignment.Start) + ) { + paragraph.words.forEach { word -> + RenderWordWithoutPreview( + word, + state, + backgroundColor, + textStyle, + accountViewModel, + nav + ) } } } @@ -232,17 +233,15 @@ private fun RenderRegular( } @Composable -fun MeasureSpaceWidth( - content: @Composable (measuredWidth: Dp) -> Unit -) { - SubcomposeLayout { constraints -> - val measuredWidth = subcompose("viewToMeasure", { Text(" ") })[0].measure(Constraints()).width.toDp() +fun measureSpaceWidth(textStyle: TextStyle): Dp { + val fontFamilyResolver = LocalFontFamilyResolver.current + val density = LocalDensity.current + val layoutDirection = LocalLayoutDirection.current - val contentPlaceable = subcompose("content") { - content(measuredWidth) - }[0].measure(constraints) - layout(contentPlaceable.width, contentPlaceable.height) { - contentPlaceable.place(0, 0) + return remember(fontFamilyResolver, density, layoutDirection, textStyle) { + val widthPx = TextMeasurer(fontFamilyResolver, density, layoutDirection, 1).measure(" ", textStyle).size.width + with(density) { + widthPx.toDp() } } } @@ -567,7 +566,7 @@ private fun RenderHashtag( ) { val primary = MaterialTheme.colorScheme.primary val background = MaterialTheme.colorScheme.onBackground - val hashtagIcon = remember(segment.hashtag) { + val hashtagIcon: HashtagIcon? = remember(segment.hashtag) { checkForHashtagWithIcon(segment.hashtag, primary) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt index d08fb87a5..a1a57b9ec 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt @@ -189,7 +189,7 @@ class ZoomableLocalVideo( ) : ZoomablePreloadedContent(localFile, description, mimeType, isVerified, dim, uri) fun figureOutMimeType(fullUrl: String): ZoomableContent { - val removedParamsFromUrl = removeQueryParams(fullUrl) + val removedParamsFromUrl = removeQueryParamsForExtensionComparison(fullUrl) val isImage = imageExtensions.any { removedParamsFromUrl.endsWith(it) } val isVideo = videoExtensions.any { removedParamsFromUrl.endsWith(it) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt index 615c73028..f5e8efc77 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt @@ -97,7 +97,6 @@ import com.vitorpamplona.amethyst.ui.components.CreateClickableTextWithEmoji import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji import com.vitorpamplona.amethyst.ui.components.LoadNote import com.vitorpamplona.amethyst.ui.components.LoadThumbAndThenVideoView -import com.vitorpamplona.amethyst.ui.components.MeasureSpaceWidth import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage import com.vitorpamplona.amethyst.ui.components.SensitivityWarning @@ -113,7 +112,8 @@ import com.vitorpamplona.amethyst.ui.components.ZoomableUrlImage import com.vitorpamplona.amethyst.ui.components.ZoomableUrlVideo import com.vitorpamplona.amethyst.ui.components.figureOutMimeType import com.vitorpamplona.amethyst.ui.components.imageExtensions -import com.vitorpamplona.amethyst.ui.components.removeQueryParams +import com.vitorpamplona.amethyst.ui.components.measureSpaceWidth +import com.vitorpamplona.amethyst.ui.components.removeQueryParamsForExtensionComparison import com.vitorpamplona.amethyst.ui.screen.equalImmutableLists import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChannelHeader @@ -3024,19 +3024,19 @@ private fun DisplayQuoteAuthor( } } - MeasureSpaceWidth { - FlowRow(horizontalArrangement = Arrangement.spacedBy(it), verticalArrangement = Arrangement.Center) { - userBase?.let { userBase -> - LoadAndDisplayUser(userBase, nav) - } + val spaceWidth = measureSpaceWidth(textStyle = LocalTextStyle.current) - url?.let { url -> - LoadAndDisplayUrl(url) - } + FlowRow(horizontalArrangement = Arrangement.spacedBy(spaceWidth), verticalArrangement = Arrangement.Center) { + userBase?.let { userBase -> + LoadAndDisplayUser(userBase, nav) + } - postAddress?.let { address -> - LoadAndDisplayPost(address, accountViewModel, nav) - } + url?.let { url -> + LoadAndDisplayUrl(url) + } + + postAddress?.let { address -> + LoadAndDisplayPost(address, accountViewModel, nav) } } } @@ -3455,7 +3455,7 @@ fun FileHeaderDisplay(note: Note, roundedCorner: Boolean, accountViewModel: Acco val hash = event.hash() val dimensions = event.dimensions() val description = event.alt() ?: event.content - val isImage = imageExtensions.any { removeQueryParams(fullUrl).lowercase().endsWith(it) } + val isImage = imageExtensions.any { removeQueryParamsForExtensionComparison(fullUrl).lowercase().endsWith(it) } val uri = note.toNostrUri() mutableStateOf(