feat: Enhance AppleEmojiTextView to support :emoji_XXXX: format and Unicode emojis
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user