feat: Enhance logging in MessageRepository and ChatsListViewModel for better debugging and flow tracking

This commit is contained in:
k1ngsterr1
2026-01-13 23:28:48 +05:00
parent 14ef342e80
commit 2c173bda26
6 changed files with 232 additions and 94 deletions

View File

@@ -75,23 +75,44 @@ class MessageRepository private constructor(private val context: Context) {
INSTANCE ?: MessageRepository(context.applicationContext).also { INSTANCE = it } INSTANCE ?: MessageRepository(context.applicationContext).also { INSTANCE = it }
} }
} }
/**
* Генерация детерминированного messageId на основе данных сообщения
* Аналог generateRandomKeyFormSeed из Архива
*/
fun generateMessageId(fromPublicKey: String, toPublicKey: String, timestamp: Long): String {
val seed = fromPublicKey + toPublicKey + timestamp.toString()
val hash = java.security.MessageDigest.getInstance("SHA-256")
.digest(seed.toByteArray())
// Берём первые 16 символов hex-представления
return hash.take(8).joinToString("") { String.format("%02x", it) }
}
} }
/** /**
* Инициализация с текущим аккаунтом * Инициализация с текущим аккаунтом
*/ */
fun initialize(publicKey: String, privateKey: String) { fun initialize(publicKey: String, privateKey: String) {
android.util.Log.d("MessageRepository", "🔐 initialize() called with publicKey: ${publicKey.take(16)}...")
currentAccount = publicKey currentAccount = publicKey
currentPrivateKey = privateKey currentPrivateKey = privateKey
// Загрузка диалогов // Загрузка диалогов
scope.launch { scope.launch {
dialogDao.getDialogsFlow(publicKey).collect { entities -> dialogDao.getDialogsFlow(publicKey).collect { entities ->
android.util.Log.d("MessageRepository", "📋 MessageRepository dialogsFlow emitted: ${entities.size} dialogs")
_dialogs.value = entities.map { it.toDialog() } _dialogs.value = entities.map { it.toDialog() }
} }
} }
} }
/**
* Проверка инициализации
*/
fun isInitialized(): Boolean {
return currentAccount != null && currentPrivateKey != null
}
/** /**
* Получить поток сообщений для диалога * Получить поток сообщений для диалога
*/ */
@@ -210,13 +231,39 @@ class MessageRepository private constructor(private val context: Context) {
* Обработка входящего сообщения * Обработка входящего сообщения
*/ */
suspend fun handleIncomingMessage(packet: PacketMessage) { suspend fun handleIncomingMessage(packet: PacketMessage) {
val account = currentAccount ?: return android.util.Log.d("MessageRepository", "═══════════════════════════════════════")
val privateKey = currentPrivateKey ?: return android.util.Log.d("MessageRepository", "📩 handleIncomingMessage START")
android.util.Log.d("MessageRepository", " from: ${packet.fromPublicKey.take(20)}...")
// Проверяем, не дубликат ли // 🔥 Генерируем messageId если он пустой (как в Архиве - generateRandomKeyFormSeed)
if (messageDao.messageExists(account, packet.messageId)) return val messageId = if (packet.messageId.isBlank()) {
generateMessageId(packet.fromPublicKey, packet.toPublicKey, packet.timestamp)
} else {
packet.messageId
}
android.util.Log.d("MessageRepository", " messageId: $messageId (original: ${packet.messageId})")
android.util.Log.d("MessageRepository", " currentAccount: ${currentAccount?.take(20) ?: "NULL"}...")
android.util.Log.d("MessageRepository", " currentPrivateKey: ${if (currentPrivateKey != null) "SET" else "NULL"}")
val account = currentAccount ?: run {
android.util.Log.e("MessageRepository", "❌ ABORT: currentAccount is NULL!")
return
}
val privateKey = currentPrivateKey ?: run {
android.util.Log.e("MessageRepository", "❌ ABORT: currentPrivateKey is NULL!")
return
}
// Проверяем, не дубликат ли (используем сгенерированный messageId)
val isDuplicate = messageDao.messageExists(account, messageId)
android.util.Log.d("MessageRepository", " isDuplicate: $isDuplicate")
if (isDuplicate) {
android.util.Log.d("MessageRepository", "⚠️ Skipping duplicate message")
return
}
val dialogKey = getDialogKey(packet.fromPublicKey) val dialogKey = getDialogKey(packet.fromPublicKey)
android.util.Log.d("MessageRepository", " dialogKey: $dialogKey")
try { try {
// Расшифровываем // Расшифровываем
@@ -247,21 +294,25 @@ class MessageRepository private constructor(private val context: Context) {
read = 0, read = 0,
fromMe = 0, fromMe = 0,
delivered = DeliveryStatus.DELIVERED.value, delivered = DeliveryStatus.DELIVERED.value,
messageId = packet.messageId, messageId = messageId, // 🔥 Используем сгенерированный messageId!
plainMessage = encryptedPlainMessage, // 🔒 Зашифрованный текст plainMessage = encryptedPlainMessage, // 🔒 Зашифрованный текст
attachments = attachmentsJson, attachments = attachmentsJson,
dialogKey = dialogKey dialogKey = dialogKey
) )
messageDao.insertMessage(entity) messageDao.insertMessage(entity)
android.util.Log.d("MessageRepository", "✅ Message saved to DB: ${packet.messageId.take(16)}...")
// Обновляем диалог // Обновляем диалог
android.util.Log.d("MessageRepository", "🔄 Calling updateDialog for ${packet.fromPublicKey.take(16)}...")
updateDialog(packet.fromPublicKey, plainText, packet.timestamp, incrementUnread = true) updateDialog(packet.fromPublicKey, plainText, packet.timestamp, incrementUnread = true)
android.util.Log.d("MessageRepository", "✅ updateDialog completed!")
// Обновляем кэш // Обновляем кэш
val message = entity.toMessage() val message = entity.toMessage()
updateMessageCache(dialogKey, message) updateMessageCache(dialogKey, message)
} catch (e: Exception) { } catch (e: Exception) {
android.util.Log.e("MessageRepository", "❌ Error handling incoming message", e)
e.printStackTrace() e.printStackTrace()
} }
} }
@@ -301,13 +352,20 @@ class MessageRepository private constructor(private val context: Context) {
/** /**
* Отметить диалог как прочитанный * Отметить диалог как прочитанный
* 🔥 После обновления messages обновляем диалог через updateDialogFromMessages
*/ */
suspend fun markDialogAsRead(opponentKey: String) { suspend fun markDialogAsRead(opponentKey: String) {
val account = currentAccount ?: return val account = currentAccount ?: return
val dialogKey = getDialogKey(opponentKey) val dialogKey = getDialogKey(opponentKey)
// Отмечаем сообщения как прочитанные
messageDao.markDialogAsRead(account, dialogKey) messageDao.markDialogAsRead(account, dialogKey)
dialogDao.clearUnreadCount(account, opponentKey)
// 🔥 КРИТИЧНО: Пересчитываем счетчики из таблицы messages
// чтобы unread_count обновился моментально
dialogDao.updateDialogFromMessages(account, opponentKey)
android.util.Log.d("MessageRepository", "✅ Dialog marked as read and updated from messages")
} }
/** /**
@@ -392,22 +450,43 @@ class MessageRepository private constructor(private val context: Context) {
incrementUnread: Boolean = false incrementUnread: Boolean = false
) { ) {
val account = currentAccount ?: return val account = currentAccount ?: return
val privateKey = currentPrivateKey ?: return
android.util.Log.d("MessageRepository", "📝 Updating dialog for ${opponentKey.take(16)}...")
android.util.Log.d("MessageRepository", " lastMessage: ${lastMessage.take(50)}")
try {
// 🔥 КРИТИЧНО: Сначала считаем реальное количество непрочитанных из messages
val unreadCount = messageDao.getUnreadCountForDialog(account, opponentKey)
android.util.Log.d("MessageRepository", " unreadCount from messages: $unreadCount")
// 🔒 Шифруем lastMessage
val encryptedLastMessage = CryptoManager.encryptWithPassword(lastMessage, privateKey)
// Проверяем существует ли диалог
val existing = dialogDao.getDialog(account, opponentKey) val existing = dialogDao.getDialog(account, opponentKey)
if (existing != null) { if (existing != null) {
dialogDao.updateLastMessage(account, opponentKey, lastMessage, timestamp) // Обновляем существующий диалог
if (incrementUnread) { android.util.Log.d("MessageRepository", " ✏️ Updating existing dialog...")
dialogDao.incrementUnreadCount(account, opponentKey) dialogDao.updateLastMessage(account, opponentKey, encryptedLastMessage, timestamp)
} dialogDao.updateUnreadCount(account, opponentKey, unreadCount)
} else { } else {
// Создаем новый диалог
android.util.Log.d("MessageRepository", " Creating new dialog...")
dialogDao.insertDialog(DialogEntity( dialogDao.insertDialog(DialogEntity(
account = account, account = account,
opponentKey = opponentKey, opponentKey = opponentKey,
lastMessage = lastMessage, lastMessage = encryptedLastMessage,
lastMessageTimestamp = timestamp, lastMessageTimestamp = timestamp,
unreadCount = if (incrementUnread) 1 else 0 unreadCount = unreadCount
)) ))
} }
android.util.Log.d("MessageRepository", " ✅ Dialog updated successfully!")
} catch (e: Exception) {
android.util.Log.e("MessageRepository", " ❌ Error updating dialog", e)
}
} }
/** /**

View File

@@ -198,6 +198,19 @@ interface MessageDao {
@Query("DELETE FROM messages WHERE account = :account AND message_id = :messageId") @Query("DELETE FROM messages WHERE account = :account AND message_id = :messageId")
suspend fun deleteMessage(account: String, messageId: String) suspend fun deleteMessage(account: String, messageId: String)
/**
* Получить количество непрочитанных сообщений для диалога
* Считает только входящие сообщения (from_me = 0) которые не прочитаны (read = 0)
*/
@Query("""
SELECT COUNT(*) FROM messages
WHERE account = :account
AND from_public_key = :opponentKey
AND from_me = 0
AND read = 0
""")
suspend fun getUnreadCountForDialog(account: String, opponentKey: String): Int
/** /**
* Удалить все сообщения диалога * Удалить все сообщения диалога
*/ */
@@ -345,8 +358,74 @@ interface DialogDao {
fun getTotalUnreadCountExcludingFlow(account: String, excludeOpponentKey: String): Flow<Int> fun getTotalUnreadCountExcludingFlow(account: String, excludeOpponentKey: String): Flow<Int>
/** /**
* Получить общее количество непрочитанных сообщений * Обновить диалог, пересчитав счетчики из таблицы messages
* Этот метод аналогичен updateDialog из Архива - обновляет все поля диалога одним запросом
*
* Логика:
* 1. Берем последнее сообщение (по timestamp DESC)
* 2. Считаем количество непрочитанных сообщений (from_me = 0 AND read = 0)
* 3. Обновляем диалог или создаем новый
*/ */
@Query("SELECT COALESCE(SUM(unread_count), 0) FROM dialogs WHERE account = :account") @Query("""
fun getTotalUnreadCountFlow(account: String): Flow<Int> INSERT OR REPLACE INTO dialogs (
account,
opponent_key,
opponent_title,
opponent_username,
last_message,
last_message_timestamp,
unread_count,
is_online,
last_seen,
verified
)
SELECT
:account AS account,
:opponentKey AS opponent_key,
COALESCE(
(SELECT opponent_title FROM dialogs WHERE account = :account AND opponent_key = :opponentKey),
''
) AS opponent_title,
COALESCE(
(SELECT opponent_username FROM dialogs WHERE account = :account AND opponent_key = :opponentKey),
''
) AS opponent_username,
COALESCE(
(SELECT plain_message FROM messages
WHERE account = :account
AND ((from_public_key = :opponentKey AND to_public_key = :account)
OR (from_public_key = :account AND to_public_key = :opponentKey))
ORDER BY timestamp DESC LIMIT 1),
''
) AS last_message,
COALESCE(
(SELECT MAX(timestamp) FROM messages
WHERE account = :account
AND ((from_public_key = :opponentKey AND to_public_key = :account)
OR (from_public_key = :account AND to_public_key = :opponentKey))),
0
) AS last_message_timestamp,
COALESCE(
(SELECT COUNT(*) FROM messages
WHERE account = :account
AND from_public_key = :opponentKey
AND to_public_key = :account
AND from_me = 0
AND read = 0),
0
) AS unread_count,
COALESCE(
(SELECT is_online FROM dialogs WHERE account = :account AND opponent_key = :opponentKey),
0
) AS is_online,
COALESCE(
(SELECT last_seen FROM dialogs WHERE account = :account AND opponent_key = :opponentKey),
0
) AS last_seen,
COALESCE(
(SELECT verified FROM dialogs WHERE account = :account AND opponent_key = :opponentKey),
0
) AS verified
""")
suspend fun updateDialogFromMessages(account: String, opponentKey: String)
} }

