feat: Enhance AppleEmojiTextView to support :emoji_XXXX: format and Unicode emojis

This commit is contained in:
k1ngsterr1
2026-01-13 05:13:16 +05:00
parent f6409a9474
commit b1a334c954

View File

@@ -272,6 +272,9 @@ fun AppleEmojiText(
/**
* Apple Emoji TextView - для отображения текста с PNG эмодзи
* 🔥 Поддерживает:
* - Реальные Unicode эмодзи (😀)
* - Текстовый формат :emoji_1f600: (как в React Native версии)
*/
class AppleEmojiTextView @JvmOverloads constructor(
context: Context,
@@ -281,6 +284,8 @@ class AppleEmojiTextView @JvmOverloads constructor(
companion object {
private val EMOJI_PATTERN = AppleEmojiEditTextView.EMOJI_PATTERN
// 🔥 Паттерн для :emoji_XXXX: формата (из React Native)
private val EMOJI_CODE_PATTERN = Pattern.compile(":emoji_([a-fA-F0-9_-]+):")
private val bitmapCache = LruCache<String, Bitmap>(100)
}
@@ -290,24 +295,67 @@ class AppleEmojiTextView @JvmOverloads constructor(
}
fun setTextWithEmojis(text: String) {
// 🔥 Сначала заменяем :emoji_XXXX: на PNG изображения
val spannable = SpannableStringBuilder(text)
val matcher = EMOJI_PATTERN.matcher(text)
while (matcher.find()) {
val emoji = matcher.group()
// Собираем все замены (чтобы не сбить индексы)
data class EmojiMatch(val start: Int, val end: Int, val unified: String)
val emojiMatches = mutableListOf<EmojiMatch>()
// 1. Ищем :emoji_XXXX: формат
val codeMatcher = EMOJI_CODE_PATTERN.matcher(text)
while (codeMatcher.find()) {
val unified = codeMatcher.group(1) ?: continue
emojiMatches.add(EmojiMatch(codeMatcher.start(), codeMatcher.end(), unified))
}
// 2. Ищем реальные Unicode эмодзи
val unicodeMatcher = EMOJI_PATTERN.matcher(text)
while (unicodeMatcher.find()) {
val emoji = unicodeMatcher.group()
val unified = emojiToUnified(emoji)
val bitmap = loadEmojiBitmap(unified)
// Проверяем что этот диапазон не перекрывается с :emoji_XXXX:
val overlaps = emojiMatches.any {
(unicodeMatcher.start() >= it.start && unicodeMatcher.start() < it.end) ||
(unicodeMatcher.end() > it.start && unicodeMatcher.end() <= it.end)
}
if (!overlaps) {
emojiMatches.add(EmojiMatch(unicodeMatcher.start(), unicodeMatcher.end(), unified))
}
}
// 3. Сортируем по позиции в обратном порядке (чтобы не сбить индексы при замене)
emojiMatches.sortByDescending { it.start }
// 4. Применяем все замены
for (match in emojiMatches) {
val bitmap = loadEmojiBitmap(match.unified)
if (bitmap != null) {
val size = (textSize * 1.3).toInt() // Увеличиваем размер emoji
val size = (textSize * 1.3).toInt()
val scaledBitmap = Bitmap.createScaledBitmap(bitmap, size, size, true)
val drawable = BitmapDrawable(resources, scaledBitmap)
drawable.setBounds(0, 0, size, size)
// ALIGN_BOTTOM лучше работает с emoji - не обрезает сверху
val span = ImageSpan(drawable, ImageSpan.ALIGN_BOTTOM)
spannable.setSpan(span, matcher.start(), matcher.end(),
android.text.Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
// Для :emoji_XXXX: заменяем весь текст на пробел + span
// Для Unicode эмодзи оставляем символ как есть
if (match.end - match.start > 10) {
// Это :emoji_XXXX: формат - заменяем на один символ
spannable.replace(match.start, match.end, "\u200B") // Zero-width space
spannable.setSpan(span, match.start, match.start + 1,
android.text.Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
} else {
// Это Unicode эмодзи
spannable.setSpan(span, match.start, match.end,
android.text.Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
} else if (match.end - match.start > 10) {
// 🔥 Fallback: если PNG не найден, конвертируем :emoji_XXXX: в Unicode эмодзи
val unicodeEmoji = unifiedToEmoji(match.unified)
if (unicodeEmoji != null) {
spannable.replace(match.start, match.end, unicodeEmoji)
}
}
}
@@ -336,4 +384,16 @@ class AppleEmojiTextView @JvmOverloads constructor(
.toList()
.joinToString("-")
}
/**
* 🔥 Конвертирует unified код (1f600) в Unicode эмодзи (😀)
*/
private fun unifiedToEmoji(unified: String): String? {
return try {
val codePoints = unified.split("-").map { it.toInt(16) }
String(codePoints.toIntArray(), 0, codePoints.size)
} catch (e: Exception) {
null
}
}
}