I have a large project that uses regular android layouts. I'm starting to use compose on this project. However, I already have a large codebase and a lot of utils that deal with CharSequence and Spannable. For example, currency formatter that returns Spannable.
Compose Text doesn't accept neither CharSequence nor Spannable. However, it does accept AnnotatedString and, from what I can tell, they are basically the same thing. So I'm thinking that there must be a way to easily convert Spannable to AnnotatedString, something like spannable.toAnnotatedString(), but I can't find anything so far.
Can I convert Spannable to AnnotatedString or do I have to write a lot of code from scratch?
This solution works for basic html, but you might want to add more Copier classes to support more exotic spans.
fun Spannable.toAnnotatedString(primaryColor: Color): AnnotatedString {
val builder = AnnotatedString.Builder(this.toString())
val copierContext = CopierContext(primaryColor)
SpanCopier.values().forEach { copier ->
getSpans(0, length, copier.spanClass).forEach { span ->
copier.copySpan(span, getSpanStart(span), getSpanEnd(span), builder, copierContext)
}
}
return builder.toAnnotatedString()
}
private data class CopierContext(
val primaryColor: Color,
)
private enum class SpanCopier {
URL {
override val spanClass = URLSpan::class.java
override fun copySpan(
span: Any,
start: Int,
end: Int,
destination: AnnotatedString.Builder,
context: CopierContext
) {
val urlSpan = span as URLSpan
destination.addStringAnnotation(
tag = name,
annotation = urlSpan.url,
start = start,
end = end,
)
destination.addStyle(
style = SpanStyle(color = context.primaryColor, textDecoration = TextDecoration.Underline),
start = start,
end = end,
)
}
},
FOREGROUND_COLOR {
override val spanClass = ForegroundColorSpan::class.java
override fun copySpan(
span: Any,
start: Int,
end: Int,
destination: AnnotatedString.Builder,
context: CopierContext
) {
val colorSpan = span as ForegroundColorSpan
destination.addStyle(
style = SpanStyle(color = Color(colorSpan.foregroundColor)),
start = start,
end = end,
)
}
},
UNDERLINE {
override val spanClass = UnderlineSpan::class.java
override fun copySpan(
span: Any,
start: Int,
end: Int,
destination: AnnotatedString.Builder,
context: CopierContext
) {
destination.addStyle(
style = SpanStyle(textDecoration = TextDecoration.Underline),
start = start,
end = end,
)
}
},
STYLE {
override val spanClass = StyleSpan::class.java
override fun copySpan(
span: Any,
start: Int,
end: Int,
destination: AnnotatedString.Builder,
context: CopierContext
) {
val styleSpan = span as StyleSpan
destination.addStyle(
style = when (styleSpan.style) {
Typeface.ITALIC -> SpanStyle(fontStyle = FontStyle.Italic)
Typeface.BOLD -> SpanStyle(fontWeight = FontWeight.Bold)
Typeface.BOLD_ITALIC -> SpanStyle(fontWeight = FontWeight.Bold, fontStyle = FontStyle.Italic)
else -> SpanStyle()
},
start = start,
end = end,
)
}
};
abstract val spanClass: Class<out CharacterStyle>
abstract fun copySpan(span: Any, start: Int, end: Int, destination: AnnotatedString.Builder, context: CopierContext)
}
There is a really handy library which already solves this problem. You can convert Android's Spanned into AnnotatedString with a lot of customisations.
https://github.com/Aghajari/AnnotatedText?tab=readme-ov-file
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With