feat: Enhance logging in MessageRepository and ChatsListViewModel for better debugging and flow tracking
This commit is contained in:
@@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user