feat: Implement read receipt handling and mark all outgoing messages as read
This commit is contained in:
@@ -262,16 +262,20 @@ class MessageRepository private constructor(private val context: Context) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Обработка прочтения
|
* Обработка прочтения
|
||||||
|
* В Desktop PacketRead сообщает что собеседник прочитал наши сообщения
|
||||||
|
* fromPublicKey - кто прочитал (собеседник)
|
||||||
*/
|
*/
|
||||||
suspend fun handleRead(packet: PacketRead) {
|
suspend fun handleRead(packet: PacketRead) {
|
||||||
val account = currentAccount ?: return
|
val account = currentAccount ?: return
|
||||||
messageDao.markAsRead(account, packet.messageId)
|
|
||||||
|
|
||||||
// Обновляем кэш
|
// Отмечаем все наши исходящие сообщения к этому собеседнику как прочитанные
|
||||||
|
messageDao.markAllAsRead(account, packet.fromPublicKey)
|
||||||
|
|
||||||
|
// Обновляем кэш - все исходящие сообщения помечаем как прочитанные
|
||||||
val dialogKey = getDialogKey(packet.fromPublicKey)
|
val dialogKey = getDialogKey(packet.fromPublicKey)
|
||||||
messageCache[dialogKey]?.let { flow ->
|
messageCache[dialogKey]?.let { flow ->
|
||||||
flow.value = flow.value.map { msg ->
|
flow.value = flow.value.map { msg ->
|
||||||
if (msg.messageId == packet.messageId) msg.copy(isRead = true)
|
if (msg.isFromMe && !msg.isRead) msg.copy(isRead = true)
|
||||||
else msg
|
else msg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -221,6 +221,16 @@ interface MessageDao {
|
|||||||
*/
|
*/
|
||||||
@Query("SELECT EXISTS(SELECT 1 FROM messages WHERE account = :account AND message_id = :messageId)")
|
@Query("SELECT EXISTS(SELECT 1 FROM messages WHERE account = :account AND message_id = :messageId)")
|
||||||
suspend fun messageExists(account: String, messageId: String): Boolean
|
suspend fun messageExists(account: String, messageId: String): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Отметить все исходящие сообщения к собеседнику как прочитанные (delivered=3)
|
||||||
|
* Используется когда приходит PacketRead от собеседника
|
||||||
|
*/
|
||||||
|
@Query("""
|
||||||
|
UPDATE messages SET delivered = 3
|
||||||
|
WHERE account = :account AND to_public_key = :opponent AND from_me = 1 AND delivered < 3
|
||||||
|
""")
|
||||||
|
suspend fun markAllAsRead(account: String, opponent: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -335,17 +335,18 @@ class PacketMessage : Packet() {
|
|||||||
/**
|
/**
|
||||||
* Read packet (ID: 0x07)
|
* Read packet (ID: 0x07)
|
||||||
* Уведомление о прочтении сообщения
|
* Уведомление о прочтении сообщения
|
||||||
|
* Порядок полей как в Desktop: privateKey, fromPublicKey, toPublicKey
|
||||||
*/
|
*/
|
||||||
class PacketRead : Packet() {
|
class PacketRead : Packet() {
|
||||||
var messageId: String = ""
|
var privateKey: String = ""
|
||||||
var fromPublicKey: String = ""
|
var fromPublicKey: String = ""
|
||||||
var toPublicKey: String = ""
|
var toPublicKey: String = ""
|
||||||
var privateKey: String = ""
|
|
||||||
|
|
||||||
override fun getPacketId(): Int = 0x07
|
override fun getPacketId(): Int = 0x07
|
||||||
|
|
||||||
override fun receive(stream: Stream) {
|
override fun receive(stream: Stream) {
|
||||||
messageId = stream.readString()
|
// Desktop: privateKey, fromPublicKey, toPublicKey
|
||||||
|
privateKey = stream.readString()
|
||||||
fromPublicKey = stream.readString()
|
fromPublicKey = stream.readString()
|
||||||
toPublicKey = stream.readString()
|
toPublicKey = stream.readString()
|
||||||
}
|
}
|
||||||
@@ -353,10 +354,10 @@ class PacketRead : Packet() {
|
|||||||
override fun send(): Stream {
|
override fun send(): Stream {
|
||||||
val stream = Stream()
|
val stream = Stream()
|
||||||
stream.writeInt16(getPacketId())
|
stream.writeInt16(getPacketId())
|
||||||
stream.writeString(messageId)
|
// Desktop: privateKey, fromPublicKey, toPublicKey
|
||||||
|
stream.writeString(privateKey)
|
||||||
stream.writeString(fromPublicKey)
|
stream.writeString(fromPublicKey)
|
||||||
stream.writeString(toPublicKey)
|
stream.writeString(toPublicKey)
|
||||||
stream.writeString(privateKey)
|
|
||||||
return stream
|
return stream
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -422,6 +423,7 @@ class PacketTyping : Packet() {
|
|||||||
/**
|
/**
|
||||||
* Chunk packet (ID: 0x09)
|
* Chunk packet (ID: 0x09)
|
||||||
* Для разбиения больших пакетов на части (как в Desktop)
|
* Для разбиения больших пакетов на части (как в Desktop)
|
||||||
|
* ВАЖНО: chunkIndex и totalChunks - Int16, не Int32!
|
||||||
*/
|
*/
|
||||||
class PacketChunk : Packet() {
|
class PacketChunk : Packet() {
|
||||||
var chunkId: String = ""
|
var chunkId: String = ""
|
||||||
@@ -432,18 +434,20 @@ class PacketChunk : Packet() {
|
|||||||
override fun getPacketId(): Int = 0x09
|
override fun getPacketId(): Int = 0x09
|
||||||
|
|
||||||
override fun receive(stream: Stream) {
|
override fun receive(stream: Stream) {
|
||||||
|
// В Desktop: readInt16 для index и total
|
||||||
|
chunkIndex = stream.readInt16()
|
||||||
|
totalChunks = stream.readInt16()
|
||||||
chunkId = stream.readString()
|
chunkId = stream.readString()
|
||||||
chunkIndex = stream.readInt32()
|
|
||||||
totalChunks = stream.readInt32()
|
|
||||||
data = stream.readBytes()
|
data = stream.readBytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun send(): Stream {
|
override fun send(): Stream {
|
||||||
val stream = Stream()
|
val stream = Stream()
|
||||||
stream.writeInt16(getPacketId())
|
stream.writeInt16(getPacketId())
|
||||||
|
// В Desktop: writeInt16 для index и total
|
||||||
|
stream.writeInt16(chunkIndex)
|
||||||
|
stream.writeInt16(totalChunks)
|
||||||
stream.writeString(chunkId)
|
stream.writeString(chunkId)
|
||||||
stream.writeInt32(chunkIndex)
|
|
||||||
stream.writeInt32(totalChunks)
|
|
||||||
stream.writeBytes(data)
|
stream.writeBytes(data)
|
||||||
return stream
|
return stream
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,9 +87,10 @@ object ProtocolManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Обработчик прочтения (0x07)
|
// Обработчик прочтения (0x07)
|
||||||
|
// В Desktop PacketRead не содержит messageId - сообщает что собеседник прочитал сообщения
|
||||||
waitPacket(0x07) { packet ->
|
waitPacket(0x07) { packet ->
|
||||||
val readPacket = packet as PacketRead
|
val readPacket = packet as PacketRead
|
||||||
addLog("✓✓ Read: ${readPacket.messageId.take(16)}...")
|
addLog("✓✓ Read from: ${readPacket.fromPublicKey.take(16)}...")
|
||||||
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
messageRepository?.handleRead(readPacket)
|
messageRepository?.handleRead(readPacket)
|
||||||
|
|||||||
@@ -134,17 +134,28 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Прочитано
|
// Прочитано - пакет сообщает что собеседник прочитал наши сообщения
|
||||||
|
// В Desktop нет messageId - просто отмечаем все исходящие сообщения как прочитанные
|
||||||
ProtocolManager.waitPacket(0x07) { packet ->
|
ProtocolManager.waitPacket(0x07) { packet ->
|
||||||
val readPacket = packet as PacketRead
|
val readPacket = packet as PacketRead
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
// Обновляем в БД
|
// Если fromPublicKey == наш собеседник, значит он прочитал наши сообщения
|
||||||
updateMessageStatusInDb(readPacket.messageId, 3) // READ
|
if (readPacket.fromPublicKey == opponentKey) {
|
||||||
// Обновляем UI
|
// Обновляем все непрочитанные исходящие сообщения в БД
|
||||||
|
val account = myPublicKey ?: return@launch
|
||||||
|
val opponent = opponentKey ?: return@launch
|
||||||
|
messageDao.markAllAsRead(account, opponent)
|
||||||
|
|
||||||
|
// Обновляем UI - все исходящие сообщения помечаем как прочитанные
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
updateMessageStatus(readPacket.messageId, MessageStatus.READ)
|
_messages.value = _messages.value.map { msg ->
|
||||||
|
if (msg.isOutgoing && msg.status != MessageStatus.READ) {
|
||||||
|
msg.copy(status = MessageStatus.READ)
|
||||||
|
} else msg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ProtocolManager.addLog("✓✓ Read receipt from: ${readPacket.fromPublicKey.take(8)}...")
|
||||||
}
|
}
|
||||||
ProtocolManager.addLog("✓✓ Read: ${readPacket.messageId.take(8)}...")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -762,29 +773,31 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 👁️ Отправить подтверждение о прочтении сообщения
|
* 👁️ Отправить подтверждение о прочтении сообщения
|
||||||
|
* В Desktop PacketRead не содержит messageId - он просто сообщает что мы прочитали сообщения
|
||||||
*/
|
*/
|
||||||
fun sendReadReceipt(messageId: String, senderPublicKey: String) {
|
fun sendReadReceipt(messageId: String, senderPublicKey: String) {
|
||||||
// Не отправляем повторно
|
// Не отправляем повторно для этого собеседника
|
||||||
if (sentReadReceipts.contains(messageId)) return
|
val receiptKey = senderPublicKey
|
||||||
|
if (sentReadReceipts.contains(receiptKey)) return
|
||||||
|
|
||||||
val sender = myPublicKey ?: return
|
val sender = myPublicKey ?: return
|
||||||
val privateKey = myPrivateKey ?: return
|
val privateKey = myPrivateKey ?: return
|
||||||
|
|
||||||
sentReadReceipts.add(messageId)
|
sentReadReceipts.add(receiptKey)
|
||||||
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val privateKeyHash = CryptoManager.generatePrivateKeyHash(privateKey)
|
val privateKeyHash = CryptoManager.generatePrivateKeyHash(privateKey)
|
||||||
|
|
||||||
|
// Desktop формат: privateKey, fromPublicKey, toPublicKey
|
||||||
val packet = PacketRead().apply {
|
val packet = PacketRead().apply {
|
||||||
this.messageId = messageId
|
|
||||||
fromPublicKey = sender
|
|
||||||
toPublicKey = senderPublicKey
|
|
||||||
this.privateKey = privateKeyHash
|
this.privateKey = privateKeyHash
|
||||||
|
fromPublicKey = sender // Мы (кто прочитал)
|
||||||
|
toPublicKey = senderPublicKey // Кому отправляем уведомление
|
||||||
}
|
}
|
||||||
|
|
||||||
ProtocolManager.send(packet)
|
ProtocolManager.send(packet)
|
||||||
ProtocolManager.addLog("👁️ Read receipt sent for: ${messageId.take(8)}...")
|
ProtocolManager.addLog("👁️ Read receipt sent to: ${senderPublicKey.take(8)}...")
|
||||||
|
|
||||||
// Обновляем в БД что сообщение прочитано
|
// Обновляем в БД что сообщение прочитано
|
||||||
updateMessageReadInDb(messageId)
|
updateMessageReadInDb(messageId)
|
||||||
|
|||||||
Reference in New Issue
Block a user