fix: Update read receipt handling to prevent automatic sending and ensure user visibility before marking messages as read
This commit is contained in:
@@ -113,15 +113,15 @@ data class DialogEntity(
|
||||
interface MessageDao {
|
||||
|
||||
/**
|
||||
* Вставка нового сообщения
|
||||
* Вставка нового сообщения (IGNORE если уже существует)
|
||||
*/
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
suspend fun insertMessage(message: MessageEntity): Long
|
||||
|
||||
/**
|
||||
* Вставка нескольких сообщений
|
||||
* Вставка нескольких сообщений (IGNORE если уже существуют)
|
||||
*/
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
suspend fun insertMessages(messages: List<MessageEntity>)
|
||||
|
||||
/**
|
||||
|
||||
@@ -371,6 +371,8 @@ fun ChatDetailScreen(
|
||||
onDispose {
|
||||
focusManager.clearFocus()
|
||||
keyboardController?.hide()
|
||||
// 🔥 Закрываем диалог - сообщения больше не будут читаться автоматически
|
||||
viewModel.closeDialog()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -110,6 +110,10 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
// Флаг что read receipt уже отправлен для текущего диалога
|
||||
private var readReceiptSentForCurrentDialog = false
|
||||
|
||||
// 🔥 Флаг что диалог АКТИВЕН (пользователь внутри чата, а не на главной)
|
||||
// Как currentDialogPublicKeyView в архиве
|
||||
private var isDialogActive = false
|
||||
|
||||
init {
|
||||
setupPacketListeners()
|
||||
}
|
||||
@@ -292,34 +296,25 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
ProtocolManager.addLog("✅ Added to UI: ${packet.messageId.take(8)}... text: ${decryptedText.take(20)}")
|
||||
}
|
||||
|
||||
// Сохраняем в БД (INSERT OR IGNORE - не будет дублей)
|
||||
// 🔥 Сохраняем в БД здесь (в ChatViewModel)
|
||||
// ProtocolManager.setupPacketHandlers() не вызывается, поэтому сохраняем сами
|
||||
saveMessageToDatabase(
|
||||
messageId = packet.messageId,
|
||||
text = decryptedText,
|
||||
encryptedContent = packet.content,
|
||||
encryptedKey = packet.chachaKey,
|
||||
timestamp = packet.timestamp,
|
||||
isFromMe = false,
|
||||
delivered = 1,
|
||||
attachmentsJson = attachmentsJson // 🔥 Сохраняем attachments
|
||||
isFromMe = false, // Это входящее сообщение
|
||||
delivered = DeliveryStatus.DELIVERED.value,
|
||||
attachmentsJson = attachmentsJson
|
||||
)
|
||||
|
||||
// Обновляем диалог
|
||||
saveDialog(decryptedText, packet.timestamp)
|
||||
|
||||
// ⚠️ Delivery отправляется в ProtocolManager.setupPacketHandlers()
|
||||
// Не отправляем повторно чтобы избежать дублирования!
|
||||
// 🔥 Обновляем диалог
|
||||
updateDialog(opponentKey!!, decryptedText, packet.timestamp, incrementUnread = !isDialogActive)
|
||||
|
||||
// 👁️ Отмечаем сообщение как прочитанное в БД
|
||||
messageDao.markAsRead(account, packet.messageId)
|
||||
|
||||
// 👁️ Отправляем read receipt собеседнику (как в архиве - сразу при получении)
|
||||
delay(100) // Небольшая задержка для естественности
|
||||
withContext(Dispatchers.Main) {
|
||||
// Обновляем timestamp и отправляем read receipt
|
||||
lastReadMessageTimestamp = packet.timestamp
|
||||
sendReadReceiptToOpponent()
|
||||
}
|
||||
// 👁️ НЕ отправляем read receipt автоматически!
|
||||
// Read receipt отправляется только когда пользователь видит сообщение
|
||||
// (через markVisibleMessagesAsRead вызываемый из ChatDetailScreen)
|
||||
|
||||
} catch (e: Exception) {
|
||||
ProtocolManager.addLog("❌ Error handling incoming message: ${e.message}")
|
||||
@@ -381,6 +376,7 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
isLoadingMessages = false
|
||||
lastReadMessageTimestamp = 0L
|
||||
readReceiptSentForCurrentDialog = false
|
||||
isDialogActive = true // 🔥 Диалог активен!
|
||||
|
||||
ProtocolManager.addLog("💬 Dialog opened: ${title.ifEmpty { publicKey.take(16) }}...")
|
||||
|
||||
@@ -391,6 +387,15 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
loadMessagesFromDatabase()
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔥 Закрыть диалог (вызывается когда пользователь выходит из чата)
|
||||
* Как setCurrentDialogPublicKeyView("") в архиве
|
||||
*/
|
||||
fun closeDialog() {
|
||||
isDialogActive = false
|
||||
ProtocolManager.addLog("💬 Dialog closed (isDialogActive = false)")
|
||||
}
|
||||
|
||||
/**
|
||||
* 🚀 Оптимизированная загрузка сообщений с пагинацией
|
||||
*/
|
||||
@@ -922,6 +927,44 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновить диалог при входящем сообщении
|
||||
*/
|
||||
private suspend fun updateDialog(opponentKey: String, lastMessage: String, timestamp: Long, incrementUnread: Boolean) {
|
||||
val account = myPublicKey ?: return
|
||||
|
||||
try {
|
||||
val existingDialog = dialogDao.getDialog(account, opponentKey)
|
||||
|
||||
if (existingDialog != null) {
|
||||
// Обновляем последнее сообщение
|
||||
dialogDao.updateLastMessage(account, opponentKey, lastMessage, timestamp)
|
||||
|
||||
// Инкрементируем непрочитанные если нужно
|
||||
if (incrementUnread) {
|
||||
dialogDao.incrementUnreadCount(account, opponentKey)
|
||||
ProtocolManager.addLog("📬 Unread incremented for: ${opponentKey.take(16)}...")
|
||||
}
|
||||
ProtocolManager.addLog("✅ Dialog updated: ${lastMessage.take(20)}...")
|
||||
} else {
|
||||
// Создаём новый диалог
|
||||
dialogDao.insertDialog(DialogEntity(
|
||||
account = account,
|
||||
opponentKey = opponentKey,
|
||||
opponentTitle = opponentTitle,
|
||||
opponentUsername = opponentUsername,
|
||||
lastMessage = lastMessage,
|
||||
lastMessageTimestamp = timestamp,
|
||||
unreadCount = if (incrementUnread) 1 else 0
|
||||
))
|
||||
ProtocolManager.addLog("✅ Dialog created (new)")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
ProtocolManager.addLog("❌ updateDialog error: ${e.message}")
|
||||
Log.e(TAG, "updateDialog error", e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Сохранить сообщение в базу данных
|
||||
*/
|
||||
@@ -941,8 +984,11 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
try {
|
||||
val dialogKey = getDialogKey(account, opponent)
|
||||
|
||||
// Проверяем существует ли сообщение
|
||||
val exists = messageDao.messageExists(account, messageId)
|
||||
ProtocolManager.addLog("💾 Saving message to DB:")
|
||||
ProtocolManager.addLog(" messageId: ${messageId.take(8)}...")
|
||||
ProtocolManager.addLog(" messageId: $messageId")
|
||||
ProtocolManager.addLog(" exists in DB: $exists")
|
||||
ProtocolManager.addLog(" dialogKey: $dialogKey")
|
||||
ProtocolManager.addLog(" text: ${text.take(20)}...")
|
||||
|
||||
@@ -1033,6 +1079,12 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
* Означает что мы прочитали все сообщения от этого собеседника
|
||||
*/
|
||||
private fun sendReadReceiptToOpponent() {
|
||||
// 🔥 Не отправляем read receipt если диалог не активен (как в архиве)
|
||||
if (!isDialogActive) {
|
||||
ProtocolManager.addLog("👁️ Read receipt skipped - dialog not active")
|
||||
return
|
||||
}
|
||||
|
||||
val opponent = opponentKey ?: return
|
||||
val sender = myPublicKey ?: return
|
||||
val privateKey = myPrivateKey ?: return
|
||||
@@ -1068,6 +1120,12 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
* Теперь работает как в архиве - при изменении списка сообщений
|
||||
*/
|
||||
fun markVisibleMessagesAsRead() {
|
||||
// 🔥 Не читаем если диалог не активен
|
||||
if (!isDialogActive) {
|
||||
ProtocolManager.addLog("👁️ markVisibleMessagesAsRead skipped - dialog not active")
|
||||
return
|
||||
}
|
||||
|
||||
val opponent = opponentKey ?: return
|
||||
val account = myPublicKey ?: return
|
||||
|
||||
|
||||
Reference in New Issue
Block a user