Skip to content

Instantly share code, notes, and snippets.

@svenjacobs
Created April 25, 2023 11:57
Show Gist options
  • Save svenjacobs/918a05ca40e4b6916b31cc9481467533 to your computer and use it in GitHub Desktop.
Save svenjacobs/918a05ca40e4b6916b31cc9481467533 to your computer and use it in GitHub Desktop.
Composable for rendering text with Markdown-style hyperlinks
import androidx.compose.foundation.text.ClickableText
import androidx.compose.material.LocalContentAlpha
import androidx.compose.material.LocalContentColor
import androidx.compose.material.LocalTextStyle
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.graphics.takeOrElse
import androidx.compose.ui.text.ExperimentalTextApi
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.withAnnotation
import androidx.compose.ui.text.withStyle
private const val TAG_LINK = "link"
/**
* Renders text with clickable Markdown-style hyperlinks like `[Description](https://link)`.
*
* **Note that no other Markdown formatting is supported!**
*/
@Composable
@OptIn(ExperimentalTextApi::class)
fun MarkdownLinkText(
text: String,
onLinkClick: (String) -> Unit,
modifier: Modifier = Modifier,
color: Color = Color.Unspecified,
textAlign: TextAlign? = null,
style: TextStyle = LocalTextStyle.current,
linkStyle: SpanStyle = SpanStyle(textDecoration = TextDecoration.Underline),
) {
val textColor = color.takeOrElse {
style.color.takeOrElse {
LocalContentColor.current.copy(alpha = LocalContentAlpha.current)
}
}
val mergedStyle = style.merge(
TextStyle(
color = textColor,
textAlign = textAlign,
),
)
val annotatedText = remember(text) {
val linksRegex = Regex("\\[(.*?)]\\((.*?)\\)")
val results = linksRegex.findAll(text)
buildAnnotatedString {
val lastIndex = results.fold(0) { currentIndex, result ->
val before = text.substring(currentIndex until result.range.first)
append(before)
withStyle(linkStyle) {
withAnnotation(
tag = TAG_LINK,
annotation = result.groupValues[2],
) {
append(result.groupValues[1])
}
}
result.range.last + 1
}
val after = text.substring(lastIndex)
append(after)
}
}
ClickableText(
modifier = modifier,
text = annotatedText,
style = mergedStyle,
onClick = {
annotatedText.getStringAnnotations(TAG_LINK, it, it).firstOrNull()?.let { annotation ->
onLinkClick(annotation.item)
}
},
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment