From 5fdc910b6e5727a8afb249991e0e051c39c5032b Mon Sep 17 00:00:00 2001 From: k1ngsterr1 Date: Fri, 16 Jan 2026 05:19:33 +0500 Subject: [PATCH] feat: Enhance message caching and background refresh logic in ChatViewModel; improve decryption methods with multiple attempts for better reliability --- .../messenger/ui/chats/ChatViewModel.kt | 96 ++++++++++++++----- 1 file changed, 72 insertions(+), 24 deletions(-) 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 ba256fe..0562664 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 @@ -296,6 +296,10 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { ) // Просто добавляем как в архиве: setMessages((prev) => ([...prev, newMessage])) _messages.value = _messages.value + message + + // 🔥 Обновляем кэш чтобы при перезаходе сообщение уже было + val dialogKey = getDialogKey(account, packet.fromPublicKey) + updateDialogCache(dialogKey, message) } // ✅ НЕ сохраняем в БД здесь - это делает MessageRepository.handleIncomingMessage()! @@ -517,8 +521,9 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { } /** - * � Фоновое обновление сообщений из БД (проверка новых) + * 🔥 Фоновое обновление сообщений из БД (проверка новых) * Вызывается когда кэш уже отображён, но нужно проверить есть ли новые сообщения + * 🔥 ВАЖНО: НЕ заменяем все сообщения - только добавляем новые, сохраняя существующие! */ private suspend fun refreshMessagesFromDb( account: String, @@ -529,17 +534,27 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { try { val entities = messageDao.getMessages(account, dialogKey, limit = PAGE_SIZE, offset = 0) - // Если в БД есть новые сообщения - if (entities.size != cachedMessages.size) { - val messages = ArrayList(entities.size) - for (entity in entities.asReversed()) { - messages.add(entityToChatMessage(entity)) + // 🔥 Берём ТЕКУЩЕЕ состояние UI (может уже содержать новые сообщения) + val currentMessages = _messages.value + val existingIds = currentMessages.map { it.id }.toSet() + + // 🔥 Находим только НОВЫЕ сообщения (которых нет в текущем UI) + val newEntities = entities.filter { it.messageId !in existingIds } + + if (newEntities.isNotEmpty()) { + val newMessages = ArrayList(newEntities.size) + for (entity in newEntities) { + newMessages.add(entityToChatMessage(entity)) } + // 🔥 ДОБАВЛЯЕМ новые к текущим, а не заменяем! + // Сортируем по timestamp чтобы новые были в конце + val updatedMessages = (currentMessages + newMessages).sortedBy { it.timestamp } + // Обновляем кэш и UI - dialogMessagesCache[dialogKey] = messages.toList() + dialogMessagesCache[dialogKey] = updatedMessages withContext(Dispatchers.Main.immediate) { - _messages.value = messages + _messages.value = updatedMessages } } @@ -560,9 +575,11 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { */ private fun updateDialogCache(dialogKey: String, newMessage: ChatMessage) { val current = dialogMessagesCache[dialogKey]?.toMutableList() ?: mutableListOf() - // Добавляем в конец (новые сообщения) - current.add(newMessage) - dialogMessagesCache[dialogKey] = current + // 🔥 Проверяем на дубликат по ID + if (current.none { it.id == newMessage.id }) { + current.add(newMessage) + dialogMessagesCache[dialogKey] = current + } } /** @@ -790,30 +807,61 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { if (dataJson.contains(":") && dataJson.split(":").size == 2) { 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 - android.util.Log.d("ReplyDebug", " - Private key available: ${privateKey != null}") + var decryptionSuccess = false + // 🔥 Способ 1: Пробуем расшифровать с приватным ключом (для исходящих сообщений) if (privateKey != null) { - android.util.Log.d("ReplyDebug", " - Private key length: ${privateKey.length}") + android.util.Log.d("ReplyDebug", " - Attempting to decrypt with private key...") try { val decrypted = CryptoManager.decryptWithPassword(dataJson, privateKey) - 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 with private key") android.util.Log.d("ReplyDebug", " - Decrypted preview: ${decrypted.take(200)}") dataJson = decrypted - } else { - android.util.Log.e("ReplyDebug", " - ❌ Decryption returned null") - continue + decryptionSuccess = true } } catch (e: Exception) { - android.util.Log.e("ReplyDebug", " - ❌ Decryption exception: ${e.message}", e) - android.util.Log.e("ReplyDebug", " - Stack trace:", e) - continue + android.util.Log.d("ReplyDebug", " - Private key decryption failed: ${e.message}") } - } else { - android.util.Log.e("ReplyDebug", " - ❌ Cannot decrypt: private key is NULL") + } + + // 🔥 Способ 2: Пробуем расшифровать с ChaCha ключом сообщения (для входящих) + if (!decryptionSuccess && content.isNotEmpty() && chachaKey.isNotEmpty() && privateKey != null) { + android.util.Log.d("ReplyDebug", " - Attempting to decrypt with ChaCha key...") + try { + val decrypted = MessageCrypto.decryptAttachmentBlob(dataJson, chachaKey, privateKey) + if (decrypted != null) { + android.util.Log.d("ReplyDebug", " - ✅ Decrypted with ChaCha key") + android.util.Log.d("ReplyDebug", " - Decrypted preview: ${decrypted.take(200)}") + dataJson = decrypted + decryptionSuccess = true + } + } catch (e: Exception) { + android.util.Log.d("ReplyDebug", " - ChaCha decryption failed: ${e.message}") + } + } + + // 🔥 Способ 3: Пробуем decryptReplyBlob с plainKeyAndNonce + if (!decryptionSuccess && content.isNotEmpty() && chachaKey.isNotEmpty() && privateKey != null) { + android.util.Log.d("ReplyDebug", " - Attempting to decrypt with plainKeyAndNonce...") + try { + val decryptResult = MessageCrypto.decryptIncomingFull(content, chachaKey, privateKey) + val plainKeyAndNonce = decryptResult.plainKeyAndNonce + val decrypted = MessageCrypto.decryptReplyBlob(dataJson, plainKeyAndNonce) + if (decrypted.isNotEmpty()) { + android.util.Log.d("ReplyDebug", " - ✅ Decrypted with plainKeyAndNonce") + android.util.Log.d("ReplyDebug", " - Decrypted preview: ${decrypted.take(200)}") + dataJson = decrypted + decryptionSuccess = true + } + } catch (e: Exception) { + android.util.Log.d("ReplyDebug", " - plainKeyAndNonce decryption failed: ${e.message}") + } + } + + if (!decryptionSuccess) { + android.util.Log.e("ReplyDebug", " - ❌ All decryption methods failed") continue } } else {