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() },
|
0x02 to { PacketResult() },
|
||||||
0x03 to { PacketSearch() },
|
0x03 to { PacketSearch() },
|
||||||
0x04 to { PacketOnlineSubscribe() },
|
0x04 to { PacketOnlineSubscribe() },
|
||||||
0x05 to { PacketOnlineState() }
|
0x05 to { PacketOnlineState() },
|
||||||
|
0x06 to { PacketMessage() },
|
||||||
|
0x07 to { PacketRead() },
|
||||||
|
0x08 to { PacketDelivery() },
|
||||||
|
0x0B to { PacketTyping() }
|
||||||
)
|
)
|
||||||
|
|
||||||
init {
|
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.VerifiedBadge
|
||||||
import com.rosetta.messenger.ui.components.AppleEmojiPickerPanel
|
import com.rosetta.messenger.ui.components.AppleEmojiPickerPanel
|
||||||
import com.rosetta.messenger.ui.components.AppleEmojiTextField
|
import com.rosetta.messenger.ui.components.AppleEmojiTextField
|
||||||
|
import com.rosetta.messenger.ui.components.AppleEmojiText
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
@@ -421,7 +422,7 @@ private fun MessageBubble(
|
|||||||
.padding(horizontal = 12.dp, vertical = 8.dp)
|
.padding(horizontal = 12.dp, vertical = 8.dp)
|
||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
Text(
|
AppleEmojiText(
|
||||||
text = message.text,
|
text = message.text,
|
||||||
color = textColor,
|
color = textColor,
|
||||||
fontSize = 15.sp
|
fontSize = 15.sp
|
||||||
|
|||||||
@@ -121,9 +121,13 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
* Открыть диалог
|
* Открыть диалог
|
||||||
*/
|
*/
|
||||||
fun openDialog(publicKey: String) {
|
fun openDialog(publicKey: String) {
|
||||||
|
if (opponentKey == publicKey) {
|
||||||
|
ProtocolManager.addLog("💬 Dialog already open: ${publicKey.take(16)}...")
|
||||||
|
return
|
||||||
|
}
|
||||||
opponentKey = publicKey
|
opponentKey = publicKey
|
||||||
_messages.value = emptyList()
|
_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 = ""
|
_inputText.value = ""
|
||||||
|
|
||||||
ProtocolManager.addLog("📤 Send: \"${text.take(20)}...\"")
|
ProtocolManager.addLog("📤 Send: \"${text.take(20)}...\"")
|
||||||
|
ProtocolManager.addLog("📋 Messages count: ${_messages.value.size}")
|
||||||
|
|
||||||
// 2. Отправка в фоне
|
// 2. Отправка в фоне
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ class AppleEmojiEditTextView @JvmOverloads constructor(
|
|||||||
private var isUpdating = false
|
private var isUpdating = false
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
// Regex для эмодзи
|
// Regex для эмодзи - public для доступа из других компонентов
|
||||||
private val EMOJI_PATTERN = Pattern.compile(
|
val EMOJI_PATTERN = Pattern.compile(
|
||||||
"[\\x{1F600}-\\x{1F64F}]|" + // Emoticons
|
"[\\x{1F600}-\\x{1F64F}]|" + // Emoticons
|
||||||
"[\\x{1F300}-\\x{1F5FF}]|" + // Misc Symbols and Pictographs
|
"[\\x{1F300}-\\x{1F5FF}]|" + // Misc Symbols and Pictographs
|
||||||
"[\\x{1F680}-\\x{1F6FF}]|" + // Transport and Map
|
"[\\x{1F680}-\\x{1F6FF}]|" + // Transport and Map
|
||||||
@@ -217,3 +217,93 @@ fun AppleEmojiTextField(
|
|||||||
modifier = modifier
|
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