Introduce ClickableLinkText

This commit is contained in:
ganfra 2022-12-01 21:39:42 +01:00
parent 6636c653d7
commit aa77e7fe5a
3 changed files with 135 additions and 43 deletions

View file

@ -1,10 +1,22 @@
package io.element.android.x.features.messages.components
import android.text.SpannableString
import android.text.style.URLSpan
import android.text.util.Linkify
import android.text.util.Linkify.*
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.platform.UriHandler
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.core.text.util.LinkifyCompat
import io.element.android.x.designsystem.components.ClickableLinkText
import io.element.android.x.features.messages.components.html.HtmlDocument
import io.element.android.x.features.messages.model.content.MessagesTimelineItemTextBasedContent
@ -26,8 +38,44 @@ fun MessagesTimelineItemTextView(
interactionSource = interactionSource
)
} else {
val uriHandler = LocalUriHandler.current
Box(modifier) {
Text(text = content.body)
val linkStyle = SpanStyle(
color = Color.Blue,
)
val styledText = remember(content.body) { content.body.linkify(linkStyle) }
ClickableLinkText(
text = styledText,
linkAnnotationTag = "URL",
onClick = onTextClicked,
onLongClick = onTextLongClicked,
interactionSource = interactionSource
)
}
}
}
private fun String.linkify(
linkStyle: SpanStyle,
) = buildAnnotatedString {
append(this@linkify)
val spannable = SpannableString(this@linkify)
LinkifyCompat.addLinks(spannable, WEB_URLS or PHONE_NUMBERS)
val spans = spannable.getSpans(0, spannable.length, URLSpan::class.java)
for (span in spans) {
val start = spannable.getSpanStart(span)
val end = spannable.getSpanEnd(span)
addStyle(
start = start,
end = end,
style = linkStyle,
)
addStringAnnotation(
tag = "URL",
annotation = span.url,
start = start,
end = end
)
}
}

View file

@ -1,24 +1,20 @@
package io.element.android.x.features.messages.components.html
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.PressInteraction
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.InlineTextContent
import androidx.compose.foundation.text.appendInlineContent
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
@ -27,6 +23,7 @@ import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.google.accompanist.flowlayout.FlowRow
import io.element.android.x.designsystem.components.ClickableLinkText
import io.element.android.x.matrix.permalink.PermalinkData
import io.element.android.x.matrix.permalink.PermalinkParser
import org.jsoup.nodes.Document
@ -500,7 +497,7 @@ private fun AnnotatedString.Builder.appendLink(link: Element) {
val permalinkData = PermalinkParser.parse(uriString)
when (permalinkData) {
is PermalinkData.FallbackLink -> {
pushStringAnnotation(tag = "link", annotation = link.ownText())
pushStringAnnotation(tag = "URL", annotation = link.ownText())
withStyle(
style = SpanStyle(color = Color.Blue)
) {
@ -529,41 +526,16 @@ private fun HtmlText(
onLongClick: () -> Unit,
interactionSource: MutableInteractionSource,
) {
val uriHandler = LocalUriHandler.current
val layoutResult = remember { mutableStateOf<TextLayoutResult?>(null) }
val pressIndicator = Modifier.pointerInput(onClick) {
detectTapGestures(
onPress = { offset: Offset ->
val pressInteraction = PressInteraction.Press(offset)
interactionSource.emit(pressInteraction)
awaitRelease()
interactionSource.emit(PressInteraction.Release(pressInteraction))
},
onLongPress = { _ ->
onLongClick()
}
) { offset ->
layoutResult.value?.let { layoutResult ->
val position = layoutResult.getOffsetForPosition(offset)
val linkAnnotations = text.getStringAnnotations("link", position, position)
if (linkAnnotations.isEmpty()) {
onClick()
} else {
uriHandler.openUri(linkAnnotations.first().item)
}
}
}
}
val inlineContentMap = emptyMap<String, InlineTextContent>()
Text(
ClickableLinkText(
text = text,
modifier = modifier.then(pressIndicator),
linkAnnotationTag = "URL",
style = style,
onTextLayout = {
layoutResult.value = it
},
inlineContent = inlineContentMap
modifier = modifier,
inlineContent = inlineContentMap,
interactionSource = interactionSource,
onClick = onClick,
onLongClick = onLongClick
)
}

View file

@ -0,0 +1,72 @@
package io.element.android.x.designsystem.components
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.PressInteraction
import androidx.compose.foundation.text.InlineTextContent
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.TextStyle
@Composable
fun ClickableLinkText(
text: AnnotatedString,
linkAnnotationTag: String,
onClick: () -> Unit,
onLongClick: () -> Unit,
interactionSource: MutableInteractionSource,
modifier: Modifier = Modifier,
style: TextStyle = LocalTextStyle.current,
inlineContent: Map<String, InlineTextContent> = mapOf(),
) {
val uriHandler = LocalUriHandler.current
val layoutResult = remember { mutableStateOf<TextLayoutResult?>(null) }
val pressIndicator = Modifier.pointerInput(onClick) {
detectTapGestures(
onPress = { offset: Offset ->
val pressInteraction = PressInteraction.Press(offset)
interactionSource.emit(pressInteraction)
val isReleased = tryAwaitRelease()
if (isReleased) {
interactionSource.emit(PressInteraction.Release(pressInteraction))
} else {
interactionSource.emit(PressInteraction.Cancel(pressInteraction))
}
},
onLongPress = {
onLongClick()
}
) { offset ->
layoutResult.value?.let { layoutResult ->
val position = layoutResult.getOffsetForPosition(offset)
val linkAnnotations =
text.getStringAnnotations(linkAnnotationTag, position, position)
if (linkAnnotations.isEmpty()) {
onClick()
} else {
uriHandler.openUri(linkAnnotations.first().item)
}
}
}
}
Text(
text = text,
modifier = modifier.then(pressIndicator),
style = style,
onTextLayout = {
layoutResult.value = it
},
inlineContent = inlineContent
)
}