diff --git a/app/src/main/java/com/rosetta/messenger/network/Protocol.kt b/app/src/main/java/com/rosetta/messenger/network/Protocol.kt index bfae0a3..7db47d4 100644 --- a/app/src/main/java/com/rosetta/messenger/network/Protocol.kt +++ b/app/src/main/java/com/rosetta/messenger/network/Protocol.kt @@ -76,7 +76,11 @@ class Protocol( 0x02 to { PacketResult() }, 0x03 to { PacketSearch() }, 0x04 to { PacketOnlineSubscribe() }, - 0x05 to { PacketOnlineState() } + 0x05 to { PacketOnlineState() }, + 0x06 to { PacketMessage() }, + 0x07 to { PacketRead() }, + 0x08 to { PacketDelivery() }, + 0x0B to { PacketTyping() } ) init { diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt index 00e3de0..b19abbc 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt @@ -49,6 +49,7 @@ import com.rosetta.messenger.ui.onboarding.PrimaryBlue import com.rosetta.messenger.ui.components.VerifiedBadge import com.rosetta.messenger.ui.components.AppleEmojiPickerPanel import com.rosetta.messenger.ui.components.AppleEmojiTextField +import com.rosetta.messenger.ui.components.AppleEmojiText import androidx.compose.ui.text.font.FontFamily import kotlinx.coroutines.launch import java.text.SimpleDateFormat @@ -421,7 +422,7 @@ private fun MessageBubble( .padding(horizontal = 12.dp, vertical = 8.dp) ) { Column { - Text( + AppleEmojiText( text = message.text, color = textColor, fontSize = 15.sp diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatViewModel.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatViewModel.kt index 70cdbfa..9e6583d 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatViewModel.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatViewModel.kt @@ -121,9 +121,13 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { * Открыть диалог */ fun openDialog(publicKey: String) { + if (opponentKey == publicKey) { + ProtocolManager.addLog("💬 Dialog already open: ${publicKey.take(16)}...") + return + } opponentKey = publicKey _messages.value = emptyList() - ProtocolManager.addLog("💬 Dialog: ${publicKey.take(16)}...") + ProtocolManager.addLog("💬 Dialog opened: ${publicKey.take(16)}...") } /** @@ -176,6 +180,7 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { _inputText.value = "" ProtocolManager.addLog("📤 Send: \"${text.take(20)}...\"") + ProtocolManager.addLog("📋 Messages count: ${_messages.value.size}") // 2. Отправка в фоне viewModelScope.launch { 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 4f9d95a..5669a3a 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 @@ -33,8 +33,8 @@ class AppleEmojiEditTextView @JvmOverloads constructor( private var isUpdating = false companion object { - // Regex для эмодзи - private val EMOJI_PATTERN = Pattern.compile( + // Regex для эмодзи - public для доступа из других компонентов + val EMOJI_PATTERN = Pattern.compile( "[\\x{1F600}-\\x{1F64F}]|" + // Emoticons "[\\x{1F300}-\\x{1F5FF}]|" + // Misc Symbols and Pictographs "[\\x{1F680}-\\x{1F6FF}]|" + // Transport and Map @@ -217,3 +217,93 @@ fun AppleEmojiTextField( modifier = modifier ) } + +/** + * TextView с Apple эмодзи (для отображения, не редактирования) + */ +@Composable +fun AppleEmojiText( + text: String, + modifier: Modifier = Modifier, + color: androidx.compose.ui.graphics.Color = androidx.compose.ui.graphics.Color.White, + fontSize: androidx.compose.ui.unit.TextUnit = androidx.compose.ui.unit.TextUnit.Unspecified +) { + val fontSizeValue = if (fontSize == androidx.compose.ui.unit.TextUnit.Unspecified) 15f + else fontSize.value + + AndroidView( + factory = { ctx -> + AppleEmojiTextView(ctx).apply { + setTextColor(color.toArgb()) + setTextSize(fontSizeValue) + } + }, + update = { view -> + view.setTextWithEmojis(text) + view.setTextColor(color.toArgb()) + }, + modifier = modifier + ) +} + +/** + * Apple Emoji TextView - для отображения текста с PNG эмодзи + */ +class AppleEmojiTextView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = android.R.attr.textViewStyle +) : android.widget.TextView(context, attrs, defStyleAttr) { + + companion object { + private val EMOJI_PATTERN = AppleEmojiEditTextView.EMOJI_PATTERN + private val bitmapCache = LruCache(100) + } + + fun setTextWithEmojis(text: String) { + val spannable = SpannableStringBuilder(text) + val matcher = EMOJI_PATTERN.matcher(text) + + while (matcher.find()) { + val emoji = matcher.group() + val unified = emojiToUnified(emoji) + val bitmap = loadEmojiBitmap(unified) + + if (bitmap != null) { + val size = (textSize * 1.2).toInt() + val scaledBitmap = Bitmap.createScaledBitmap(bitmap, size, size, true) + val drawable = BitmapDrawable(resources, scaledBitmap) + drawable.setBounds(0, 0, size, size) + + val span = ImageSpan(drawable, ImageSpan.ALIGN_BASELINE) + spannable.setSpan(span, matcher.start(), matcher.end(), + android.text.Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + } + } + + setText(spannable) + } + + private fun loadEmojiBitmap(unified: String): Bitmap? { + bitmapCache.get(unified)?.let { return it } + + return try { + val path = "emoji/$unified.png" + val inputStream = context.assets.open(path) + val bitmap = BitmapFactory.decodeStream(inputStream) + inputStream.close() + bitmap?.let { bitmapCache.put(unified, it) } + bitmap + } catch (e: Exception) { + null + } + } + + private fun emojiToUnified(emoji: String): String { + return emoji.codePoints() + .filter { it != 0xFE0F } + .mapToObj { String.format("%04x", it) } + .toList() + .joinToString("-") + } +}