Skip to content

Commit 4d3846b

Browse files
authored
Merge pull request #47 from openai/nicklas/animate-emojis
Animate emojis
2 parents 27abd98 + 94dcebf commit 4d3846b

File tree

1 file changed

+58
-3
lines changed
  • richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/string

1 file changed

+58
-3
lines changed

richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/string/Text.kt

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import androidx.compose.ui.graphics.Color
2222
import androidx.compose.ui.graphics.LinearGradientShader
2323
import androidx.compose.ui.graphics.Shader
2424
import androidx.compose.ui.graphics.ShaderBrush
25+
import androidx.compose.ui.graphics.isSpecified
2526
import androidx.compose.ui.layout.layout
2627
import androidx.compose.ui.text.AnnotatedString
2728
import androidx.compose.ui.text.SpanStyle
@@ -260,14 +261,68 @@ private fun AnnotatedString.withDynamicColorPhrases(
260261
}
261262

262263
private 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+
271326
private fun AnnotatedString.getConsumableAnnotations(
272327
textFormatObjects: Map<String, Any>,
273328
offset: Int,

0 commit comments

Comments
 (0)