Calculate the text width of a space outside a composable.

This commit is contained in:
Vitor Pamplona
2023-12-12 20:31:03 -05:00
parent c2532322fd
commit 0e5b5d657f
5 changed files with 79 additions and 80 deletions
@@ -11,7 +11,7 @@ import com.vitorpamplona.amethyst.ui.components.ZoomableUrlImage
import com.vitorpamplona.amethyst.ui.components.ZoomableUrlVideo import com.vitorpamplona.amethyst.ui.components.ZoomableUrlVideo
import com.vitorpamplona.amethyst.ui.components.hashTagsPattern import com.vitorpamplona.amethyst.ui.components.hashTagsPattern
import com.vitorpamplona.amethyst.ui.components.imageExtensions 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.tagIndex
import com.vitorpamplona.amethyst.ui.components.videoExtensions import com.vitorpamplona.amethyst.ui.components.videoExtensions
import com.vitorpamplona.quartz.events.ImmutableListOfLists import com.vitorpamplona.quartz.events.ImmutableListOfLists
@@ -88,7 +88,7 @@ class RichTextParser() {
} }
val imagesForPager = urlSet.mapNotNull { fullUrl -> val imagesForPager = urlSet.mapNotNull { fullUrl ->
val removedParamsFromUrl = removeQueryParams(fullUrl) val removedParamsFromUrl = removeQueryParamsForExtensionComparison(fullUrl)
if (imageExtensions.any { removedParamsFromUrl.endsWith(it) }) { if (imageExtensions.any { removedParamsFromUrl.endsWith(it) }) {
val frags = URI(fullUrl).fragments() val frags = URI(fullUrl).fragments()
ZoomableUrlImage( ZoomableUrlImage(
@@ -93,7 +93,7 @@ class MarkdownParser {
content.split('\n').forEach { paragraph -> content.split('\n').forEach { paragraph ->
paragraph.split(' ').forEach { word: String -> paragraph.split(' ').forEach { word: String ->
if (isValidURL(word)) { if (isValidURL(word)) {
val removedParamsFromUrl = removeQueryParams(word) val removedParamsFromUrl = removeQueryParamsForExtensionComparison(word)
if (imageExtensions.any { removedParamsFromUrl.endsWith(it) }) { if (imageExtensions.any { removedParamsFromUrl.endsWith(it) }) {
returnContent += "![]($word) " returnContent += "![]($word) "
} else { } else {
@@ -30,17 +30,18 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.takeOrElse 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.LocalLayoutDirection
import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.Placeholder import androidx.compose.ui.text.Placeholder
import androidx.compose.ui.text.PlaceholderVerticalAlign import androidx.compose.ui.text.PlaceholderVerticalAlign
import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextMeasurer
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.withStyle import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.em 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 tagIndex = Pattern.compile("\\#\\[([0-9]+)\\](.*)")
val hashTagsPattern: Pattern = Pattern.compile("#([^\\s!@#\$%^&*()=+./,\\[{\\]};:'\"?><]+)(.*)", Pattern.CASE_INSENSITIVE) val hashTagsPattern: Pattern = Pattern.compile("#([^\\s!@#\$%^&*()=+./,\\[{\\]};:'\"?><]+)(.*)", Pattern.CASE_INSENSITIVE)
fun removeQueryParams(fullUrl: String): String { 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("#")) {
@@ -170,59 +171,59 @@ private fun RenderRegular(
) )
} }
MeasureSpaceWidth() { spaceWidth -> val spaceWidth = measureSpaceWidth(textStyle)
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) { Column() {
FlowRow( if (canPreview) {
modifier = Modifier.align(if (paragraph.isRTL) Alignment.End else Alignment.Start), // FlowRow doesn't work well with paragraphs. So we need to split them
horizontalArrangement = Arrangement.spacedBy(spaceWidth) state.paragraphs.forEach { paragraph ->
) { val direction = if (paragraph.isRTL) {
paragraph.words.forEach { word -> LayoutDirection.Rtl
RenderWordWithPreview( } else {
word, LayoutDirection.Ltr
state, }
backgroundColor,
textStyle, CompositionLocalProvider(LocalLayoutDirection provides direction) {
accountViewModel, FlowRow(
nav 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 } else {
state.paragraphs.forEach { paragraph -> // FlowRow doesn't work well with paragraphs. So we need to split them
val direction = if (paragraph.isRTL) { state.paragraphs.forEach { paragraph ->
LayoutDirection.Rtl val direction = if (paragraph.isRTL) {
} else { LayoutDirection.Rtl
LayoutDirection.Ltr } else {
} LayoutDirection.Ltr
}
CompositionLocalProvider(LocalLayoutDirection provides direction) { CompositionLocalProvider(LocalLayoutDirection provides direction) {
FlowRow( FlowRow(
horizontalArrangement = Arrangement.spacedBy(spaceWidth), horizontalArrangement = Arrangement.spacedBy(spaceWidth),
modifier = Modifier.align(if (paragraph.isRTL) Alignment.End else Alignment.Start) modifier = Modifier.align(if (paragraph.isRTL) Alignment.End else Alignment.Start)
) { ) {
paragraph.words.forEach { word -> paragraph.words.forEach { word ->
RenderWordWithoutPreview( RenderWordWithoutPreview(
word, word,
state, state,
backgroundColor, backgroundColor,
textStyle, textStyle,
accountViewModel, accountViewModel,
nav nav
) )
}
} }
} }
} }
@@ -232,17 +233,15 @@ private fun RenderRegular(
} }
@Composable @Composable
fun MeasureSpaceWidth( fun measureSpaceWidth(textStyle: TextStyle): Dp {
content: @Composable (measuredWidth: Dp) -> Unit val fontFamilyResolver = LocalFontFamilyResolver.current
) { val density = LocalDensity.current
SubcomposeLayout { constraints -> val layoutDirection = LocalLayoutDirection.current
val measuredWidth = subcompose("viewToMeasure", { Text(" ") })[0].measure(Constraints()).width.toDp()
val contentPlaceable = subcompose("content") { return remember(fontFamilyResolver, density, layoutDirection, textStyle) {
content(measuredWidth) val widthPx = TextMeasurer(fontFamilyResolver, density, layoutDirection, 1).measure(" ", textStyle).size.width
}[0].measure(constraints) with(density) {
layout(contentPlaceable.width, contentPlaceable.height) { widthPx.toDp()
contentPlaceable.place(0, 0)
} }
} }
} }
@@ -567,7 +566,7 @@ private fun RenderHashtag(
) { ) {
val primary = MaterialTheme.colorScheme.primary val primary = MaterialTheme.colorScheme.primary
val background = MaterialTheme.colorScheme.onBackground val background = MaterialTheme.colorScheme.onBackground
val hashtagIcon = remember(segment.hashtag) { val hashtagIcon: HashtagIcon? = remember(segment.hashtag) {
checkForHashtagWithIcon(segment.hashtag, primary) checkForHashtagWithIcon(segment.hashtag, primary)
} }
@@ -189,7 +189,7 @@ class ZoomableLocalVideo(
) : ZoomablePreloadedContent(localFile, description, mimeType, isVerified, dim, uri) ) : ZoomablePreloadedContent(localFile, description, mimeType, isVerified, dim, uri)
fun figureOutMimeType(fullUrl: String): ZoomableContent { fun figureOutMimeType(fullUrl: String): ZoomableContent {
val removedParamsFromUrl = removeQueryParams(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) }
@@ -97,7 +97,6 @@ import com.vitorpamplona.amethyst.ui.components.CreateClickableTextWithEmoji
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
import com.vitorpamplona.amethyst.ui.components.LoadNote import com.vitorpamplona.amethyst.ui.components.LoadNote
import com.vitorpamplona.amethyst.ui.components.LoadThumbAndThenVideoView 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.ObserveDisplayNip05Status
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning 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.ZoomableUrlVideo
import com.vitorpamplona.amethyst.ui.components.figureOutMimeType import com.vitorpamplona.amethyst.ui.components.figureOutMimeType
import com.vitorpamplona.amethyst.ui.components.imageExtensions 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.equalImmutableLists
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChannelHeader import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChannelHeader
@@ -3024,19 +3024,19 @@ private fun DisplayQuoteAuthor(
} }
} }
MeasureSpaceWidth { val spaceWidth = measureSpaceWidth(textStyle = LocalTextStyle.current)
FlowRow(horizontalArrangement = Arrangement.spacedBy(it), verticalArrangement = Arrangement.Center) {
userBase?.let { userBase ->
LoadAndDisplayUser(userBase, nav)
}
url?.let { url -> FlowRow(horizontalArrangement = Arrangement.spacedBy(spaceWidth), verticalArrangement = Arrangement.Center) {
LoadAndDisplayUrl(url) userBase?.let { userBase ->
} LoadAndDisplayUser(userBase, nav)
}
postAddress?.let { address -> url?.let { url ->
LoadAndDisplayPost(address, accountViewModel, nav) 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 hash = event.hash()
val dimensions = event.dimensions() val dimensions = event.dimensions()
val description = event.alt() ?: event.content 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() val uri = note.toNostrUri()
mutableStateOf<ZoomableContent>( mutableStateOf<ZoomableContent>(