-
-
Save stevdza-san/ff9dbec0e072d8090e1e6d16e6b73c91 to your computer and use it in GitHub Desktop.
import androidx.compose.foundation.text.ClickableText | |
import androidx.compose.runtime.Composable | |
import androidx.compose.ui.Modifier | |
import androidx.compose.ui.graphics.Color | |
import androidx.compose.ui.platform.LocalUriHandler | |
import androidx.compose.ui.text.SpanStyle | |
import androidx.compose.ui.text.buildAnnotatedString | |
import androidx.compose.ui.text.font.FontWeight | |
import androidx.compose.ui.text.style.TextDecoration | |
import androidx.compose.ui.unit.TextUnit | |
@Composable | |
fun HyperlinkText( | |
modifier: Modifier = Modifier, | |
fullText: String, | |
linkText: List<String>, | |
linkTextColor: Color = Color.Blue, | |
linkTextFontWeight: FontWeight = FontWeight.Medium, | |
linkTextDecoration: TextDecoration = TextDecoration.Underline, | |
hyperlinks: List<String> = listOf("https://stevdza-san.com"), | |
fontSize: TextUnit = TextUnit.Unspecified | |
) { | |
val annotatedString = buildAnnotatedString { | |
append(fullText) | |
linkText.forEachIndexed { index, link -> | |
val startIndex = fullText.indexOf(link) | |
val endIndex = startIndex + link.length | |
addStyle( | |
style = SpanStyle( | |
color = linkTextColor, | |
fontSize = fontSize, | |
fontWeight = linkTextFontWeight, | |
textDecoration = linkTextDecoration | |
), | |
start = startIndex, | |
end = endIndex | |
) | |
addStringAnnotation( | |
tag = "URL", | |
annotation = hyperlinks[index], | |
start = startIndex, | |
end = endIndex | |
) | |
} | |
addStyle( | |
style = SpanStyle( | |
fontSize = fontSize | |
), | |
start = 0, | |
end = fullText.length | |
) | |
} | |
val uriHandler = LocalUriHandler.current | |
ClickableText( | |
modifier = modifier, | |
text = annotatedString, | |
onClick = { | |
annotatedString | |
.getStringAnnotations("URL", it, it) | |
.firstOrNull()?.let { stringAnnotation -> | |
uriHandler.openUri(stringAnnotation.item) | |
} | |
} | |
) | |
} |
useful composable. Thanks.
It's best to wrap uriHandler.openUri(stringAnnotation.item)
in a try-catch
because it throws exception if no activity is found to handle the intent.
try {
uriHandler.openUri(stringAnnotation.item)
} catch(e: Exception) {
// handle it
}
If you have to use @StringRes:
@Composable
fun HyperlinkText(
modifier: Modifier = Modifier,
@StringRes fullTextResId: Int,
linksActions: List<String>,
hyperLinks: List<String>,
textStyle: TextStyle = TextStyle.Default,
linkTextColor: Color = Color.Blue,
linkTextFontWeight: FontWeight = FontWeight.Normal,
linkTextDecoration: TextDecoration = TextDecoration.None,
fontSize: TextUnit = TextUnit.Unspecified
) {
val fullText = LocalContext.current.getText(fullTextResId).toSpannable()
val annotations = fullText.getSpans(0, fullText.length, Annotation::class.java)
val annotatedString = buildAnnotatedString {
append(fullText)
linksActions.forEachIndexed { index, actionAnnotation ->
annotations?.find { it.value == actionAnnotation }?.let {
addStyle(
style = SpanStyle(
color = linkTextColor,
fontSize = fontSize,
fontWeight = linkTextFontWeight,
textDecoration = linkTextDecoration
),
start = fullText.getSpanStart(it),
end = fullText.getSpanEnd(it)
)
addStringAnnotation(
tag = "URL",
annotation = hyperLinks.get(index),
start = fullText.getSpanStart(it),
end = fullText.getSpanEnd(it)
)
}
addStyle(
style = SpanStyle(
fontSize = fontSize
),
start = 0,
end = fullText.length
)
}
}
val uriHandler = LocalUriHandler.current
ClickableText(
modifier = modifier,
text = annotatedString,
style = textStyle,
onClick = {
annotatedString
.getStringAnnotations("URL", it, it)
.firstOrNull()?.let { stringAnnotation ->
uriHandler.openUri(stringAnnotation.item)
}
}
)
}
Usage:
HyperlinkText(
modifier = Modifier.fillMaxWidth(),
fullTextResId = R.string.text_for_link,
linksActions = listOf("LINK"),
hyperLinks = listOf("https://google.com")
)
<string name="text_for_link">With text contain a <annotation type="LINK">link</annotation></string>
if you need a few links:
HyperlinkText(
modifier = Modifier.fillMaxWidth(),
fullTextResId = R.string.text_for_link,
linksActions = listOf("LINK1, LINK2"),
hyperLinks = listOf("https://google.com", "https://github.com")
)
<string name="text_for_link">With text contain a <annotation type="LINK1">link1</annotation> and you can paste <annotation type="LINK2">link2</annotation></string>
Why the onClick callback is never called using the top composable exemple:
@composable
fun HyperlinkText(
modifier: Modifier = Modifier,
fullText: String,
linkText: List,
linkTextColor: Color = Color.Blue,
linkTextFontWeight: FontWeight = FontWeight.Medium,
linkTextDecoration: TextDecoration = TextDecoration.Underline,
hyperlinks: List = listOf(),
fontSize: TextUnit = 24.sp
)
Cant find annotation key or value
This implementation as many others online does not work well with Accesibility. TalkBack is not reading each link individually and by that they are not triggered. I'm working on this and if I have a solution for it in Compose only i'll share it here. In the meantime you can maybe can be aware of it too :)
Now that ClickableText
in Jetpack Compose is deprecated, this is my implementation, of course inspired by @stevdza-san's solution.
import androidx.compose.foundation.text.LinkAnnotation
import androidx.compose.foundation.text.TextLinkStyles
import androidx.compose.foundation.text.withLink
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.TextUnit
@Composable
fun HyperlinkText(
modifier: Modifier = Modifier,
text: String,
linkText: List<String>,
hyperlinks: List<String>,
linkTextColor: Color = MaterialTheme.colorScheme.primary,
linkTextFontWeight: FontWeight = FontWeight.Normal,
linkTextDecoration: TextDecoration = TextDecoration.Underline,
fontSize: TextUnit = TextUnit.Unspecified,
fontFamily: FontFamily = FontFamily.Monospace
) {
val uriHandler = LocalUriHandler.current
val annotatedString = buildAnnotatedString {
var lastIndex = 0
linkText.forEachIndexed { index, link ->
val startIndex = text.indexOf(link, lastIndex)
val endIndex = startIndex + link.length
if (startIndex > lastIndex) append(text.substring(lastIndex, startIndex))
val linkUrL = LinkAnnotation.Url(
hyperlinks[index], TextLinkStyles(
SpanStyle(
color = linkTextColor,
fontSize = fontSize,
fontWeight = linkTextFontWeight,
textDecoration = linkTextDecoration,
fontFamily = fontFamily
)
)
) {
val url = (it as LinkAnnotation.Url).url
uriHandler.openUri(url)
}
withLink(linkUrL) { append(link) }
append(" ")
lastIndex = endIndex + 1
}
if (lastIndex < text.length) {
append(text.substring(lastIndex))
}
addStyle(
style = SpanStyle(
fontSize = fontSize, fontFamily = fontFamily
), start = 0, end = text.length
)
}
Text(text = annotatedString, modifier = modifier)
}
Thanks 👍
Thanks for sharing the code, appreciate that.
I made it a bit more expressive using
Maps
Usage
New Implementation