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 {
|
interface MessageDao {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Вставка нового сообщения
|
* Вставка нового сообщения (IGNORE если уже существует)
|
||||||
*/
|
*/
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||||
suspend fun insertMessage(message: MessageEntity): Long
|
suspend fun insertMessage(message: MessageEntity): Long
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Вставка нескольких сообщений
|
* Вставка нескольких сообщений (IGNORE если уже существуют)
|
||||||
*/
|
*/
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||||
suspend fun insertMessages(messages: List<MessageEntity>)
|
suspend fun insertMessages(messages: List<MessageEntity>)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -371,6 +371,8 @@ fun ChatDetailScreen(
|
|||||||
onDispose {
|
onDispose {
|
||||||
focusManager.clearFocus()
|
focusManager.clearFocus()
|
||||||
keyboardController?.hide()
|
keyboardController?.hide()
|
||||||
|
// 🔥 Закрываем диалог - сообщения больше не будут читаться автоматически
|
||||||
|
viewModel.closeDialog()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -110,6 +110,10 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
// Флаг что read receipt уже отправлен для текущего диалога
|
// Флаг что read receipt уже отправлен для текущего диалога
|
||||||
private var readReceiptSentForCurrentDialog = false
|
private var readReceiptSentForCurrentDialog = false
|
||||||
|
|
||||||
|
// 🔥 Флаг что диалог АКТИВЕН (пользователь внутри чата, а не на главной)
|
||||||
|
// Как currentDialogPublicKeyView в архиве
|
||||||
|
private var isDialogActive = false
|
||||||
|
|
||||||
init {
|
init {
|
||||||
setupPacketListeners()
|
setupPacketListeners()
|
||||||
}
|
}
|
||||||
@@ -292,34 +296,25 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
ProtocolManager.addLog("✅ Added to UI: ${packet.messageId.take(8)}... text: ${decryptedText.take(20)}")
|
ProtocolManager.addLog("✅ Added to UI: ${packet.messageId.take(8)}... text: ${decryptedText.take(20)}")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Сохраняем в БД (INSERT OR IGNORE - не будет дублей)
|
// 🔥 Сохраняем в БД здесь (в ChatViewModel)
|
||||||
|
// ProtocolManager.setupPacketHandlers() не вызывается, поэтому сохраняем сами
|
||||||
saveMessageToDatabase(
|
saveMessageToDatabase(
|
||||||
messageId = packet.messageId,
|
messageId = packet.messageId,
|
||||||
text = decryptedText,
|
text = decryptedText,
|
||||||
encryptedContent = packet.content,
|
encryptedContent = packet.content,
|
||||||
encryptedKey = packet.chachaKey,
|
encryptedKey = packet.chachaKey,
|
||||||
timestamp = packet.timestamp,
|
timestamp = packet.timestamp,
|
||||||
isFromMe = false,
|
isFromMe = false, // Это входящее сообщение
|
||||||
delivered = 1,
|
delivered = DeliveryStatus.DELIVERED.value,
|
||||||
attachmentsJson = attachmentsJson // 🔥 Сохраняем attachments
|
attachmentsJson = attachmentsJson
|
||||||
)
|
)
|
||||||
|
|
||||||
// Обновляем диалог
|
|
||||||
saveDialog(decryptedText, packet.timestamp)
|
|
||||||
|
|
||||||
// ⚠️ Delivery отправляется в ProtocolManager.setupPacketHandlers()
|
// 🔥 Обновляем диалог
|
||||||
// Не отправляем повторно чтобы избежать дублирования!
|
updateDialog(opponentKey!!, decryptedText, packet.timestamp, incrementUnread = !isDialogActive)
|
||||||
|
|
||||||
// 👁️ Отмечаем сообщение как прочитанное в БД
|
// 👁️ НЕ отправляем read receipt автоматически!
|
||||||
messageDao.markAsRead(account, packet.messageId)
|
// Read receipt отправляется только когда пользователь видит сообщение
|
||||||
|
// (через markVisibleMessagesAsRead вызываемый из ChatDetailScreen)
|
||||||
// 👁️ Отправляем read receipt собеседнику (как в архиве - сразу при получении)
|
|
||||||
delay(100) // Небольшая задержка для естественности
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
// Обновляем timestamp и отправляем read receipt
|
|
||||||
lastReadMessageTimestamp = packet.timestamp
|
|
||||||
sendReadReceiptToOpponent()
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ProtocolManager.addLog("❌ Error handling incoming message: ${e.message}")
|
ProtocolManager.addLog("❌ Error handling incoming message: ${e.message}")
|
||||||
@@ -381,6 +376,7 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
isLoadingMessages = false
|
isLoadingMessages = false
|
||||||
lastReadMessageTimestamp = 0L
|
lastReadMessageTimestamp = 0L
|
||||||
readReceiptSentForCurrentDialog = false
|
readReceiptSentForCurrentDialog = false
|
||||||
|
isDialogActive = true // 🔥 Диалог активен!
|
||||||
|
|
||||||
ProtocolManager.addLog("💬 Dialog opened: ${title.ifEmpty { publicKey.take(16) }}...")
|
ProtocolManager.addLog("💬 Dialog opened: ${title.ifEmpty { publicKey.take(16) }}...")
|
||||||
|
|
||||||
@@ -391,6 +387,15 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
loadMessagesFromDatabase()
|
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 {
|
try {
|
||||||
val dialogKey = getDialogKey(account, opponent)
|
val dialogKey = getDialogKey(account, opponent)
|
||||||
|
|
||||||
|
// Проверяем существует ли сообщение
|
||||||
|
val exists = messageDao.messageExists(account, messageId)
|
||||||
ProtocolManager.addLog("💾 Saving message to DB:")
|
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(" dialogKey: $dialogKey")
|
||||||
ProtocolManager.addLog(" text: ${text.take(20)}...")
|
ProtocolManager.addLog(" text: ${text.take(20)}...")
|
||||||
|
|
||||||
@@ -1033,6 +1079,12 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
* Означает что мы прочитали все сообщения от этого собеседника
|
* Означает что мы прочитали все сообщения от этого собеседника
|
||||||
*/
|
*/
|
||||||
private fun sendReadReceiptToOpponent() {
|
private fun sendReadReceiptToOpponent() {
|
||||||
|
// 🔥 Не отправляем read receipt если диалог не активен (как в архиве)
|
||||||
|
if (!isDialogActive) {
|
||||||
|
ProtocolManager.addLog("👁️ Read receipt skipped - dialog not active")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
val opponent = opponentKey ?: return
|
val opponent = opponentKey ?: return
|
||||||
val sender = myPublicKey ?: return
|
val sender = myPublicKey ?: return
|
||||||
val privateKey = myPrivateKey ?: return
|
val privateKey = myPrivateKey ?: return
|
||||||
@@ -1068,6 +1120,12 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
* Теперь работает как в архиве - при изменении списка сообщений
|
* Теперь работает как в архиве - при изменении списка сообщений
|
||||||
*/
|
*/
|
||||||
fun markVisibleMessagesAsRead() {
|
fun markVisibleMessagesAsRead() {
|
||||||
|
// 🔥 Не читаем если диалог не активен
|
||||||
|
if (!isDialogActive) {
|
||||||
|
ProtocolManager.addLog("👁️ markVisibleMessagesAsRead skipped - dialog not active")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
val opponent = opponentKey ?: return
|
val opponent = opponentKey ?: return
|
||||||
val account = myPublicKey ?: return
|
val account = myPublicKey ?: return
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user