Skip to content

Instantly share code, notes, and snippets.

@venator85
Forked from euri16/StyledString.kt
Last active August 7, 2025 13:12
Show Gist options
  • Save venator85/e19857c7cfdddeae3210253b40f19108 to your computer and use it in GitHub Desktop.
Save venator85/e19857c7cfdddeae3210253b40f19108 to your computer and use it in GitHub Desktop.
package com.cmttranslations.listener.ui.util
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.LinkAnnotation
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextLinkStyles
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextDecoration
sealed interface ClickableStyleString
/**
* A sealed interface representing different types of styled strings that can be used in a
* [rememberStyledClickableString] function. It includes clickable email links
* and clickable URL links.
*
* Each type contains the highlighted text, its style, and additional properties
* for clickable types (email and URL).
*/
@Immutable
sealed interface StyledString {
val highlightedText: String
val style: SpanStyle
/**
* Represents a clickable email link with a specific style.
*
* @param highlightedText The text to be highlighted.
* @param email The email address that the link points to.
* @param style The style to be applied to the highlighted text.
*/
@Immutable
data class ClickableEmail(
override val highlightedText: String,
val email: String,
override val style: SpanStyle,
) : StyledString, ClickableStyleString
/**
* Represents a clickable URL link with a specific style.
*
* @param highlightedText The text to be highlighted.
* @param url The URL that the link points to.
* @param style The style to be applied to the highlighted text.
*/
@Immutable
data class ClickableUrl(
override val highlightedText: String,
val url: String,
override val style: SpanStyle
) : StyledString, ClickableStyleString
}
/**
* Creates an [AnnotatedString] from the provided full text, automatically detecting
* email addresses and URLs using regex patterns.
*
* This function builds an annotated string that applies clickable links to detected
* email addresses and URLs in the text.
*
* @param fullText The complete text to be processed.
* @param emailStyle The style to apply to detected email addresses. Defaults to blue color.
* @param urlStyle The style to apply to detected URLs. Defaults to blue color.
* @param onClick A callback function that is invoked when a clickable link is clicked.
* @return An [AnnotatedString] with automatically detected clickable email and URL links.
*/
@Composable
fun rememberStyledClickableString(
fullText: String,
emailStyle: SpanStyle = SpanStyle(color = MaterialTheme.colorScheme.primary, textDecoration = TextDecoration.Underline),
urlStyle: SpanStyle = SpanStyle(color = MaterialTheme.colorScheme.primary, textDecoration = TextDecoration.Underline),
onClick: (ClickableStyleString) -> Unit
): AnnotatedString {
val currentOnClick by rememberUpdatedState(onClick)
return remember(fullText, emailStyle, urlStyle) {
buildAnnotatedString {
append(fullText)
// Apply email styles
val emailMatches = EMAIL_REGEX.findAll(fullText)
emailMatches.forEach { match ->
val emailString = StyledString.ClickableEmail(
highlightedText = match.value,
email = match.value,
style = emailStyle
)
applyStyle(
styledString = emailString,
startIndex = match.range.first,
endIndex = match.range.last + 1,
onClick = currentOnClick
)
}
// Apply URL styles
val urlMatches = URL_REGEX.findAll(fullText)
urlMatches.forEach { match ->
val urlString = StyledString.ClickableUrl(
highlightedText = match.value,
url = match.value,
style = urlStyle
)
applyStyle(
styledString = urlString,
startIndex = match.range.first,
endIndex = match.range.last + 1,
onClick = currentOnClick
)
}
}
}
}
private fun AnnotatedString.Builder.applyStyle(
styledString: StyledString,
startIndex: Int,
endIndex: Int,
onClick: (ClickableStyleString) -> Unit
) {
when (styledString) {
is StyledString.ClickableUrl -> {
val linkAnnotation = LinkAnnotation.Url(
url = styledString.url,
styles = TextLinkStyles(style = styledString.style),
linkInteractionListener = { onClick(styledString) }
)
addLink(linkAnnotation, startIndex, endIndex)
}
is StyledString.ClickableEmail -> {
val linkAnnotation = LinkAnnotation.Clickable(
tag = styledString.highlightedText,
styles = TextLinkStyles(style = styledString.style),
linkInteractionListener = { onClick(styledString) }
)
addLink(linkAnnotation, startIndex, endIndex)
}
}
}
// Regex patterns for email and URL detection
private val EMAIL_REGEX = Regex(
pattern = "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}",
option = RegexOption.IGNORE_CASE
)
private val URL_REGEX = Regex(
pattern = "https?://[a-zA-Z0-9.-]+(?:\\.[a-zA-Z]{2,})+(?:/[^\\s]*)?",
option = RegexOption.IGNORE_CASE
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment