Skip to content

Instantly share code, notes, and snippets.

@mutkuensert
Last active May 6, 2025 09:00
Show Gist options
  • Save mutkuensert/46e7ccb6aa298059bee1f853cec7883f to your computer and use it in GitHub Desktop.
Save mutkuensert/46e7ccb6aa298059bee1f853cec7883f to your computer and use it in GitHub Desktop.
import android.graphics.Typeface
import android.text.Annotation
import android.text.SpannedString
import android.text.style.ForegroundColorSpan
import android.text.style.StrikethroughSpan
import android.text.style.StyleSpan
import android.text.style.UnderlineSpan
import androidx.annotation.StringRes
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.LinkAnnotation
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextDecoration
import androidx.core.text.HtmlCompat
@Composable
fun spannedStringResource(
@StringRes id: Int
): SpannedString {
val context = LocalContext.current
return context.getText(id) as SpannedString
}
/**
* Add string resource as
* ```
* <string name="some_text"><annotation clickable="">Clickable part of the text</annotation> the rest of the text</string>
* ```
*
* Use [spannedStringResource] in [Text] composable
* ```
* Text(
* text = spannedStringResource(R.string.some_text)
* .toAnnotatedString { println("Clicked") }
* )
* ```
*
* @param style The style that will be applied to the annotated string.
* @param onPartClicked The click action that will be invoked when user clicks on the tagged part of the text.
*/
@Composable
fun SpannedString.toAnnotatedString(
style: SpanStyle = SpanStyle(),
onPartClicked: () -> Unit
): AnnotatedString {
return buildAnnotatedString {
val spanned = HtmlCompat.fromHtml(
[email protected](),
HtmlCompat.FROM_HTML_MODE_LEGACY
)
append(spanned)
val annotations = [email protected](
0,
[email protected],
Annotation::class.java
)
annotations.forEach { annotation ->
if ("clickable" == annotation.key) {
addLink(
LinkAnnotation.Clickable("clickable") { onPartClicked.invoke() },
getSpanStart(annotation),
getSpanEnd(annotation)
)
}
}
spanned.getSpans(0, spanned.length, Any::class.java).forEach { span ->
val start = spanned.getSpanStart(span)
val end = spanned.getSpanEnd(span)
addStyle(span, style, start, end)
}
}
}
private fun AnnotatedString.Builder.addStyle(
span: Any,
style: SpanStyle = SpanStyle(),
start: Int,
end: Int
) {
when (span) {
is StyleSpan -> when (span.style) {
Typeface.BOLD -> addStyle(style.copy(fontWeight = FontWeight.Bold), start, end)
Typeface.ITALIC -> addStyle(
style.copy(fontStyle = FontStyle.Italic),
start,
end
)
Typeface.BOLD_ITALIC -> addStyle(
style.copy(
fontWeight = FontWeight.Bold,
fontStyle = FontStyle.Italic
),
start,
end
)
}
is UnderlineSpan -> addStyle(
style.copy(textDecoration = TextDecoration.Underline),
start,
end
)
is ForegroundColorSpan -> addStyle(
style.copy(color = Color(span.foregroundColor)),
start,
end
)
is StrikethroughSpan -> addStyle(
style.copy(textDecoration = TextDecoration.LineThrough),
start,
end
)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment