feat: Enhance ForwardManager and ChatViewModel for improved message handling and logging; adjust ForwardChatPickerBottomSheet height for better UX

This commit is contained in:
k1ngsterr1
2026-01-16 03:38:33 +05:00
parent 81d2b744ba
commit fbab2d0f80
3 changed files with 144 additions and 43 deletions

View File

@@ -103,6 +103,22 @@ object ForwardManager {
*/ */
fun hasForwardMessagesForChat(publicKey: String): Boolean { fun hasForwardMessagesForChat(publicKey: String): Boolean {
val selectedKey = _selectedChatPublicKey.value val selectedKey = _selectedChatPublicKey.value
return selectedKey == publicKey && _forwardMessages.value.isNotEmpty() val hasMessages = _forwardMessages.value.isNotEmpty()
android.util.Log.d("ForwardManager", "📨 hasForwardMessagesForChat($publicKey): selectedKey=$selectedKey, hasMessages=$hasMessages")
return selectedKey == publicKey && hasMessages
}
/**
* Установить выбранный чат и вернуть сообщения для него
* Комбинированный метод для атомарного получения данных
*/
fun getForwardMessagesForChat(publicKey: String): List<ForwardMessage> {
val selectedKey = _selectedChatPublicKey.value
return if (selectedKey == publicKey && _forwardMessages.value.isNotEmpty()) {
android.util.Log.d("ForwardManager", "📨 getForwardMessagesForChat: returning ${_forwardMessages.value.size} messages")
_forwardMessages.value
} else {
emptyList()
}
} }
} }

View File

@@ -355,6 +355,14 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
opponentTitle = title opponentTitle = title
opponentUsername = username opponentUsername = username
// 📨 СНАЧАЛА проверяем ForwardManager - ДО сброса состояния!
// Это важно для правильного отображения forward сообщений сразу
val forwardMessages = ForwardManager.getForwardMessagesForChat(publicKey)
val hasForward = forwardMessages.isNotEmpty()
if (hasForward) {
android.util.Log.d("ChatViewModel", "📨 Will apply ${forwardMessages.size} forward messages")
}
// Сбрасываем состояние // Сбрасываем состояние
_messages.value = emptyList() _messages.value = emptyList()
_opponentOnline.value = false _opponentOnline.value = false
@@ -366,11 +374,9 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
readReceiptSentForCurrentDialog = false readReceiptSentForCurrentDialog = false
isDialogActive = true // 🔥 Диалог активен! isDialogActive = true // 🔥 Диалог активен!
// 📨 Проверяем ForwardManager - если есть сообщения для пересылки в этот чат // 📨 Применяем Forward сообщения СРАЗУ после сброса
if (ForwardManager.hasForwardMessagesForChat(publicKey)) { if (hasForward) {
val forwardMessages = ForwardManager.consumeForwardMessages() android.util.Log.d("ChatViewModel", "📨 Applying ${forwardMessages.size} forward messages NOW")
if (forwardMessages.isNotEmpty()) {
android.util.Log.d("ChatViewModel", "📨 Received ${forwardMessages.size} forward messages")
// Конвертируем ForwardMessage в ReplyMessage // Конвертируем ForwardMessage в ReplyMessage
_replyMessages.value = forwardMessages.map { fm -> _replyMessages.value = forwardMessages.map { fm ->
ReplyMessage( ReplyMessage(
@@ -382,9 +388,13 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
) )
} }
_isForwardMode.value = true _isForwardMode.value = true
android.util.Log.d("ChatViewModel", "📨 isForwardMode set to TRUE, replyMessages.size = ${_replyMessages.value.size}")
// Очищаем ForwardManager после применения // Очищаем ForwardManager после применения
ForwardManager.clear() ForwardManager.clear()
} } else {
// Сбрасываем forward state если нет forward сообщений
_replyMessages.value = emptyList()
_isForwardMode.value = false
} }
// Подписываемся на онлайн статус // Подписываемся на онлайн статус
@@ -608,6 +618,11 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
* Как в архиве: расшифровываем при каждой загрузке * Как в архиве: расшифровываем при каждой загрузке
*/ */
private suspend fun entityToChatMessage(entity: MessageEntity): ChatMessage { private suspend fun entityToChatMessage(entity: MessageEntity): ChatMessage {
android.util.Log.d("ReplyDebug", "🔄 [DB LOAD] entityToChatMessage called")
android.util.Log.d("ReplyDebug", " - messageId: ${entity.messageId}")
android.util.Log.d("ReplyDebug", " - attachments: ${entity.attachments}")
android.util.Log.d("ReplyDebug", " - attachments length: ${entity.attachments.length}")
// Расшифровываем сообщение из content + chachaKey // Расшифровываем сообщение из content + chachaKey
var displayText = try { var displayText = try {
val privateKey = myPrivateKey val privateKey = myPrivateKey
@@ -645,24 +660,36 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
} }
} }
android.util.Log.d("ReplyDebug", " - Decrypted text: ${displayText.take(50)}")
// Парсим attachments для поиска MESSAGES (цитата) // Парсим attachments для поиска MESSAGES (цитата)
// 🔥 ВАЖНО: Передаем content и chachaKey для расшифровки reply blob если нужно // 🔥 ВАЖНО: Передаем content и chachaKey для расшифровки reply blob если нужно
android.util.Log.d("ReplyDebug", " - Calling parseReplyFromAttachments...")
var replyData = parseReplyFromAttachments( var replyData = parseReplyFromAttachments(
attachmentsJson = entity.attachments, attachmentsJson = entity.attachments,
isFromMe = entity.fromMe == 1, isFromMe = entity.fromMe == 1,
content = entity.content, content = entity.content,
chachaKey = entity.chachaKey chachaKey = entity.chachaKey
) )
android.util.Log.d("ReplyDebug", " - parseReplyFromAttachments returned: ${if (replyData != null) "NOT NULL" else "NULL"}")
// Если не нашли reply в attachments, пробуем распарсить из текста // Если не нашли reply в attachments, пробуем распарсить из текста
if (replyData == null) { if (replyData == null) {
android.util.Log.d("ReplyDebug", " - Reply is null, trying to parse from text...")
val parseResult = parseReplyFromText(displayText) val parseResult = parseReplyFromText(displayText)
if (parseResult != null) { if (parseResult != null) {
android.util.Log.d("ReplyDebug", " - ✅ Parsed reply from text")
replyData = parseResult.first replyData = parseResult.first
displayText = parseResult.second displayText = parseResult.second
} else {
android.util.Log.d("ReplyDebug", " - ❌ Failed to parse reply from text")
} }
} else {
android.util.Log.d("ReplyDebug", " - ✅ Reply found in attachments!")
} }
android.util.Log.d("ReplyDebug", " - Creating ChatMessage with replyData: ${if (replyData != null) "NOT NULL" else "NULL"}")
return ChatMessage( return ChatMessage(
id = entity.messageId, id = entity.messageId,
text = displayText, text = displayText,
@@ -714,74 +741,132 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
content: String, content: String,
chachaKey: String chachaKey: String
): ReplyData? { ): ReplyData? {
if (attachmentsJson.isEmpty() || attachmentsJson == "[]") return null android.util.Log.d("ReplyDebug", "🔍 [DB LOAD] parseReplyFromAttachments called")
android.util.Log.d("ReplyDebug", " - attachmentsJson.isEmpty(): ${attachmentsJson.isEmpty()}")
android.util.Log.d("ReplyDebug", " - attachmentsJson == '[]': ${attachmentsJson == "[]"}")
if (attachmentsJson.isEmpty() || attachmentsJson == "[]") {
android.util.Log.d("ReplyDebug", " - Early return: attachments empty or []")
return null
}
return try { return try {
android.util.Log.d("ReplyDebug", "💾 [DB] Parsing reply from attachments JSON") android.util.Log.d("ReplyDebug", "💾 [DB LOAD] Parsing reply from attachments JSON")
android.util.Log.d("ReplyDebug", " - Attachments JSON preview: ${attachmentsJson.take(200)}") android.util.Log.d("ReplyDebug", " - Full JSON: $attachmentsJson")
android.util.Log.d("ReplyDebug", " - JSON length: ${attachmentsJson.length}")
val attachments = JSONArray(attachmentsJson) val attachments = JSONArray(attachmentsJson)
android.util.Log.d("ReplyDebug", " - Attachments count: ${attachments.length()}") android.util.Log.d("ReplyDebug", " - Attachments count: ${attachments.length()}")
for (i in 0 until attachments.length()) { for (i in 0 until attachments.length()) {
android.util.Log.d("ReplyDebug", " - Processing attachment $i...")
val attachment = attachments.getJSONObject(i) val attachment = attachments.getJSONObject(i)
val type = attachment.optInt("type", 0) val type = attachment.optInt("type", 0)
android.util.Log.d("ReplyDebug", " - Attachment $i type: $type") android.util.Log.d("ReplyDebug", " - type: $type")
// MESSAGES = 1 (цитата) // MESSAGES = 1 (цитата)
if (type == 1) { if (type == 1) {
android.util.Log.d("ReplyDebug", " - ✅ Found MESSAGES type!")
// Данные могут быть в blob или preview // Данные могут быть в blob или preview
var dataJson = attachment.optString("blob", "").ifEmpty { var dataJson = attachment.optString("blob", "")
attachment.optString("preview", "") android.util.Log.d("ReplyDebug", " - blob from JSON: ${if (dataJson.isEmpty()) "EMPTY" else "length=${dataJson.length}"}")
if (dataJson.isEmpty()) {
dataJson = attachment.optString("preview", "")
android.util.Log.d("ReplyDebug", " - blob was empty, trying preview: ${if (dataJson.isEmpty()) "ALSO EMPTY" else "length=${dataJson.length}"}")
}
android.util.Log.d("ReplyDebug", " - Final data length: ${dataJson.length}")
android.util.Log.d("ReplyDebug", " - Data preview (first 200): ${dataJson.take(200)}")
if (dataJson.isEmpty()) {
android.util.Log.e("ReplyDebug", " - ❌ Data is empty, skipping")
continue
} }
android.util.Log.d("ReplyDebug", " - Found MESSAGES attachment, data length: ${dataJson.length}")
android.util.Log.d("ReplyDebug", " - Data preview: ${dataJson.take(200)}")
if (dataJson.isEmpty()) continue
// 🔥 Проверяем формат blob - если содержит ":", то это зашифрованный формат "iv:ciphertext" // 🔥 Проверяем формат blob - если содержит ":", то это зашифрованный формат "iv:ciphertext"
// Расшифровываем с приватным ключом (как в Desktop Архиве) val colonCount = dataJson.count { it == ':' }
android.util.Log.d("ReplyDebug", " - Colon count in data: $colonCount")
if (dataJson.contains(":") && dataJson.split(":").size == 2) { if (dataJson.contains(":") && dataJson.split(":").size == 2) {
android.util.Log.d("ReplyDebug", " - Blob is encrypted, decrypting with private key...") android.util.Log.d("ReplyDebug", " - 🔒 Blob is ENCRYPTED (iv:ciphertext format)")
android.util.Log.d("ReplyDebug", " - Attempting to decrypt with private key...")
val privateKey = myPrivateKey val privateKey = myPrivateKey
android.util.Log.d("ReplyDebug", " - Private key available: ${privateKey != null}")
if (privateKey != null) { if (privateKey != null) {
android.util.Log.d("ReplyDebug", " - Private key length: ${privateKey.length}")
try { try {
dataJson = CryptoManager.decryptWithPassword(dataJson, privateKey) ?: dataJson val decrypted = CryptoManager.decryptWithPassword(dataJson, privateKey)
android.util.Log.d("ReplyDebug", " - Decrypted successfully, length: ${dataJson.length}") android.util.Log.d("ReplyDebug", " - Decryption result: ${if (decrypted != null) "SUCCESS" else "NULL"}")
if (decrypted != null) {
android.util.Log.d("ReplyDebug", " - Decrypted length: ${decrypted.length}")
android.util.Log.d("ReplyDebug", " - Decrypted preview: ${decrypted.take(200)}")
dataJson = decrypted
} else {
android.util.Log.e("ReplyDebug", " - ❌ Decryption returned null")
continue
}
} catch (e: Exception) { } catch (e: Exception) {
android.util.Log.e("ReplyDebug", " - Failed to decrypt blob", e) android.util.Log.e("ReplyDebug", " - ❌ Decryption exception: ${e.message}", e)
android.util.Log.e("ReplyDebug", " - Stack trace:", e)
continue continue
} }
} else { } else {
android.util.Log.e("ReplyDebug", " - Cannot decrypt: private key is null") android.util.Log.e("ReplyDebug", " - ❌ Cannot decrypt: private key is NULL")
continue continue
} }
} else {
android.util.Log.d("ReplyDebug", " - 📄 Blob is PLAINTEXT (not encrypted)")
} }
val messagesArray = JSONArray(dataJson) android.util.Log.d("ReplyDebug", " - Attempting to parse as JSONArray...")
val messagesArray = try {
JSONArray(dataJson)
} catch (e: Exception) {
android.util.Log.e("ReplyDebug", " - ❌ Failed to parse as JSONArray: ${e.message}")
android.util.Log.e("ReplyDebug", " - Data was: $dataJson")
continue
}
android.util.Log.d("ReplyDebug", " - Messages array length: ${messagesArray.length()}")
if (messagesArray.length() > 0) { if (messagesArray.length() > 0) {
android.util.Log.d("ReplyDebug", " - Extracting first message from array...")
val replyMessage = messagesArray.getJSONObject(0) val replyMessage = messagesArray.getJSONObject(0)
val replyPublicKey = replyMessage.optString("publicKey", "") val replyPublicKey = replyMessage.optString("publicKey", "")
val replyText = replyMessage.optString("message", "") val replyText = replyMessage.optString("message", "")
val replyMessageId = replyMessage.optString("message_id", "") val replyMessageId = replyMessage.optString("message_id", "")
// Определяем, кто автор цитируемого сообщения android.util.Log.d("ReplyDebug", " - replyMessageId: $replyMessageId")
// Если publicKey == myPublicKey - цитата от меня android.util.Log.d("ReplyDebug", " - replyPublicKey: ${replyPublicKey.take(20)}...")
val isReplyFromMe = replyPublicKey == myPublicKey android.util.Log.d("ReplyDebug", " - replyText: ${replyText.take(50)}")
android.util.Log.d("ReplyDebug", " - myPublicKey: ${myPublicKey?.take(20)}...")
// Определяем, кто автор цитируемого сообщения
val isReplyFromMe = replyPublicKey == myPublicKey
android.util.Log.d("ReplyDebug", " - isReplyFromMe: $isReplyFromMe")
android.util.Log.d("ReplyDebug", " - Parsed from DB: id=$replyMessageId, text=${replyText.take(30)}, isFromMe=$isReplyFromMe")
val result = ReplyData( val result = ReplyData(
messageId = replyMessageId, messageId = replyMessageId,
senderName = if (isReplyFromMe) "You" else opponentTitle.ifEmpty { opponentUsername.ifEmpty { "User" } }, senderName = if (isReplyFromMe) "You" else opponentTitle.ifEmpty { opponentUsername.ifEmpty { "User" } },
text = replyText, text = replyText,
isFromMe = isReplyFromMe isFromMe = isReplyFromMe
) )
android.util.Log.d("ReplyDebug", "✅ [DB] Reply data parsed successfully from DB") android.util.Log.d("ReplyDebug", " - ✅ Created ReplyData: senderName=${result.senderName}, text=${result.text.take(30)}")
android.util.Log.d("ReplyDebug", "✅ [DB LOAD] Reply data parsed successfully from DB - RETURNING")
return result return result
} else {
android.util.Log.e("ReplyDebug", " - ❌ Messages array is empty")
}
} else {
android.util.Log.d("ReplyDebug", " - ⏭️ Skipping attachment: type != 1")
} }
} }
} android.util.Log.d("ReplyDebug", "💾 [DB LOAD] No MESSAGES attachment found")
android.util.Log.d("ReplyDebug", "💾 [DB] No MESSAGES attachment found")
null null
} catch (e: Exception) { } catch (e: Exception) {
android.util.Log.e("ReplyDebug", "❌ [DB] Failed to parse reply from attachments:", e) android.util.Log.e("ReplyDebug", "❌ [DB LOAD] Failed to parse reply from attachments:", e)
null null
} }
} }

View File

@@ -165,7 +165,7 @@ fun ForwardChatPickerBottomSheet(
LazyColumn( LazyColumn(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.heightIn(max = 400.dp) .heightIn(min = 300.dp, max = 400.dp) // 🔥 Минимальная высота для лучшего UX
) { ) {
items(dialogs, key = { it.opponentKey }) { dialog -> items(dialogs, key = { it.opponentKey }) { dialog ->
ForwardDialogItem( ForwardDialogItem(