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)
}