diff --git a/app/src/main/java/com/rosetta/messenger/ui/components/AppleEmojiEditText.kt b/app/src/main/java/com/rosetta/messenger/ui/components/AppleEmojiEditText.kt index d880d63..1b2ba94 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/components/AppleEmojiEditText.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/components/AppleEmojiEditText.kt @@ -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(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() + + // 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 + } + } }