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 0cbbbc1..117873b 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 @@ -119,6 +119,14 @@ private val TelegramSendIcon: ImageVector } .build() +/** Данные цитируемого сообщения */ +data class ReplyData( + val messageId: String, + val senderName: String, // Имя отправителя цитируемого сообщения + val text: String, + val isFromMe: Boolean // Цитируемое сообщение от меня? +) + /** Модель сообщения (Legacy - для совместимости) */ data class ChatMessage( val id: String, @@ -126,7 +134,8 @@ data class ChatMessage( val isOutgoing: Boolean, val timestamp: Date, val status: MessageStatus = MessageStatus.SENT, - val showDateHeader: Boolean = false // Показывать ли разделитель даты + val showDateHeader: Boolean = false, // Показывать ли разделитель даты + val replyData: ReplyData? = null // Данные цитируемого сообщения ) enum class MessageStatus { @@ -1434,6 +1443,16 @@ private fun MessageBubble( .padding(horizontal = 12.dp, vertical = 7.dp) ) { Column { + // 🔥 Reply bubble (цитата) - как в React Native + message.replyData?.let { reply -> + ReplyBubble( + replyData = reply, + isOutgoing = message.isOutgoing, + isDarkTheme = isDarkTheme + ) + Spacer(modifier = Modifier.height(6.dp)) + } + AppleEmojiText(text = message.text, color = textColor, fontSize = 16.sp) Spacer(modifier = Modifier.height(2.dp)) Row( @@ -1517,6 +1536,83 @@ private fun AnimatedMessageStatus( } } +/** + * 🔥 Reply bubble (цитата) внутри сообщения - как в React Native + * Стиль: вертикальная линия слева + имя + текст + */ +@Composable +private fun ReplyBubble( + replyData: ReplyData, + isOutgoing: Boolean, + isDarkTheme: Boolean +) { + // Цвета как в React Native + val backgroundColor = if (isOutgoing) { + Color.Black.copy(alpha = 0.15f) + } else { + Color.Black.copy(alpha = 0.08f) + } + + val borderColor = if (isOutgoing) { + Color.White + } else { + PrimaryBlue + } + + val nameColor = if (isOutgoing) { + Color.White + } else { + PrimaryBlue + } + + val textColor = if (isOutgoing) { + Color.White.copy(alpha = 0.85f) + } else { + if (isDarkTheme) Color.White else Color.Black + } + + Row( + modifier = Modifier + .fillMaxWidth() + .height(IntrinsicSize.Min) + .clip(RoundedCornerShape(4.dp)) + .background(backgroundColor) + ) { + // Вертикальная линия слева (как borderLeft в React Native) + Box( + modifier = Modifier + .width(3.dp) + .fillMaxHeight() + .background(borderColor) + ) + + // Контент + Column( + modifier = Modifier + .padding(start = 10.dp, end = 10.dp, top = 6.dp, bottom = 6.dp) + ) { + // Имя отправителя цитируемого сообщения + Text( + text = replyData.senderName, + color = nameColor, + fontSize = 14.sp, + fontWeight = FontWeight.SemiBold, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + + // Текст цитируемого сообщения + Text( + text = replyData.text.ifEmpty { "..." }, + color = textColor, + fontSize = 14.sp, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + } +} + /** 🚀 Разделитель даты с fade-in анимацией */ @Composable private fun DateHeader(dateText: String, secondaryTextColor: Color) { 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 4101567..46a3ac0 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 @@ -12,6 +12,8 @@ import com.rosetta.messenger.database.RosettaDatabase import com.rosetta.messenger.network.* import kotlinx.coroutines.* import kotlinx.coroutines.flow.* +import org.json.JSONArray +import org.json.JSONObject import java.util.UUID import java.util.Date import java.util.concurrent.ConcurrentHashMap @@ -419,6 +421,9 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { * 🔥 Быстрая конвертация Entity -> ChatMessage */ private fun entityToChatMessage(entity: MessageEntity): ChatMessage { + // Парсим attachments для поиска MESSAGES (цитата) + val replyData = parseReplyFromAttachments(entity.attachments, entity.fromMe == 1) + return ChatMessage( id = entity.messageId, text = entity.plainMessage, // Уже расшифровано при сохранении @@ -430,10 +435,59 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { 2 -> MessageStatus.SENT // Changed from ERROR to SENT 3 -> MessageStatus.READ else -> MessageStatus.SENT - } + }, + replyData = replyData ) } + /** + * Парсинг MESSAGES attachment для извлечения данных цитаты + * Формат: [{"message_id": "...", "publicKey": "...", "message": "..."}] + */ + private fun parseReplyFromAttachments(attachmentsJson: String, isFromMe: Boolean): ReplyData? { + if (attachmentsJson.isEmpty() || attachmentsJson == "[]") return null + + return try { + val attachments = JSONArray(attachmentsJson) + for (i in 0 until attachments.length()) { + val attachment = attachments.getJSONObject(i) + val type = attachment.optInt("type", 0) + + // MESSAGES = 1 (цитата) + if (type == 1) { + // Данные могут быть в blob или preview + val dataJson = attachment.optString("blob", "").ifEmpty { + attachment.optString("preview", "") + } + if (dataJson.isEmpty()) continue + + val messagesArray = JSONArray(dataJson) + if (messagesArray.length() > 0) { + val replyMessage = messagesArray.getJSONObject(0) + val replyPublicKey = replyMessage.optString("publicKey", "") + val replyText = replyMessage.optString("message", "") + val replyMessageId = replyMessage.optString("message_id", "") + + // Определяем, кто автор цитируемого сообщения + // Если publicKey == myPublicKey - цитата от меня + val isReplyFromMe = replyPublicKey == myPublicKey + + return ReplyData( + messageId = replyMessageId, + senderName = if (isReplyFromMe) "You" else opponentTitle.ifEmpty { opponentUsername.ifEmpty { "User" } }, + text = replyText, + isFromMe = isReplyFromMe + ) + } + } + } + null + } catch (e: Exception) { + Log.e(TAG, "Error parsing reply from attachments", e) + null + } + } + /** * Получить ключ диалога для группировки сообщений */