@@ -22,6 +22,7 @@ import androidx.compose.ui.graphics.Color
2222import androidx.compose.ui.graphics.LinearGradientShader
2323import androidx.compose.ui.graphics.Shader
2424import androidx.compose.ui.graphics.ShaderBrush
25+ import androidx.compose.ui.graphics.isSpecified
2526import androidx.compose.ui.layout.layout
2627import androidx.compose.ui.text.AnnotatedString
2728import androidx.compose.ui.text.SpanStyle
@@ -260,14 +261,68 @@ private fun AnnotatedString.withDynamicColorPhrases(
260261}
261262
262263private fun AnnotatedString.withDynamicColor (color : Color , alpha : () -> Float ): AnnotatedString {
264+ val useDynamicColor = ! maybeContainsEmojis()
265+
263266 val subStyles = spanStyles.map {
264- it.copy(item = it.item.copy(brush = DynamicSolidColor (it.item.color, alpha)))
267+ val style = it.item
268+ if (useDynamicColor) {
269+ it.copy(item = style.copy(brush = DynamicSolidColor (style.color) { style.alpha * alpha() }))
270+ } else if (style.color.isSpecified) {
271+ it.copy(item = style.copy(color = style.color.copy(alpha = style.color.alpha * alpha())))
272+ } else {
273+ it.copy(item = style.copy(brush = style.brush, alpha = alpha()))
274+ }
265275 }
266- val fullStyle =
267- AnnotatedString .Range (SpanStyle (brush = DynamicSolidColor (color, alpha)), 0 , length)
276+ val fullStyle = AnnotatedString .Range (
277+ item = if (useDynamicColor) {
278+ SpanStyle (brush = DynamicSolidColor (color, alpha))
279+ } else {
280+ SpanStyle (brush = DynamicSolidColor (color) { 1f }, alpha = alpha())
281+ },
282+ start = 0 ,
283+ end = length
284+ )
268285 return AnnotatedString (text, subStyles + fullStyle)
269286}
270287
288+ private fun CharSequence.maybeContainsEmojis (): Boolean {
289+ var i = 0
290+ val n = length
291+ while (i < n) {
292+ val cp = Character .codePointAt(this , i)
293+
294+ // --- Quick accepts: common emoji blocks ---
295+ val isEmoji = when (cp) {
296+ // Misc Symbols + Dingbats + arrows subset that often render as emoji
297+ in 0x2600 .. 0x27BF -> true
298+ // Enclosed CJK (e.g., 🈶, 🈚)
299+ in 0x1F200 .. 0x1F2FF -> true
300+ // Misc Symbols & Pictographs
301+ in 0x1F300 .. 0x1F5FF -> true
302+ // Emoticons
303+ in 0x1F600 .. 0x1F64F -> true
304+ // Transport & Map
305+ in 0x1F680 .. 0x1F6FF -> true
306+ // Supplemental Symbols & Pictographs
307+ in 0x1F900 .. 0x1F9FF -> true
308+ // Symbols & Pictographs Extended-A (newer emoji live here)
309+ in 0x1FA70 .. 0x1FAFF -> true
310+ // Regional indicators (flags as pairs, but single is enough for "contains")
311+ in 0x1F1E6 .. 0x1F1FF -> true
312+ // Keycap base digits/#/* (paired with VS16 + COMBINING ENCLOSING KEYCAP, but base char is fine)
313+ in 0x0030 .. 0x0039 , 0x0023 , 0x002A -> true
314+ // Variation Selector-16 forces emoji presentation for some BMP symbols
315+ 0xFE0F -> true
316+ else -> false
317+ }
318+
319+ if (isEmoji) return true
320+
321+ i + = Character .charCount(cp)
322+ }
323+ return false
324+ }
325+
271326private fun AnnotatedString.getConsumableAnnotations (
272327 textFormatObjects : Map <String , Any >,
273328 offset : Int ,
0 commit comments