View File

@@ -392,6 +392,8 @@ class Protocol(
*/ */
fun waitPacket(packetId: Int, callback: (Packet) -> Unit) { fun waitPacket(packetId: Int, callback: (Packet) -> Unit) {
packetWaiters.getOrPut(packetId) { mutableListOf() }.add(callback) packetWaiters.getOrPut(packetId) { mutableListOf() }.add(callback)
val count = packetWaiters[packetId]?.size ?: 0
log("📝 waitPacket(0x${Integer.toHexString(packetId)}) registered. Total handlers for 0x${Integer.toHexString(packetId)}: $count")
} }
/** /**

View File

@@ -62,8 +62,10 @@ object ProtocolManager {
* Инициализация с контекстом для доступа к MessageRepository * Инициализация с контекстом для доступа к MessageRepository
*/ */
fun initialize(context: Context) { fun initialize(context: Context) {
addLog("🚀 ProtocolManager.initialize() called")
messageRepository = MessageRepository.getInstance(context) messageRepository = MessageRepository.getInstance(context)
setupPacketHandlers() setupPacketHandlers()
addLog("🚀 ProtocolManager.initialize() completed")
} }
/** /**
@@ -79,10 +81,15 @@ object ProtocolManager {
* Настройка обработчиков пакетов * Настройка обработчиков пакетов
*/ */
private fun setupPacketHandlers() { private fun setupPacketHandlers() {
addLog("📦 setupPacketHandlers() - Registering packet handlers...")
// Обработчик входящих сообщений (0x06) // Обработчик входящих сообщений (0x06)
waitPacket(0x06) { packet -> waitPacket(0x06) { packet ->
addLog("📦 ⚡⚡⚡ PACKET 0x06 RECEIVED IN PROTOCOL_MANAGER!!! ⚡⚡⚡")
val messagePacket = packet as PacketMessage val messagePacket = packet as PacketMessage
addLog("📩 Incoming message from ${messagePacket.fromPublicKey.take(16)}...") addLog("📩 Incoming message from ${messagePacket.fromPublicKey.take(16)}...")
addLog(" messageRepository = ${if (messageRepository != null) "OK" else "NULL"}")
addLog(" messageRepository.isInitialized = ${messageRepository?.isInitialized() ?: false}")
// ⚡ ВАЖНО: Отправляем подтверждение доставки обратно серверу // ⚡ ВАЖНО: Отправляем подтверждение доставки обратно серверу
// Без этого сервер не будет отправлять следующие сообщения! // Без этого сервер не будет отправлять следующие сообщения!
@@ -94,7 +101,12 @@ object ProtocolManager {
addLog("✅ Sent delivery confirmation for message ${messagePacket.messageId.take(16)}...") addLog("✅ Sent delivery confirmation for message ${messagePacket.messageId.take(16)}...")
scope.launch { scope.launch {
try {
messageRepository?.handleIncomingMessage(messagePacket) messageRepository?.handleIncomingMessage(messagePacket)
addLog("✅ handleIncomingMessage completed!")
} catch (e: Exception) {
addLog("❌ handleIncomingMessage ERROR: ${e.message}")
}
} }
} }

View File

@@ -464,7 +464,8 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
launch(Dispatchers.IO) { launch(Dispatchers.IO) {
// Отмечаем как прочитанные в БД // Отмечаем как прочитанные в БД
messageDao.markDialogAsRead(account, dialogKey) messageDao.markDialogAsRead(account, dialogKey)
dialogDao.clearUnreadCount(account, opponent) // 🔥 Пересчитываем счетчики из messages
dialogDao.updateDialogFromMessages(account, opponent)
// Отправляем read receipt собеседнику // Отправляем read receipt собеседнику
if (messages.isNotEmpty()) { if (messages.isNotEmpty()) {
@@ -519,7 +520,8 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
// Фоновые операции // Фоновые операции
messageDao.markDialogAsRead(account, dialogKey) messageDao.markDialogAsRead(account, dialogKey)
dialogDao.clearUnreadCount(account, opponent) // 🔥 Пересчитываем счетчики из messages
dialogDao.updateDialogFromMessages(account, opponent)
} catch (e: Exception) { } catch (e: Exception) {
} }
@@ -1013,33 +1015,18 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
/** /**
* Сохранить диалог в базу данных * Сохранить диалог в базу данных
* 🔒 lastMessage шифруется для безопасного хранения * <EFBFBD> Используем updateDialogFromMessages для пересчета счетчиков из messages
*/ */
private suspend fun saveDialog(lastMessage: String, timestamp: Long) { private suspend fun saveDialog(lastMessage: String, timestamp: Long) {
val account = myPublicKey ?: return val account = myPublicKey ?: return
val opponent = opponentKey ?: return val opponent = opponentKey ?: return
val privateKey = myPrivateKey ?: return
try { try {
// 🔒 Шифруем lastMessage перед сохранением // 🔥 КРИТИЧНО: Используем updateDialogFromMessages который пересчитывает счетчики
val encryptedLastMessage = CryptoManager.encryptWithPassword(lastMessage, privateKey) // напрямую из таблицы messages, как в Архиве!
dialogDao.updateDialogFromMessages(account, opponent)
val existingDialog = dialogDao.getDialog(account, opponent) Log.d(TAG, "✅ Dialog saved/updated from messages table")
if (existingDialog != null) {
// Обновляем последнее сообщение
dialogDao.updateLastMessage(account, opponent, encryptedLastMessage, timestamp)
} else {
// Создаём новый диалог
dialogDao.insertDialog(DialogEntity(
account = account,
opponentKey = opponent,
opponentTitle = opponentTitle,
opponentUsername = opponentUsername,
lastMessage = encryptedLastMessage,
lastMessageTimestamp = timestamp
))
}
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Dialog save error", e) Log.e(TAG, "Dialog save error", e)
} }
@@ -1050,34 +1037,14 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
*/ */
private suspend fun updateDialog(opponentKey: String, lastMessage: String, timestamp: Long, incrementUnread: Boolean) { private suspend fun updateDialog(opponentKey: String, lastMessage: String, timestamp: Long, incrementUnread: Boolean) {
val account = myPublicKey ?: return val account = myPublicKey ?: return
val privateKey = myPrivateKey ?: return
try { try {
// 🔒 Шифруем lastMessage для диалога // 🔥 КРИТИЧНО: Используем updateDialogFromMessages который пересчитывает счетчики
val encryptedLastMessage = CryptoManager.encryptWithPassword(lastMessage, privateKey) // напрямую из таблицы messages, как в Архиве!
// Это гарантирует что unread_count всегда соответствует реальному количеству непрочитанных
dialogDao.updateDialogFromMessages(account, opponentKey)
val existingDialog = dialogDao.getDialog(account, opponentKey) Log.d(TAG, "✅ Dialog updated from messages table for $opponentKey")
if (existingDialog != null) {
// Обновляем последнее сообщение
dialogDao.updateLastMessage(account, opponentKey, encryptedLastMessage, timestamp)
// Инкрементируем непрочитанные если нужно
if (incrementUnread) {
dialogDao.incrementUnreadCount(account, opponentKey)
}
} else {
// Создаём новый диалог
dialogDao.insertDialog(DialogEntity(
account = account,
opponentKey = opponentKey,
opponentTitle = opponentTitle,
opponentUsername = opponentUsername,
lastMessage = encryptedLastMessage, // 🔒 Зашифрованный
lastMessageTimestamp = timestamp,
unreadCount = if (incrementUnread) 1 else 0
))
}
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "updateDialog error", e) Log.e(TAG, "updateDialog error", e)
} }
@@ -1252,12 +1219,13 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
if (lastIncoming.timestamp.time <= lastReadMessageTimestamp) return if (lastIncoming.timestamp.time <= lastReadMessageTimestamp) return
// Отмечаем в БД и очищаем счетчик непрочитанных // Отмечаем в БД и пересчитываем счетчики
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
val dialogKey = getDialogKey(account, opponent) val dialogKey = getDialogKey(account, opponent)
messageDao.markDialogAsRead(account, dialogKey) messageDao.markDialogAsRead(account, dialogKey)
dialogDao.clearUnreadCount(account, opponent) // 🔥 Пересчитываем счетчики из messages
dialogDao.updateDialogFromMessages(account, opponent)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Mark as read error", e) Log.e(TAG, "Mark as read error", e)
} }

View File

@@ -54,14 +54,20 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
* Установить текущий аккаунт и загрузить диалоги * Установить текущий аккаунт и загрузить диалоги
*/ */
fun setAccount(publicKey: String, privateKey: String) { fun setAccount(publicKey: String, privateKey: String) {
if (currentAccount == publicKey) return if (currentAccount == publicKey) {
android.util.Log.d("ChatsListViewModel", "⚠️ setAccount called again for same account, skipping")
return
}
currentAccount = publicKey currentAccount = publicKey
currentPrivateKey = privateKey currentPrivateKey = privateKey
android.util.Log.d("ChatsListViewModel", "✅ Setting up dialogs Flow for account: ${publicKey.take(16)}...")
viewModelScope.launch { viewModelScope.launch {
dialogDao.getDialogsFlow(publicKey) dialogDao.getDialogsFlow(publicKey)
.flowOn(Dispatchers.IO) // 🚀 Flow работает на IO .flowOn(Dispatchers.IO) // 🚀 Flow работает на IO
.map { dialogsList -> .map { dialogsList ->
android.util.Log.d("ChatsListViewModel", "📋 Dialogs Flow emitted: ${dialogsList.size} dialogs")
// 🔓 Расшифровываем lastMessage на IO потоке (PBKDF2 - тяжелая операция!) // 🔓 Расшифровываем lastMessage на IO потоке (PBKDF2 - тяжелая операция!)
dialogsList.map { dialog -> dialogsList.map { dialog ->
val decryptedLastMessage = try { val decryptedLastMessage = try {
@@ -91,7 +97,9 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
} }
} }
.flowOn(Dispatchers.Default) // 🚀 map выполняется на Default (CPU) .flowOn(Dispatchers.Default) // 🚀 map выполняется на Default (CPU)
.flowOn(Dispatchers.Main) // 🎯 КРИТИЧНО: Обновляем UI на главном потоке!
.collect { decryptedDialogs -> .collect { decryptedDialogs ->
android.util.Log.d("ChatsListViewModel", "✅ Updated UI with ${decryptedDialogs.size} decrypted dialogs")
_dialogs.value = decryptedDialogs _dialogs.value = decryptedDialogs
// 🟢 Подписываемся на онлайн-статусы всех собеседников // 🟢 Подписываемся на онлайн-статусы всех собеседников
@@ -125,6 +133,7 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
/** /**
* Создать или обновить диалог после отправки/получения сообщения * Создать или обновить диалог после отправки/получения сообщения
* 🔥 Используем updateDialogFromMessages для пересчета счетчиков из messages
*/ */
suspend fun upsertDialog( suspend fun upsertDialog(
opponentKey: String, opponentKey: String,
@@ -136,31 +145,20 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
isOnline: Int = 0 isOnline: Int = 0
) { ) {
if (currentAccount.isEmpty()) return if (currentAccount.isEmpty()) return
val privateKey = currentPrivateKey ?: return
// 🔒 Шифруем lastMessage перед сохранением try {
val encryptedLastMessage = CryptoManager.encryptWithPassword(lastMessage, privateKey) // 🔥 КРИТИЧНО: Используем updateDialogFromMessages который пересчитывает счетчики
// напрямую из таблицы messages, как в Архиве!
dialogDao.updateDialogFromMessages(currentAccount, opponentKey)
val existingDialog = dialogDao.getDialog(currentAccount, opponentKey) // Обновляем информацию о собеседнике если есть
if (existingDialog != null) {
// Обновляем
dialogDao.updateLastMessage(currentAccount, opponentKey, encryptedLastMessage, timestamp)
if (opponentTitle.isNotEmpty()) { if (opponentTitle.isNotEmpty()) {
dialogDao.updateOpponentInfo(currentAccount, opponentKey, opponentTitle, opponentUsername, verified) dialogDao.updateOpponentInfo(currentAccount, opponentKey, opponentTitle, opponentUsername, verified)
} }
} else {
// Создаём новый android.util.Log.d("ChatsListViewModel", "✅ Dialog upserted from messages table")
dialogDao.insertDialog(DialogEntity( } catch (e: Exception) {
account = currentAccount, android.util.Log.e("ChatsListViewModel", "Error upserting dialog", e)
opponentKey = opponentKey,
opponentTitle = opponentTitle,
opponentUsername = opponentUsername,
lastMessage = encryptedLastMessage, // 🔒 Зашифрованный
lastMessageTimestamp = timestamp,
verified = verified,
isOnline = isOnline
))
} }
} }