feat: Enhance Protocol with additional packet types and integrate AppleEmojiText for improved emoji display
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<String, Bitmap>(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("-")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user