Linkify raw links in HTML message contents (#1102)

* Linkify links in HTML too:

- Creates a `ClickableLinkText` for `String`.
- Adds a `linkify` parameter to the original function, which is `true` by default.
- Does the linkify logic inside that component, if `linkify` is true.

* Add changelog

* Make sure we don't linkify user mentions or room aliases.

* Use remember to avoid re-processing the text for no reason.
This commit is contained in:
Jorge Martin Espinosa 2023-08-21 16:31:01 +02:00 committed by GitHub
parent af04cefb89
commit bfd938a970
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 83 additions and 50 deletions

View file

@ -16,10 +16,6 @@
package io.element.android.features.messages.impl.timeline.components.event
import android.text.SpannableString
import android.text.style.URLSpan
import android.text.util.Linkify.PHONE_NUMBERS
import android.text.util.Linkify.WEB_URLS
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@ -28,20 +24,16 @@ import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.text.util.LinkifyCompat
import io.element.android.features.messages.impl.timeline.components.html.HtmlDocument
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContentProvider
import io.element.android.libraries.designsystem.components.ClickableLinkText
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.theme.LinkColor
import io.element.android.libraries.designsystem.text.toAnnotatedString
@Composable
@ -69,14 +61,11 @@ fun TimelineItemTextView(
}
} else {
Box(modifier) {
val linkStyle = SpanStyle(
color = LinkColor,
)
val styledText = remember(content.body) {
content.body.linkify(linkStyle) + extraPadding.getStr(16.sp).toAnnotatedString()
val textWithPadding = remember(content.body) {
content.body + extraPadding.getStr(16.sp).toAnnotatedString()
}
ClickableLinkText(
text = styledText,
text = textWithPadding,
linkAnnotationTag = "URL",
onClick = onTextClicked,
onLongClick = onTextLongClicked,
@ -86,31 +75,6 @@ fun TimelineItemTextView(
}
}
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
)
}
}
@Preview
@Composable
internal fun TimelineItemTextViewLightPreview(@PreviewParameter(TimelineItemTextBasedContentProvider::class) content: TimelineItemTextBasedContent) =

View file

@ -104,10 +104,7 @@ private fun HtmlBody(
when (val node = nodes.next()) {
is TextNode -> {
if (!node.isBlank) {
Text(
text = node.text(),
color = MaterialTheme.colorScheme.primary,
)
ClickableLinkText(text = node.text(), interactionSource = interactionSource)
}
}
is Element -> {
@ -579,7 +576,7 @@ private fun HtmlText(
) {
val inlineContentMap = persistentMapOf<String, InlineTextContent>()
ClickableLinkText(
text = text,
annotatedString = text,
linkAnnotationTag = "URL",
style = style,
modifier = modifier,