From 19a89ea00e043c2076cf3c639e21efbaa9a47425 Mon Sep 17 00:00:00 2001 From: k1ngsterr1 Date: Mon, 19 Jan 2026 00:32:14 +0500 Subject: [PATCH] feat: Enhance message status tracking and logging in MessageRepository and ChatsListViewModel for improved clarity and debugging --- .../messenger/data/MessageRepository.kt | 40 ++++++++++-- .../messenger/database/MessageEntities.kt | 65 +++++++++++++++---- .../messenger/ui/chats/ChatsListScreen.kt | 19 +++--- .../messenger/ui/chats/ChatsListViewModel.kt | 17 ++++- 4 files changed, 111 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/com/rosetta/messenger/data/MessageRepository.kt b/app/src/main/java/com/rosetta/messenger/data/MessageRepository.kt index 7a206b0..3653dd6 100644 --- a/app/src/main/java/com/rosetta/messenger/data/MessageRepository.kt +++ b/app/src/main/java/com/rosetta/messenger/data/MessageRepository.kt @@ -1,6 +1,7 @@ package com.rosetta.messenger.data import android.content.Context +import android.util.Log import com.rosetta.messenger.crypto.CryptoManager import com.rosetta.messenger.crypto.MessageCrypto import com.rosetta.messenger.database.* @@ -224,10 +225,17 @@ class MessageRepository private constructor(private val context: Context) { dialogKey = dialogKey ) messageDao.insertMessage(entity) + + Log.d("MessageRepository", "� Inserted OUTGOING message: fromMe=1, delivered=${entity.delivered}, read=0") } - // Обновляем диалог - updateDialog(toPublicKey, text.trim(), timestamp) + // 🔥 КРИТИЧНО: Обновляем диалог через updateDialogFromMessages + Log.d("MessageRepository", "🔄 Calling updateDialogFromMessages after sending...") + dialogDao.updateDialogFromMessages(account, toPublicKey) + + // 🔥 Логируем что записалось в диалог + val dialog = dialogDao.getDialog(account, toPublicKey) + Log.d("MessageRepository", "🎯 DIALOG AFTER SEND: lastMsgFromMe=${dialog?.lastMessageFromMe}, delivered=${dialog?.lastMessageDelivered}, read=${dialog?.lastMessageRead}") // 🔥 Отмечаем что я отправлял сообщения в этот диалог (перемещает из requests в chats) val updatedRows = dialogDao.markIHaveSent(account, toPublicKey) @@ -336,10 +344,17 @@ class MessageRepository private constructor(private val context: Context) { if (!stillExists) { // Сохраняем в БД только если сообщения нет messageDao.insertMessage(entity) + + Log.d("MessageRepository", "� Inserted INCOMING message: fromMe=0, delivered=${entity.delivered}, read=0") } - // Обновляем диалог ПОСЛЕ вставки сообщения - updateDialog(packet.fromPublicKey, plainText, packet.timestamp, incrementUnread = true) + // 🔥 КРИТИЧНО: Обновляем диалог через updateDialogFromMessages + Log.d("MessageRepository", "🔄 Calling updateDialogFromMessages after incoming message...") + dialogDao.updateDialogFromMessages(account, packet.fromPublicKey) + + // 🔥 Логируем что записалось в диалог + val dialog = dialogDao.getDialog(account, packet.fromPublicKey) + Log.d("MessageRepository", "🎯 DIALOG AFTER INCOMING: lastMsgFromMe=${dialog?.lastMessageFromMe}, delivered=${dialog?.lastMessageDelivered}, read=${dialog?.lastMessageRead}") // 🔥 Запрашиваем информацию о пользователе для отображения имени вместо ключа requestUserInfo(packet.fromPublicKey) @@ -381,8 +396,19 @@ class MessageRepository private constructor(private val context: Context) { suspend fun handleRead(packet: PacketRead) { val account = currentAccount ?: return + Log.d("MessageRepository", "🔥🔥🔥 handleRead START: fromPublicKey=${packet.fromPublicKey.take(16)}...") + + // Проверяем последнее сообщение ДО обновления + val lastMsgBefore = messageDao.getLastMessageDebug(account, packet.fromPublicKey) + Log.d("MessageRepository", "📊 BEFORE markAllAsRead: fromMe=${lastMsgBefore?.fromMe}, delivered=${lastMsgBefore?.delivered}, read=${lastMsgBefore?.read}, timestamp=${lastMsgBefore?.timestamp}") + // Отмечаем все наши исходящие сообщения к этому собеседнику как прочитанные messageDao.markAllAsRead(account, packet.fromPublicKey) + Log.d("MessageRepository", "✅ markAllAsRead completed") + + // 🔥 DEBUG: Проверяем последнее сообщение ПОСЛЕ обновления + val lastMsgAfter = messageDao.getLastMessageDebug(account, packet.fromPublicKey) + Log.d("MessageRepository", "📊 AFTER markAllAsRead: fromMe=${lastMsgAfter?.fromMe}, delivered=${lastMsgAfter?.delivered}, read=${lastMsgAfter?.read}, timestamp=${lastMsgAfter?.timestamp}") // Обновляем кэш - все исходящие сообщения помечаем как прочитанные val dialogKey = getDialogKey(packet.fromPublicKey) @@ -394,7 +420,13 @@ class MessageRepository private constructor(private val context: Context) { } // 🔥 КРИТИЧНО: Обновляем диалог чтобы lastMessageRead обновился + Log.d("MessageRepository", "🔄 Calling updateDialogFromMessages...") dialogDao.updateDialogFromMessages(account, packet.fromPublicKey) + + // Логируем что записалось в диалог + val dialog = dialogDao.getDialog(account, packet.fromPublicKey) + Log.d("MessageRepository", "🎯 DIALOG AFTER UPDATE: lastMsgFromMe=${dialog?.lastMessageFromMe}, delivered=${dialog?.lastMessageDelivered}, read=${dialog?.lastMessageRead}") + Log.d("MessageRepository", "🔥🔥🔥 handleRead END") } /** diff --git a/app/src/main/java/com/rosetta/messenger/database/MessageEntities.kt b/app/src/main/java/com/rosetta/messenger/database/MessageEntities.kt index 666e77a..5289dc7 100644 --- a/app/src/main/java/com/rosetta/messenger/database/MessageEntities.kt +++ b/app/src/main/java/com/rosetta/messenger/database/MessageEntities.kt @@ -3,6 +3,15 @@ package com.rosetta.messenger.database import androidx.room.* import kotlinx.coroutines.flow.Flow +/** + * 🔥 Data class для статуса последнего сообщения + */ +data class LastMessageStatus( + @ColumnInfo(name = "from_me") val fromMe: Int, + @ColumnInfo(name = "delivered") val delivered: Int, + @ColumnInfo(name = "read") val read: Int +) + /** * Entity для сообщений - как в React Native версии */ @@ -304,14 +313,40 @@ interface MessageDao { suspend fun messageExists(account: String, messageId: String): Boolean /** - * Отметить все исходящие сообщения к собеседнику как прочитанные (delivered=3) + * Отметить все исходящие сообщения к собеседнику как прочитанные * Используется когда приходит PacketRead от собеседника + * 🔥 ВАЖНО: delivered=3 означает READ (синхронизировано с ChatViewModel) */ @Query(""" - UPDATE messages SET delivered = 3 - WHERE account = :account AND to_public_key = :opponent AND from_me = 1 AND delivered < 3 + UPDATE messages SET delivered = 3, read = 1 + WHERE account = :account AND to_public_key = :opponent AND from_me = 1 """) suspend fun markAllAsRead(account: String, opponent: String) + + /** + * 🔥 DEBUG: Получить последнее сообщение в диалоге для отладки + */ + @Query(""" + SELECT * FROM messages + WHERE account = :account + AND ((from_public_key = :opponent AND to_public_key = :account) + OR (from_public_key = :account AND to_public_key = :opponent)) + ORDER BY timestamp DESC, id DESC LIMIT 1 + """) + suspend fun getLastMessageDebug(account: String, opponent: String): MessageEntity? + + /** + * 🔥 Получить статус последнего сообщения (fromMe, delivered, read) для диалога + * Возвращает null если сообщений нет + */ + @Query(""" + SELECT from_me, delivered, read FROM messages + WHERE account = :account + AND ((from_public_key = :opponent AND to_public_key = :account) + OR (from_public_key = :account AND to_public_key = :opponent)) + ORDER BY timestamp DESC, id DESC LIMIT 1 + """) + suspend fun getLastMessageStatus(account: String, opponent: String): LastMessageStatus? } /** @@ -543,25 +578,27 @@ interface DialogDao { 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), + ORDER BY timestamp DESC, id DESC LIMIT 1), 0 ) AS last_message_from_me, COALESCE( - (SELECT delivered FROM messages + (SELECT + CASE WHEN from_me = 1 THEN delivered ELSE 0 END + FROM messages WHERE account = :account - AND from_me = 1 - AND from_public_key = :account - AND to_public_key = :opponentKey - ORDER BY timestamp DESC LIMIT 1), + AND ((from_public_key = :opponentKey AND to_public_key = :account) + OR (from_public_key = :account AND to_public_key = :opponentKey)) + ORDER BY timestamp DESC, id DESC LIMIT 1), 0 ) AS last_message_delivered, COALESCE( - (SELECT read FROM messages + (SELECT + CASE WHEN from_me = 1 THEN read ELSE 0 END + FROM messages WHERE account = :account - AND from_me = 1 - AND from_public_key = :account - AND to_public_key = :opponentKey - ORDER BY timestamp DESC LIMIT 1), + AND ((from_public_key = :opponentKey AND to_public_key = :account) + OR (from_public_key = :account AND to_public_key = :opponentKey)) + ORDER BY timestamp DESC, id DESC LIMIT 1), 0 ) AS last_message_read WHERE EXISTS ( diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatsListScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatsListScreen.kt index d89a379..59e4559 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatsListScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatsListScreen.kt @@ -1590,12 +1590,13 @@ fun DialogItemContent( Spacer(modifier = Modifier.width(4.dp)) } else if (dialog.lastMessageFromMe == 1) { // Показываем статус только для исходящих сообщений (кроме Saved Messages) - // 🔥 ПРАВИЛЬНАЯ ЛОГИКА: - // - lastMessageRead == 1 → две синие галочки (прочитано собеседником) - // - lastMessageDelivered == 1 && lastMessageRead == 0 → одна галочка (доставлено, но не прочитано) - // - lastMessageDelivered == 0 → одна галочка (отправляется) - when { - dialog.lastMessageDelivered == 2 -> { + // 🔥 ПРАВИЛЬНАЯ ЛОГИКА (синхронизировано с ChatViewModel): + // - lastMessageDelivered == 3 → две синие галочки (прочитано собеседником) + // - lastMessageDelivered == 1 → одна галочка (доставлено) + // - lastMessageDelivered == 0 → часики (отправляется) + // - lastMessageDelivered == 2 → ошибка + when (dialog.lastMessageDelivered) { + 2 -> { // ERROR - показываем иконку ошибки Icon( imageVector = Icons.Outlined.ErrorOutline, @@ -1605,8 +1606,8 @@ fun DialogItemContent( ) Spacer(modifier = Modifier.width(4.dp)) } - dialog.lastMessageRead == 1 -> { - // ПРОЧИТАНО собеседником - две синие галочки + 3 -> { + // READ (delivered=3) - две синие галочки Icon( imageVector = Icons.Default.DoneAll, contentDescription = null, @@ -1616,7 +1617,7 @@ fun DialogItemContent( Spacer(modifier = Modifier.width(4.dp)) } else -> { - // ДОСТАВЛЕНО или ОТПРАВЛЯЕТСЯ - одна серая галочка + // DELIVERED (1) или SENDING (0) - одна серая галочка Icon( imageVector = Icons.Default.Done, contentDescription = null, diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatsListViewModel.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatsListViewModel.kt index e674edf..5cb9b80 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatsListViewModel.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatsListViewModel.kt @@ -57,6 +57,7 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio private val database = RosettaDatabase.getDatabase(application) private val dialogDao = database.dialogDao() + private val messageDao = database.messageDao() // 🔥 Добавляем для получения статуса последнего сообщения private var currentAccount: String = "" private var currentPrivateKey: String? = null @@ -139,6 +140,16 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio dialog.lastMessage // Fallback на зашифрованный текст } + // 🔥🔥🔥 НОВЫЙ ПОДХОД: Получаем статус НАПРЯМУЮ из таблицы messages + // Это гарантирует синхронизацию с тем что показывается в диалоге + val lastMsgStatus = messageDao.getLastMessageStatus(publicKey, dialog.opponentKey) + val actualFromMe = lastMsgStatus?.fromMe ?: 0 + val actualDelivered = if (actualFromMe == 1) (lastMsgStatus?.delivered ?: 0) else 0 + val actualRead = if (actualFromMe == 1) (lastMsgStatus?.read ?: 0) else 0 + + // 🔥 Лог для отладки - показываем и старые и новые значения + android.util.Log.d("ChatsListVM", "📊 Dialog ${dialog.opponentKey.take(16)}... | OLD: fromMe=${dialog.lastMessageFromMe}, del=${dialog.lastMessageDelivered}, read=${dialog.lastMessageRead} | NEW: fromMe=$actualFromMe, del=$actualDelivered, read=$actualRead") + DialogUiModel( id = dialog.id, account = dialog.account, @@ -152,9 +163,9 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio lastSeen = dialog.lastSeen, verified = dialog.verified, isSavedMessages = isSavedMessages, // 📁 Saved Messages - lastMessageFromMe = dialog.lastMessageFromMe, - lastMessageDelivered = dialog.lastMessageDelivered, - lastMessageRead = dialog.lastMessageRead + lastMessageFromMe = actualFromMe, // 🔥 Используем актуальные данные из messages + lastMessageDelivered = actualDelivered, // 🔥 Используем актуальные данные из messages + lastMessageRead = actualRead // 🔥 Используем актуальные данные из messages ) } }