feat: Enhance message status tracking and logging in MessageRepository and ChatsListViewModel for improved clarity and debugging
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
package com.rosetta.messenger.data
|
package com.rosetta.messenger.data
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
import com.rosetta.messenger.crypto.CryptoManager
|
import com.rosetta.messenger.crypto.CryptoManager
|
||||||
import com.rosetta.messenger.crypto.MessageCrypto
|
import com.rosetta.messenger.crypto.MessageCrypto
|
||||||
import com.rosetta.messenger.database.*
|
import com.rosetta.messenger.database.*
|
||||||
@@ -224,10 +225,17 @@ class MessageRepository private constructor(private val context: Context) {
|
|||||||
dialogKey = dialogKey
|
dialogKey = dialogKey
|
||||||
)
|
)
|
||||||
messageDao.insertMessage(entity)
|
messageDao.insertMessage(entity)
|
||||||
|
|
||||||
|
Log.d("MessageRepository", "<EFBFBD> Inserted OUTGOING message: fromMe=1, delivered=${entity.delivered}, read=0")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновляем диалог
|
// 🔥 КРИТИЧНО: Обновляем диалог через updateDialogFromMessages
|
||||||
updateDialog(toPublicKey, text.trim(), timestamp)
|
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)
|
// 🔥 Отмечаем что я отправлял сообщения в этот диалог (перемещает из requests в chats)
|
||||||
val updatedRows = dialogDao.markIHaveSent(account, toPublicKey)
|
val updatedRows = dialogDao.markIHaveSent(account, toPublicKey)
|
||||||
@@ -336,10 +344,17 @@ class MessageRepository private constructor(private val context: Context) {
|
|||||||
if (!stillExists) {
|
if (!stillExists) {
|
||||||
// Сохраняем в БД только если сообщения нет
|
// Сохраняем в БД только если сообщения нет
|
||||||
messageDao.insertMessage(entity)
|
messageDao.insertMessage(entity)
|
||||||
|
|
||||||
|
Log.d("MessageRepository", "<EFBFBD> Inserted INCOMING message: fromMe=0, delivered=${entity.delivered}, read=0")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновляем диалог ПОСЛЕ вставки сообщения
|
// 🔥 КРИТИЧНО: Обновляем диалог через updateDialogFromMessages
|
||||||
updateDialog(packet.fromPublicKey, plainText, packet.timestamp, incrementUnread = true)
|
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)
|
requestUserInfo(packet.fromPublicKey)
|
||||||
@@ -381,8 +396,19 @@ class MessageRepository private constructor(private val context: Context) {
|
|||||||
suspend fun handleRead(packet: PacketRead) {
|
suspend fun handleRead(packet: PacketRead) {
|
||||||
val account = currentAccount ?: return
|
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)
|
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)
|
val dialogKey = getDialogKey(packet.fromPublicKey)
|
||||||
@@ -394,7 +420,13 @@ class MessageRepository private constructor(private val context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 🔥 КРИТИЧНО: Обновляем диалог чтобы lastMessageRead обновился
|
// 🔥 КРИТИЧНО: Обновляем диалог чтобы lastMessageRead обновился
|
||||||
|
Log.d("MessageRepository", "🔄 Calling updateDialogFromMessages...")
|
||||||
dialogDao.updateDialogFromMessages(account, packet.fromPublicKey)
|
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")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,6 +3,15 @@ package com.rosetta.messenger.database
|
|||||||
import androidx.room.*
|
import androidx.room.*
|
||||||
import kotlinx.coroutines.flow.Flow
|
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 версии
|
* Entity для сообщений - как в React Native версии
|
||||||
*/
|
*/
|
||||||
@@ -304,14 +313,40 @@ interface MessageDao {
|
|||||||
suspend fun messageExists(account: String, messageId: String): Boolean
|
suspend fun messageExists(account: String, messageId: String): Boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Отметить все исходящие сообщения к собеседнику как прочитанные (delivered=3)
|
* Отметить все исходящие сообщения к собеседнику как прочитанные
|
||||||
* Используется когда приходит PacketRead от собеседника
|
* Используется когда приходит PacketRead от собеседника
|
||||||
|
* 🔥 ВАЖНО: delivered=3 означает READ (синхронизировано с ChatViewModel)
|
||||||
*/
|
*/
|
||||||
@Query("""
|
@Query("""
|
||||||
UPDATE messages SET delivered = 3
|
UPDATE messages SET delivered = 3, read = 1
|
||||||
WHERE account = :account AND to_public_key = :opponent AND from_me = 1 AND delivered < 3
|
WHERE account = :account AND to_public_key = :opponent AND from_me = 1
|
||||||
""")
|
""")
|
||||||
suspend fun markAllAsRead(account: String, opponent: String)
|
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
|
WHERE account = :account
|
||||||
AND ((from_public_key = :opponentKey AND to_public_key = :account)
|
AND ((from_public_key = :opponentKey AND to_public_key = :account)
|
||||||
OR (from_public_key = :account AND to_public_key = :opponentKey))
|
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
|
0
|
||||||
) AS last_message_from_me,
|
) AS last_message_from_me,
|
||||||
COALESCE(
|
COALESCE(
|
||||||
(SELECT delivered FROM messages
|
(SELECT
|
||||||
|
CASE WHEN from_me = 1 THEN delivered ELSE 0 END
|
||||||
|
FROM messages
|
||||||
WHERE account = :account
|
WHERE account = :account
|
||||||
AND from_me = 1
|
AND ((from_public_key = :opponentKey AND to_public_key = :account)
|
||||||
AND from_public_key = :account
|
OR (from_public_key = :account AND to_public_key = :opponentKey))
|
||||||
AND to_public_key = :opponentKey
|
ORDER BY timestamp DESC, id DESC LIMIT 1),
|
||||||
ORDER BY timestamp DESC LIMIT 1),
|
|
||||||
0
|
0
|
||||||
) AS last_message_delivered,
|
) AS last_message_delivered,
|
||||||
COALESCE(
|
COALESCE(
|
||||||
(SELECT read FROM messages
|
(SELECT
|
||||||
|
CASE WHEN from_me = 1 THEN read ELSE 0 END
|
||||||
|
FROM messages
|
||||||
WHERE account = :account
|
WHERE account = :account
|
||||||
AND from_me = 1
|
AND ((from_public_key = :opponentKey AND to_public_key = :account)
|
||||||
AND from_public_key = :account
|
OR (from_public_key = :account AND to_public_key = :opponentKey))
|
||||||
AND to_public_key = :opponentKey
|
ORDER BY timestamp DESC, id DESC LIMIT 1),
|
||||||
ORDER BY timestamp DESC LIMIT 1),
|
|
||||||
0
|
0
|
||||||
) AS last_message_read
|
) AS last_message_read
|
||||||
WHERE EXISTS (
|
WHERE EXISTS (
|
||||||
|
|||||||
@@ -1590,12 +1590,13 @@ fun DialogItemContent(
|
|||||||
Spacer(modifier = Modifier.width(4.dp))
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
} else if (dialog.lastMessageFromMe == 1) {
|
} else if (dialog.lastMessageFromMe == 1) {
|
||||||
// Показываем статус только для исходящих сообщений (кроме Saved Messages)
|
// Показываем статус только для исходящих сообщений (кроме Saved Messages)
|
||||||
// 🔥 ПРАВИЛЬНАЯ ЛОГИКА:
|
// 🔥 ПРАВИЛЬНАЯ ЛОГИКА (синхронизировано с ChatViewModel):
|
||||||
// - lastMessageRead == 1 → две синие галочки (прочитано собеседником)
|
// - lastMessageDelivered == 3 → две синие галочки (прочитано собеседником)
|
||||||
// - lastMessageDelivered == 1 && lastMessageRead == 0 → одна галочка (доставлено, но не прочитано)
|
// - lastMessageDelivered == 1 → одна галочка (доставлено)
|
||||||
// - lastMessageDelivered == 0 → одна галочка (отправляется)
|
// - lastMessageDelivered == 0 → часики (отправляется)
|
||||||
when {
|
// - lastMessageDelivered == 2 → ошибка
|
||||||
dialog.lastMessageDelivered == 2 -> {
|
when (dialog.lastMessageDelivered) {
|
||||||
|
2 -> {
|
||||||
// ERROR - показываем иконку ошибки
|
// ERROR - показываем иконку ошибки
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.ErrorOutline,
|
imageVector = Icons.Outlined.ErrorOutline,
|
||||||
@@ -1605,8 +1606,8 @@ fun DialogItemContent(
|
|||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
}
|
}
|
||||||
dialog.lastMessageRead == 1 -> {
|
3 -> {
|
||||||
// ПРОЧИТАНО собеседником - две синие галочки
|
// READ (delivered=3) - две синие галочки
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.DoneAll,
|
imageVector = Icons.Default.DoneAll,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
@@ -1616,7 +1617,7 @@ fun DialogItemContent(
|
|||||||
Spacer(modifier = Modifier.width(4.dp))
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
// ДОСТАВЛЕНО или ОТПРАВЛЯЕТСЯ - одна серая галочка
|
// DELIVERED (1) или SENDING (0) - одна серая галочка
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Done,
|
imageVector = Icons.Default.Done,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
|
|||||||
|
|
||||||
private val database = RosettaDatabase.getDatabase(application)
|
private val database = RosettaDatabase.getDatabase(application)
|
||||||
private val dialogDao = database.dialogDao()
|
private val dialogDao = database.dialogDao()
|
||||||
|
private val messageDao = database.messageDao() // 🔥 Добавляем для получения статуса последнего сообщения
|
||||||
|
|
||||||
private var currentAccount: String = ""
|
private var currentAccount: String = ""
|
||||||
private var currentPrivateKey: String? = null
|
private var currentPrivateKey: String? = null
|
||||||
@@ -139,6 +140,16 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
|
|||||||
dialog.lastMessage // Fallback на зашифрованный текст
|
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(
|
DialogUiModel(
|
||||||
id = dialog.id,
|
id = dialog.id,
|
||||||
account = dialog.account,
|
account = dialog.account,
|
||||||
@@ -152,9 +163,9 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
|
|||||||
lastSeen = dialog.lastSeen,
|
lastSeen = dialog.lastSeen,
|
||||||
verified = dialog.verified,
|
verified = dialog.verified,
|
||||||
isSavedMessages = isSavedMessages, // 📁 Saved Messages
|
isSavedMessages = isSavedMessages, // 📁 Saved Messages
|
||||||
lastMessageFromMe = dialog.lastMessageFromMe,
|
lastMessageFromMe = actualFromMe, // 🔥 Используем актуальные данные из messages
|
||||||
lastMessageDelivered = dialog.lastMessageDelivered,
|
lastMessageDelivered = actualDelivered, // 🔥 Используем актуальные данные из messages
|
||||||
lastMessageRead = dialog.lastMessageRead
|
lastMessageRead = actualRead // 🔥 Используем актуальные данные из messages
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user