feat: Enhance message handling and emoji picker

- Update MessageEntity to clarify encryption of plainMessage.
- Introduce ERROR status in MessageStatus for handling message send failures.
- Implement message delivery timeout logic in ChatDetailScreen.
- Add retry and delete functionality for failed messages in ChatViewModel.
- Improve message decryption process in ChatViewModel to handle various scenarios.
- Refactor emoji categories in AppleEmojiPicker to align with Unicode standards and improve sorting.
This commit is contained in:
k1ngsterr1
2026-01-13 16:05:42 +05:00
parent 764127c093
commit 42b1cdd79a
5 changed files with 551 additions and 113 deletions

View File

@@ -53,81 +53,303 @@ data class EmojiCategory(
val ranges: List<Pair<Int, Int>>
)
// Порядок категорий
// Unicode Standard Emoji Ordering (v15.0)
// Порядок категорий согласно Unicode CLDR
val EMOJI_CATEGORIES = listOf(
// 😀 Smileys & Emotion
// 😀 Smileys & Emotion (Unicode ordering)
EmojiCategory("Smileys", "Смайлы", Icons.Default.SentimentSatisfied, listOf(
0x1F600 to 0x1F64F,
0x1F910 to 0x1F92F,
0x1F970 to 0x1F9FF,
0x263A to 0x263A,
0x2639 to 0x2639
// Face-Smiling
0x1F600 to 0x1F60F, // 😀-😏
// Face-Affection
0x1F617 to 0x1F61D, // 😗-😝
0x1F970 to 0x1F979, // 🥰-🥹
// Face-Tongue
0x1F60B to 0x1F60D, // 😋-😍
0x1F61B to 0x1F61C, // 😛-😜
// Face-Hand
0x1F917 to 0x1F917, // 🤗
0x1F92D to 0x1F92F, // 🤭-🤯
0x1FAE1 to 0x1FAE3, // 🫡-🫣
// Face-Neutral-Skeptical
0x1F610 to 0x1F615, // 😐-😕
0x1F636 to 0x1F636, // 😶
0x1FAE4 to 0x1FAE8, // 🫤-🫨
0x1F644 to 0x1F644, // 🙄
// Face-Sleepy
0x1F62A to 0x1F62C, // 😪-😬
0x1F634 to 0x1F634, // 😴
// Face-Unwell
0x1F637 to 0x1F637, // 😷
0x1F912 to 0x1F915, // 🤒-🤕
0x1F922 to 0x1F92B, // 🤢-🤫
0x1F975 to 0x1F976, // 🥵-🥶
// Face-Hat
0x1F920 to 0x1F921, // 🤠-🤡
// Face-Glasses
0x1F913 to 0x1F913, // 🤓
0x1F60E to 0x1F60E, // 😎
0x1F978 to 0x1F978, // 🥸
// Face-Concerned
0x1F61E to 0x1F629, // 😞-😩
0x1F62D to 0x1F62D, // 😭
0x1F630 to 0x1F633, // 😰-😳
0x1F641 to 0x1F643, // 🙁-🙃
0x1F97A to 0x1F97A, // 🥺
// Face-Negative
0x1F616 to 0x1F616, // 😖
0x1F62E to 0x1F62F, // 😮-😯
0x1F635 to 0x1F635, // 😵
0x263A to 0x263A, // ☺
0x2639 to 0x2639 // ☹
)),
// 👋 People & Body
// 👋 People & Body (Hands, Body parts, Persons)
EmojiCategory("People", "Люди", Icons.Default.Person, listOf(
0x1F466 to 0x1F4FF,
0x1F9D0 to 0x1F9DF,
0x270A to 0x270D,
0x261D to 0x261D,
0x1F440 to 0x1F465
// Hand-Fingers-Open
0x1F44B to 0x1F44D, // 👋-👍
0x1FAF0 to 0x1FAF8, // 🫰-🫸
// Hand-Fingers-Partial
0x1F44E to 0x1F44F, // 👎-👏
0x1F91D to 0x1F91F, // 🤝-🤟
0x1F918 to 0x1F91C, // 🤘-🤜
// Hand-Single-Finger
0x261D to 0x261D, // ☝
0x1F446 to 0x1F44A, // 👆-👊
// Hand-Fingers-Closed
0x270A to 0x270D, // ✊-✍
0x1F450 to 0x1F450, // 👐
0x1F64C to 0x1F64F, // 🙌-🙏
// Body-Parts
0x1F440 to 0x1F445, // 👀-👅
0x1FAC0 to 0x1FAC5, // 🫀-🫅
0x1F9B4 to 0x1F9BF, // 🦴-🦿
// Person
0x1F466 to 0x1F469, // 👦-👩
0x1F9D1 to 0x1F9DD, // 🧑-🧝
// Person-Gesture
0x1F645 to 0x1F647, // 🙅-🙇
0x1F64B to 0x1F64B, // 🙋
0x1F926 to 0x1F926, // 🤦
0x1F937 to 0x1F937, // 🤷
// Person-Role, Person-Activity
0x1F46A to 0x1F46F, // 👪-👯
0x1F470 to 0x1F487, // 👰-💇
0x1F6B4 to 0x1F6B6, // 🚴-🚶
0x1F9CE to 0x1F9CF // 🧎-🧏
)),
// 🐱 Animals & Nature
EmojiCategory("Animals", "Животные", Icons.Default.Pets, listOf(
0x1F400 to 0x1F43F,
0x1F980 to 0x1F9AE,
0x1F330 to 0x1F335,
0x1F337 to 0x1F34F,
0x2618 to 0x2618
// Animal-Mammal
0x1F435 to 0x1F43E, // 🐵-🐾
0x1F9A0 to 0x1F9AE, // 🦠-🦮
0x1F981 to 0x1F99F, // 🦁-🦟
// Animal-Bird
0x1F413 to 0x1F414, // 🐓-🐔
0x1F423 to 0x1F427, // 🐣-🐧
0x1F54A to 0x1F54A, // 🕊
0x1F983 to 0x1F987, // 🦃-🦇
0x1F99A to 0x1F99C, // 🦚-🦜
0x1FABD to 0x1FABF, // 🪽-🪿
// Animal-Amphibian, Reptile
0x1F40D to 0x1F40E, // 🐍-🐎
0x1F428 to 0x1F42D, // 🐨-🐭
0x1F422 to 0x1F422, // 🐢
0x1F98E to 0x1F998, // 🦎-🦘
// Animal-Marine
0x1F419 to 0x1F421, // 🐙-🐡
0x1F988 to 0x1F98D, // 🦈-🦍
0x1FAB4 to 0x1FABC, // 🪴-🪼
// Animal-Bug
0x1F40C to 0x1F40C, // 🐌
0x1F41B to 0x1F41F, // 🐛-🐟
0x1F577 to 0x1F578, // 🕷-🕸
0x1F997 to 0x1F997, // 🦗
0x1FAB0 to 0x1FAB3, // 🪰-🪳
// Plant-Flower
0x1F337 to 0x1F340, // 🌷-🍀
0x1F490 to 0x1F490, // 💐
0x1FAB7 to 0x1FABB, // 🪷-🪻
// Plant-Other
0x1F330 to 0x1F336, // 🌰-🌶
0x1F341 to 0x1F344, // 🍁-🍄
0x2618 to 0x2618 // ☘
)),
// 🍎 Food & Drink
EmojiCategory("Food", "Еда", Icons.Default.Restaurant, listOf(
0x1F345 to 0x1F37F,
0x1F950 to 0x1F96F,
0x1F9C0 to 0x1F9CB,
0x1FAD0 to 0x1FAD9,
0x2615 to 0x2615
// Food-Fruit
0x1F347 to 0x1F353, // 🍇-🍓
0x1FAD0 to 0x1FAD4, // 🫐-🫔
0x1F95D to 0x1F95D, // 🥝
// Food-Vegetable
0x1F345 to 0x1F346, // 🍅-🍆
0x1F951 to 0x1F95C, // 🥑-🥜
0x1F96C to 0x1F96F, // 🥬-🥯
0x1FAD5 to 0x1FAD8, // 🫕-🫘
// Food-Prepared
0x1F354 to 0x1F37B, // 🍔-🍻
0x1F95E to 0x1F96B, // 🥞-🥫
0x1F9C0 to 0x1F9CB, // 🧀-🧋
0x1FAD9 to 0x1FADB, // 🫙-🫛
// Drink
0x1F37C to 0x1F37F, // 🍼-🍿
0x2615 to 0x2615, // ☕
0x1F9C3 to 0x1F9C9 // 🧃-🧉
)),
// ✈️ Travel & Places
EmojiCategory("Travel", "Места", Icons.Default.Flight, listOf(
0x1F680 to 0x1F6FF,
0x1F3D4 to 0x1F3DF,
0x1F3E0 to 0x1F3F0,
0x2708 to 0x2708,
0x26F0 to 0x26FF
// Place-Map
0x1F30D to 0x1F310, // 🌍-🌐
0x1F5FA to 0x1F5FA, // 🗺
// Place-Geographic
0x26F0 to 0x26F1, // ⛰-⛱
0x1F3D4 to 0x1F3DC, // 🏔-🏜
0x1F30B to 0x1F30C, // 🌋-🌌
// Place-Building
0x1F3D7 to 0x1F3DB, // 🏗-🏛
0x1F3DD to 0x1F3DF, // 🏝-🏟
0x1F3E0 to 0x1F3F0, // 🏠-🏰
// Place-Religious
0x26EA to 0x26EA, // ⛪
0x1F54C to 0x1F54D, // 🕌-🕍
// Place-Other
0x26F2 to 0x26F5, // ⛲-⛵
0x1F5FC to 0x1F5FF, // 🗼-🗿
// Transport-Ground
0x1F680 to 0x1F6A0, // 🚀-🚠
0x1F6A1 to 0x1F6C5, // 🚡-🛅
0x1F6F0 to 0x1F6FF, // 🛰-🛿
0x1F68A to 0x1F68F, // 🚊-🚏
0x2708 to 0x2708 // ✈
)),
// ⚽ Activities
EmojiCategory("Activities", "Спорт", Icons.Default.SportsSoccer, listOf(
0x1F3A0 to 0x1F3CA,
0x1F3CB to 0x1F3D3,
0x1F93C to 0x1F94F,
0x26BD to 0x26BE,
0x265F to 0x2660,
0x1F9E0 to 0x1F9FF
// Event
0x1F380 to 0x1F393, // 🎀-🎓
0x1F9E7 to 0x1F9E7, // 🧧
// Award-Medal
0x1F396 to 0x1F397, // 🎖-🎗
0x1F3C5 to 0x1F3C6, // 🏅-🏆
// Sport
0x26BD to 0x26BE, // ⚽-⚾
0x1F3C8 to 0x1F3D3, // 🏈-🏓
0x1F93C to 0x1F94F, // 🤼-🥏
0x1F945 to 0x1F945, // 🥅
// Game
0x1F3A0 to 0x1F3C4, // 🎠-🏄
0x1F3CB to 0x1F3CB, // 🏋
0x1F3CC to 0x1F3CF, // 🏌-🏏
0x265F to 0x2660, // ♟-♠
0x1F0CF to 0x1F0CF, // 🃏
0x1FA80 to 0x1FA88, // 🪀-🪈
// Arts & Crafts
0x1F3A4 to 0x1F3B4, // 🎤-🎴
0x1F3B5 to 0x1F3BE, // 🎵-🎾
0x1FA94 to 0x1FA9F, // 🪔-🪟
0x1F9F6 to 0x1F9FF // 🧶-🧿
)),
// 💡 Objects
EmojiCategory("Objects", "Объекты", Icons.Default.Lightbulb, listOf(
0x1F4A1 to 0x1F4FF,
0x1F500 to 0x1F5FF,
0x1F6E0 to 0x1F6EF,
0x1FA70 to 0x1FAFF,
0x2328 to 0x2328
// Clothing
0x1F451 to 0x1F462, // 👑-👢
0x1F97B to 0x1F97F, // 🥻-🥿
0x1FA70 to 0x1FA7C, // 🩰-🩼
// Sound
0x1F507 to 0x1F50E, // 🔇-🔎
0x1F4E2 to 0x1F4E3, // 📢-📣
// Music
0x1F3B7 to 0x1F3BB, // 🎷-🎻
0x1FA95 to 0x1FA98, // 🪕-🪘
// Phone
0x1F4F1 to 0x1F4F5, // 📱-📵
// Computer
0x1F4BB to 0x1F4BF, // 💻-💿
0x1F5A5 to 0x1F5B2, // 🖥-🖲
0x2328 to 0x2328, // ⌨
// Light & Video
0x1F4A1 to 0x1F4A1, // 💡
0x1F4F7 to 0x1F4FD, // 📷-📽
0x1F50B to 0x1F50D, // 🔋-🔍
0x1F56F to 0x1F570, // 🕯-🕰
// Book-Paper
0x1F4D0 to 0x1F4DA, // 📐-📚
0x1F4DC to 0x1F4E1, // 📜-📡
// Money
0x1F4B0 to 0x1F4BA, // 💰-💺
// Mail
0x1F4E4 to 0x1F4F0, // 📤-📰
// Writing
0x270F to 0x270F, // ✏
0x1F4DD to 0x1F4DF, // 📝-📟
0x1F58A to 0x1F58D, // 🖊-🖍
// Office
0x1F4C0 to 0x1F4CF, // 💀-📏
0x1F4DB to 0x1F4DB, // 📛
// Lock
0x1F50F to 0x1F513, // 🔏-🔓
// Tool
0x1F527 to 0x1F52F, // 🔧-🔯
0x1F5DC to 0x1F5E3, // 🗜-🗣
0x1F6E0 to 0x1F6E3, // 🛠-🛣
0x1FA9A to 0x1FAAC, // 🪚-🪬
// Household
0x1F6BD to 0x1F6BF, // 🚽-🚿
0x1F6C1 to 0x1F6C1, // 🛁
0x1F9F4 to 0x1F9F5, // 🧴-🧵
0x1F9F0 to 0x1F9F3, // 🧰-🧳
0x1FAA0 to 0x1FAA8, // 🪠-🪨
0x1FAAA to 0x1FAAC, // 🪪-🪬
// Other-Object
0x1F5DE to 0x1F5DE, // 🗞
0x1F4FF to 0x1F4FF // 📿
)),
// ❤️ Symbols
EmojiCategory("Symbols", "Символы", Icons.Default.Favorite, listOf(
0x2764 to 0x2764,
0x1F490 to 0x1F49F,
0x2600 to 0x26FF,
0x2700 to 0x27BF,
0x1F170 to 0x1F1FF,
0x00A9 to 0x00AE,
0x203C to 0x3299
// Heart
0x2764 to 0x2764, // ❤
0x1F493 to 0x1F49F, // 💓-💟
0x1F90D to 0x1F90F, // 🤍-🤏
0x2763 to 0x2763, // ❣
0x1FA75 to 0x1FA77, // 🩵-🩷
// Emotion
0x1F4A2 to 0x1F4AF, // 💢-💯
0x1F573 to 0x1F576, // 🕳-🕶
// Zodiac
0x2648 to 0x2653, // ♈-♓
// Av-Symbol
0x1F500 to 0x1F506, // 🔀-🔆
0x1F514 to 0x1F526, // 🔔-🔦
0x1F530 to 0x1F53D, // 🔰-🔽
// Geometric
0x26AA to 0x26AB, // ⚪-⚫
0x1F534 to 0x1F53A, // 🔴-🔺
// Other-Symbol
0x2600 to 0x2604, // ☀-☄
0x2614 to 0x2615, // ☔-☕
0x267F to 0x267F, // ♿
0x2695 to 0x269C, // ⚕-⚜
0x2700 to 0x27BF, // ✀-➿
0x00A9 to 0x00AE, // ©-®
0x203C to 0x203C, // ‼
0x2049 to 0x2049, // ⁉
0x2122 to 0x2122, // ™
0x2139 to 0x2139, //
0x2194 to 0x21AA, // ↔-↪
0x231A to 0x231B, // ⌚-⌛
0x23E9 to 0x23F3, // ⏩-⏳
0x23F8 to 0x23FA, // ⏸-⏺
0x25AA to 0x25FE, // ▪-◾
0x2611 to 0x2612, // ☑-☒
0x2714 to 0x2716, // ✔-✖
0x274C to 0x274E, // ❌-❎
0x2753 to 0x2757 // ❓-❗
)),
// 🏳️ Flags
EmojiCategory("Flags", "Флаги", Icons.Default.Flag, listOf(
0x1F1E0 to 0x1F1FF,
0x1F3F3 to 0x1F3F4,
0x1F3C1 to 0x1F3C1,
0x1F6A9 to 0x1F6A9
// Flag
0x1F3C1 to 0x1F3C1, // 🏁
0x1F6A9 to 0x1F6A9, // 🚩
0x1F3F3 to 0x1F3F4, // 🏳-🏴
// Country-Flag (Regional Indicator Symbols)
0x1F1E0 to 0x1F1FF // 🇦-🇿 (country flags)
))
)
@@ -193,28 +415,60 @@ object EmojiCache {
return emojisByCategory?.get(categoryKey) ?: emptyList()
}
/**
* Получает индекс emoji согласно Unicode порядку в рамках категории
*/
private fun getEmojiSortIndex(emoji: String, category: EmojiCategory): Int {
val unified = emoji.lowercase().split("-").firstOrNull() ?: return Int.MAX_VALUE
val codePoint = try { unified.toInt(16) } catch (e: Exception) { return Int.MAX_VALUE }
// Находим индекс диапазона, в который попадает emoji
for ((rangeIndex, range) in category.ranges.withIndex()) {
val (start, end) = range
if (codePoint in start..end) {
// Возвращаем составной индекс: номер диапазона * 100000 + позиция внутри диапазона
return rangeIndex * 100000 + (codePoint - start)
}
}
return Int.MAX_VALUE
}
private fun groupEmojis(allEmojis: List<String>): Map<String, List<String>> {
val result = mutableMapOf<String, MutableList<String>>()
val usedEmojis = mutableSetOf<String>()
val emojiToCategory = mutableMapOf<String, EmojiCategory>()
EMOJI_CATEGORIES.forEach { category ->
result[category.key] = mutableListOf()
}
// Сначала определяем категорию для каждого emoji
for (emoji in allEmojis) {
for (category in EMOJI_CATEGORIES) {
if (category.key == "All") continue
if (emojiMatchesCategory(emoji, category) && emoji !in usedEmojis) {
result[category.key]?.add(emoji)
usedEmojis.add(emoji)
emojiToCategory[emoji] = category
break
}
}
}
// Нераспределенные emoji идут в Symbols
for (emoji in allEmojis) {
if (emoji !in usedEmojis) {
result["Symbols"]?.add(emoji)
emojiToCategory[emoji] = EMOJI_CATEGORIES.find { it.key == "Symbols" }!!
}
}
// Сортируем каждую категорию согласно Unicode порядку
for ((key, emojis) in result) {
val category = EMOJI_CATEGORIES.find { it.key == key }
if (category != null) {
emojis.sortWith { a, b ->
getEmojiSortIndex(a, category).compareTo(getEmojiSortIndex(b, category))
}
}
}