Skip to content

Instantly share code, notes, and snippets.

@vadiole
Created September 19, 2022 13:26
Show Gist options
  • Save vadiole/f6b3709598f15e1530fc7a86d21d6afd to your computer and use it in GitHub Desktop.
Save vadiole/f6b3709598f15e1530fc7a86d21d6afd to your computer and use it in GitHub Desktop.
Composable function for displaying text with inline clickable links (touch effect included)
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.text.BasicText
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.isSpecified
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.style.TextOverflow
@Composable
fun HyperlinkText(
text: String,
links: Map<String, String>,
modifier: Modifier = Modifier,
style: TextStyle = TextStyle.Default,
linkStyle: SpanStyle = SpanStyle(textDecoration = TextDecoration.Underline),
softWrap: Boolean = true,
overflow: TextOverflow = TextOverflow.Clip,
maxLines: Int = Int.MAX_VALUE,
onTextLayout: (TextLayoutResult) -> Unit = {},
onLinkClick: (String) -> Unit,
) {
val pressed = links.values
.associateWith {
remember { mutableStateOf(false) }
}
.toMutableMap()
val pressedColor = linkStyle.color.let { linkColor ->
if (linkColor.isSpecified) {
linkColor.copy(alpha = linkColor.alpha / 2f)
} else {
style.color.copy(alpha = style.color.alpha / 2f)
}
}
val annotatedString = buildAnnotatedString {
append(text)
links.forEach { (linkName, link) ->
val startIndex = text.indexOf(linkName)
val endIndex = startIndex + linkName.length
val linkColor = if (pressed[link]?.value == true) {
pressedColor
} else {
linkStyle.color
}
addStyle(
style = linkStyle.copy(
color = linkColor
),
start = startIndex,
end = endIndex
)
addStringAnnotation(
tag = "URL",
annotation = link,
start = startIndex,
end = endIndex
)
}
}
val layoutResult = remember { mutableStateOf<TextLayoutResult?>(null) }
val pressIndicator = Modifier.pointerInput(onLinkClick) {
detectTapGestures(
onTap = { pos ->
val layoutResultValue = layoutResult.value ?: return@detectTapGestures
val offset = layoutResultValue.getOffsetForPosition(pos)
val stringAnnotation = annotatedString
.getStringAnnotations("URL", offset, offset)
.firstOrNull() ?: return@detectTapGestures
val link = stringAnnotation.item
onLinkClick.invoke(link)
},
onPress = { pos ->
val layoutResultValue = layoutResult.value ?: return@detectTapGestures
val offset = layoutResultValue.getOffsetForPosition(pos)
val stringAnnotation = annotatedString
.getStringAnnotations("URL", offset, offset)
.firstOrNull() ?: return@detectTapGestures
val link = stringAnnotation.item
pressed[link]?.value = true
tryAwaitRelease()
pressed[link]?.value = false
}
)
}
BasicText(
text = annotatedString,
modifier = modifier.then(pressIndicator),
style = style,
softWrap = softWrap,
overflow = overflow,
maxLines = maxLines,
onTextLayout = {
layoutResult.value = it
onTextLayout(it)
},
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment