- Added lazy composition to skip setup until the screen is first opened, reducing allocations. - Cleaned up code formatting for better readability. - Enhanced comments for clarity on functionality. - Streamlined gesture handling logic for swipe detection and animation.
631 lines
26 KiB
Kotlin
631 lines
26 KiB
Kotlin
package com.rosetta.messenger.database
|
||
|
||
import androidx.room.*
|
||
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(
|
||
tableName = "messages",
|
||
indices =
|
||
[
|
||
Index(value = ["account", "from_public_key", "to_public_key", "timestamp"]),
|
||
Index(value = ["account", "message_id"], unique = true),
|
||
Index(value = ["account", "dialog_key", "timestamp"])]
|
||
)
|
||
data class MessageEntity(
|
||
@PrimaryKey(autoGenerate = true) val id: Long = 0,
|
||
@ColumnInfo(name = "account") val account: String, // Мой публичный ключ
|
||
@ColumnInfo(name = "from_public_key") val fromPublicKey: String, // Отправитель
|
||
@ColumnInfo(name = "to_public_key") val toPublicKey: String, // Получатель
|
||
@ColumnInfo(name = "content") val content: String, // Зашифрованное содержимое
|
||
@ColumnInfo(name = "timestamp") val timestamp: Long, // Unix timestamp
|
||
@ColumnInfo(name = "chacha_key") val chachaKey: String, // Зашифрованный ключ
|
||
@ColumnInfo(name = "read") val read: Int = 0, // Прочитано (0/1)
|
||
@ColumnInfo(name = "from_me") val fromMe: Int = 0, // Мое сообщение (0/1)
|
||
@ColumnInfo(name = "delivered")
|
||
val delivered: Int = 0, // Статус доставки (0=WAITING, 1=DELIVERED, 2=ERROR)
|
||
@ColumnInfo(name = "message_id") val messageId: String, // UUID сообщения
|
||
@ColumnInfo(name = "plain_message")
|
||
val plainMessage: String, // 🔒 Зашифрованный текст (encryptWithPassword) для хранения в БД
|
||
@ColumnInfo(name = "attachments") val attachments: String = "[]", // JSON массив вложений
|
||
@ColumnInfo(name = "reply_to_message_id")
|
||
val replyToMessageId: String? = null, // ID цитируемого сообщения
|
||
@ColumnInfo(name = "dialog_key") val dialogKey: String // Ключ диалога для быстрой выборки
|
||
)
|
||
|
||
/** Entity для диалогов (кэш последнего сообщения) */
|
||
@Entity(
|
||
tableName = "dialogs",
|
||
indices =
|
||
[
|
||
Index(value = ["account", "opponent_key"], unique = true),
|
||
Index(value = ["account", "last_message_timestamp"])]
|
||
)
|
||
data class DialogEntity(
|
||
@PrimaryKey(autoGenerate = true) val id: Long = 0,
|
||
@ColumnInfo(name = "account") val account: String, // Мой публичный ключ
|
||
@ColumnInfo(name = "opponent_key") val opponentKey: String, // Публичный ключ собеседника
|
||
@ColumnInfo(name = "opponent_title") val opponentTitle: String = "", // Имя собеседника
|
||
@ColumnInfo(name = "opponent_username")
|
||
val opponentUsername: String = "", // Username собеседника
|
||
@ColumnInfo(name = "last_message")
|
||
val lastMessage: String = "", // 🔒 Последнее сообщение (зашифрованное для превью)
|
||
@ColumnInfo(name = "last_message_timestamp")
|
||
val lastMessageTimestamp: Long = 0, // Timestamp последнего сообщения
|
||
@ColumnInfo(name = "unread_count") val unreadCount: Int = 0, // Количество непрочитанных
|
||
@ColumnInfo(name = "is_online") val isOnline: Int = 0, // Онлайн статус
|
||
@ColumnInfo(name = "last_seen") val lastSeen: Long = 0, // Последний раз онлайн
|
||
@ColumnInfo(name = "verified") val verified: Int = 0, // Верифицирован
|
||
@ColumnInfo(name = "i_have_sent", defaultValue = "0")
|
||
val iHaveSent: Int = 0, // Отправлял ли я сообщения в этот диалог (0/1)
|
||
@ColumnInfo(name = "last_message_from_me", defaultValue = "0")
|
||
val lastMessageFromMe: Int = 0, // Последнее сообщение от меня (0/1)
|
||
@ColumnInfo(name = "last_message_delivered", defaultValue = "0")
|
||
val lastMessageDelivered: Int =
|
||
0, // Статус доставки последнего сообщения (0=WAITING, 1=DELIVERED, 2=ERROR)
|
||
@ColumnInfo(name = "last_message_read", defaultValue = "0")
|
||
val lastMessageRead: Int = 0, // Прочитано последнее сообщение (0/1)
|
||
@ColumnInfo(name = "last_message_attachments", defaultValue = "[]")
|
||
val lastMessageAttachments: String =
|
||
"[]" // 📎 JSON attachments последнего сообщения (кэш из messages)
|
||
)
|
||
|
||
/** DAO для работы с сообщениями */
|
||
@Dao
|
||
interface MessageDao {
|
||
|
||
/** Вставка нового сообщения (IGNORE если уже существует) */
|
||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||
suspend fun insertMessage(message: MessageEntity): Long
|
||
|
||
/** Вставка нескольких сообщений (IGNORE если уже существуют) */
|
||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||
suspend fun insertMessages(messages: List<MessageEntity>)
|
||
|
||
/** Получить сообщения диалога (постранично) */
|
||
@Query(
|
||
"""
|
||
SELECT * FROM messages
|
||
WHERE account = :account AND dialog_key = :dialogKey
|
||
ORDER BY timestamp DESC
|
||
LIMIT :limit OFFSET :offset
|
||
"""
|
||
)
|
||
suspend fun getMessages(
|
||
account: String,
|
||
dialogKey: String,
|
||
limit: Int,
|
||
offset: Int
|
||
): List<MessageEntity>
|
||
|
||
/**
|
||
* 📁 Получить сообщения для Saved Messages (постранично) Специальный метод для случая когда
|
||
* from_public_key = to_public_key = account Использует упрощенный запрос без дублирования OR
|
||
* условий
|
||
*/
|
||
@Query(
|
||
"""
|
||
SELECT * FROM messages
|
||
WHERE account = :account
|
||
AND from_public_key = :account
|
||
AND to_public_key = :account
|
||
ORDER BY timestamp DESC
|
||
LIMIT :limit OFFSET :offset
|
||
"""
|
||
)
|
||
suspend fun getMessagesForSavedDialog(
|
||
account: String,
|
||
limit: Int,
|
||
offset: Int
|
||
): List<MessageEntity>
|
||
|
||
/** 📁 Получить количество сообщений в Saved Messages */
|
||
@Query(
|
||
"""
|
||
SELECT COUNT(*) FROM messages
|
||
WHERE account = :account
|
||
AND from_public_key = :account
|
||
AND to_public_key = :account
|
||
"""
|
||
)
|
||
suspend fun getMessageCountForSavedDialog(account: String): Int
|
||
|
||
/** Получить сообщения диалога как Flow */
|
||
@Query(
|
||
"""
|
||
SELECT * FROM messages
|
||
WHERE account = :account AND dialog_key = :dialogKey
|
||
ORDER BY timestamp ASC
|
||
"""
|
||
)
|
||
fun getMessagesFlow(account: String, dialogKey: String): Flow<List<MessageEntity>>
|
||
|
||
/** Получить количество сообщений в диалоге */
|
||
@Query(
|
||
"""
|
||
SELECT COUNT(*) FROM messages
|
||
WHERE account = :account AND dialog_key = :dialogKey
|
||
"""
|
||
)
|
||
suspend fun getMessageCount(account: String, dialogKey: String): Int
|
||
|
||
/**
|
||
* 📸 Получить количество сообщений между двумя пользователями (для проверки первого сообщения
|
||
* при отправке аватара)
|
||
*/
|
||
@Query(
|
||
"""
|
||
SELECT COUNT(*) FROM messages
|
||
WHERE account = :account
|
||
AND ((from_public_key = :sender AND to_public_key = :recipient)
|
||
OR (from_public_key = :recipient AND to_public_key = :sender))
|
||
"""
|
||
)
|
||
suspend fun getMessageCount(account: String, sender: String, recipient: String): Int
|
||
|
||
/** Получить последние N сообщений диалога */
|
||
@Query(
|
||
"""
|
||
SELECT * FROM messages
|
||
WHERE account = :account AND dialog_key = :dialogKey
|
||
ORDER BY timestamp DESC
|
||
LIMIT :limit
|
||
"""
|
||
)
|
||
suspend fun getRecentMessages(
|
||
account: String,
|
||
dialogKey: String,
|
||
limit: Int
|
||
): List<MessageEntity>
|
||
|
||
/** Найти сообщение по ID */
|
||
@Query("SELECT * FROM messages WHERE account = :account AND message_id = :messageId LIMIT 1")
|
||
suspend fun getMessageById(account: String, messageId: String): MessageEntity?
|
||
|
||
/** Обновить статус доставки */
|
||
@Query(
|
||
"UPDATE messages SET delivered = :status WHERE account = :account AND message_id = :messageId"
|
||
)
|
||
suspend fun updateDeliveryStatus(account: String, messageId: String, status: Int)
|
||
|
||
/** 🔄 Обновить статус доставки и attachments (для очистки localUri после отправки) */
|
||
@Query(
|
||
"UPDATE messages SET delivered = :status, attachments = :attachments WHERE account = :account AND message_id = :messageId"
|
||
)
|
||
suspend fun updateDeliveryStatusAndAttachments(
|
||
account: String,
|
||
messageId: String,
|
||
status: Int,
|
||
attachments: String
|
||
)
|
||
|
||
/** Обновить статус прочтения */
|
||
@Query("UPDATE messages SET read = 1 WHERE account = :account AND message_id = :messageId")
|
||
suspend fun markAsRead(account: String, messageId: String)
|
||
|
||
/** Отметить все сообщения диалога как прочитанные */
|
||
@Query(
|
||
"""
|
||
UPDATE messages SET read = 1
|
||
WHERE account = :account AND dialog_key = :dialogKey AND from_me = 0
|
||
"""
|
||
)
|
||
suspend fun markDialogAsRead(account: String, dialogKey: String)
|
||
|
||
/** Удалить сообщение */
|
||
@Query("DELETE FROM messages WHERE account = :account AND message_id = :messageId")
|
||
suspend fun deleteMessage(account: String, messageId: String)
|
||
|
||
/**
|
||
* Найти сообщение по публичному ключу отправителя и timestamp (для reply) Ищет с допуском по
|
||
* времени для учета возможных рассинхронизаций
|
||
*/
|
||
@Query(
|
||
"""
|
||
SELECT * FROM messages
|
||
WHERE account = :account
|
||
AND dialog_key = :dialogKey
|
||
AND from_public_key = :fromPublicKey
|
||
AND timestamp BETWEEN :timestampFrom AND :timestampTo
|
||
ORDER BY timestamp ASC
|
||
LIMIT 1
|
||
"""
|
||
)
|
||
suspend fun findMessageByContent(
|
||
account: String,
|
||
dialogKey: String,
|
||
fromPublicKey: String,
|
||
timestampFrom: Long,
|
||
timestampTo: Long
|
||
): MessageEntity?
|
||
|
||
/**
|
||
* Получить количество непрочитанных сообщений для диалога Считает только входящие сообщения
|
||
* (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
|
||
|
||
/** Удалить все сообщения диалога (возвращает количество удалённых) */
|
||
@Query("DELETE FROM messages WHERE account = :account AND dialog_key = :dialogKey")
|
||
suspend fun deleteDialog(account: String, dialogKey: String): Int
|
||
|
||
/** Удалить все сообщения между двумя пользователями (возвращает количество удалённых) */
|
||
@Query(
|
||
"""
|
||
DELETE FROM messages
|
||
WHERE account = :account AND (
|
||
(from_public_key = :user1 AND to_public_key = :user2) OR
|
||
(from_public_key = :user2 AND to_public_key = :user1)
|
||
)
|
||
"""
|
||
)
|
||
suspend fun deleteMessagesBetweenUsers(account: String, user1: String, user2: String): Int
|
||
|
||
/** Количество непрочитанных сообщений в диалоге */
|
||
@Query(
|
||
"""
|
||
SELECT COUNT(*) FROM messages
|
||
WHERE account = :account AND dialog_key = :dialogKey AND from_me = 0 AND read = 0
|
||
"""
|
||
)
|
||
suspend fun getUnreadCount(account: String, dialogKey: String): Int
|
||
|
||
/** Проверить существование сообщения */
|
||
@Query(
|
||
"SELECT EXISTS(SELECT 1 FROM messages WHERE account = :account AND message_id = :messageId)"
|
||
)
|
||
suspend fun messageExists(account: String, messageId: String): Boolean
|
||
|
||
/**
|
||
* Отметить все исходящие сообщения к собеседнику как прочитанные Используется когда приходит
|
||
* PacketRead от собеседника 🔥 ВАЖНО: delivered=3 означает READ (синхронизировано с
|
||
* ChatViewModel)
|
||
*/
|
||
@Query(
|
||
"""
|
||
UPDATE messages SET delivered = 3, read = 1
|
||
WHERE account = :account AND to_public_key = :opponent AND from_me = 1
|
||
"""
|
||
)
|
||
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?
|
||
|
||
/**
|
||
* 📎 Получить attachments последнего сообщения для диалога Возвращает null если сообщений нет
|
||
* или нет attachments
|
||
*/
|
||
@Query(
|
||
"""
|
||
SELECT attachments 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 getLastMessageAttachments(account: String, opponent: String): String?
|
||
}
|
||
|
||
/** DAO для работы с диалогами */
|
||
@Dao
|
||
interface DialogDao {
|
||
|
||
/** Вставка/обновление диалога */
|
||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||
suspend fun insertDialog(dialog: DialogEntity): Long
|
||
|
||
/**
|
||
* Получить все диалоги отсортированные по последнему сообщению Исключает requests (диалоги без
|
||
* исходящих сообщений от нас) Исключает пустые диалоги (без сообщений)
|
||
*/
|
||
@Query(
|
||
"""
|
||
SELECT * FROM dialogs
|
||
WHERE account = :account
|
||
AND i_have_sent = 1
|
||
AND last_message_timestamp > 0
|
||
ORDER BY last_message_timestamp DESC
|
||
"""
|
||
)
|
||
fun getDialogsFlow(account: String): Flow<List<DialogEntity>>
|
||
|
||
/**
|
||
* Получить requests - диалоги где нам писали, но мы не отвечали Исключает пустые диалоги (без
|
||
* сообщений)
|
||
*/
|
||
@Query(
|
||
"""
|
||
SELECT * FROM dialogs
|
||
WHERE account = :account
|
||
AND i_have_sent = 0
|
||
AND last_message_timestamp > 0
|
||
ORDER BY last_message_timestamp DESC
|
||
"""
|
||
)
|
||
fun getRequestsFlow(account: String): Flow<List<DialogEntity>>
|
||
|
||
/** Получить количество requests Исключает пустые диалоги (без сообщений) */
|
||
@Query(
|
||
"""
|
||
SELECT COUNT(*) FROM dialogs
|
||
WHERE account = :account
|
||
AND i_have_sent = 0
|
||
AND last_message_timestamp > 0
|
||
"""
|
||
)
|
||
fun getRequestsCountFlow(account: String): Flow<Int>
|
||
|
||
/** Получить диалог */
|
||
@Query("SELECT * FROM dialogs WHERE account = :account AND opponent_key = :opponentKey LIMIT 1")
|
||
suspend fun getDialog(account: String, opponentKey: String): DialogEntity?
|
||
|
||
/** Обновить последнее сообщение */
|
||
@Query(
|
||
"""
|
||
UPDATE dialogs SET
|
||
last_message = :lastMessage,
|
||
last_message_timestamp = :timestamp
|
||
WHERE account = :account AND opponent_key = :opponentKey
|
||
"""
|
||
)
|
||
suspend fun updateLastMessage(
|
||
account: String,
|
||
opponentKey: String,
|
||
lastMessage: String,
|
||
timestamp: Long
|
||
)
|
||
|
||
/** Обновить количество непрочитанных */
|
||
@Query(
|
||
"UPDATE dialogs SET unread_count = :count WHERE account = :account AND opponent_key = :opponentKey"
|
||
)
|
||
suspend fun updateUnreadCount(account: String, opponentKey: String, count: Int)
|
||
|
||
/** Инкрементировать непрочитанные */
|
||
@Query(
|
||
"UPDATE dialogs SET unread_count = unread_count + 1 WHERE account = :account AND opponent_key = :opponentKey"
|
||
)
|
||
suspend fun incrementUnreadCount(account: String, opponentKey: String)
|
||
|
||
/** Сбросить непрочитанные */
|
||
@Query(
|
||
"UPDATE dialogs SET unread_count = 0 WHERE account = :account AND opponent_key = :opponentKey"
|
||
)
|
||
suspend fun clearUnreadCount(account: String, opponentKey: String)
|
||
|
||
/** Отметить что я отправлял сообщения в этот диалог Возвращает количество обновлённых строк */
|
||
@Query(
|
||
"UPDATE dialogs SET i_have_sent = 1 WHERE account = :account AND opponent_key = :opponentKey"
|
||
)
|
||
suspend fun markIHaveSent(account: String, opponentKey: String): Int
|
||
|
||
/** Обновить онлайн статус */
|
||
@Query(
|
||
"""
|
||
UPDATE dialogs SET
|
||
is_online = :isOnline,
|
||
last_seen = :lastSeen
|
||
WHERE account = :account AND opponent_key = :opponentKey
|
||
"""
|
||
)
|
||
suspend fun updateOnlineStatus(
|
||
account: String,
|
||
opponentKey: String,
|
||
isOnline: Int,
|
||
lastSeen: Long
|
||
)
|
||
|
||
/** Получить онлайн статус пользователя */
|
||
@Query(
|
||
"""
|
||
SELECT is_online, last_seen
|
||
FROM dialogs
|
||
WHERE account = :account AND opponent_key = :opponentKey
|
||
LIMIT 1
|
||
"""
|
||
)
|
||
fun observeOnlineStatus(account: String, opponentKey: String): Flow<OnlineStatusInfo?>
|
||
|
||
data class OnlineStatusInfo(
|
||
@ColumnInfo(name = "is_online") val isOnline: Int,
|
||
@ColumnInfo(name = "last_seen") val lastSeen: Long
|
||
)
|
||
|
||
/** Удалить диалог */
|
||
@Query("DELETE FROM dialogs WHERE account = :account AND opponent_key = :opponentKey")
|
||
suspend fun deleteDialog(account: String, opponentKey: String)
|
||
|
||
/** Обновить информацию о собеседнике */
|
||
@Query(
|
||
"""
|
||
UPDATE dialogs SET
|
||
opponent_title = :title,
|
||
opponent_username = :username,
|
||
verified = :verified
|
||
WHERE account = :account AND opponent_key = :opponentKey
|
||
"""
|
||
)
|
||
suspend fun updateOpponentInfo(
|
||
account: String,
|
||
opponentKey: String,
|
||
title: String,
|
||
username: String,
|
||
verified: Int
|
||
)
|
||
|
||
/**
|
||
* Получить общее количество непрочитанных сообщений, исключая указанный диалог Используется для
|
||
* отображения badge на кнопке "назад" в экране чата
|
||
*/
|
||
@Query(
|
||
"""
|
||
SELECT COALESCE(SUM(unread_count), 0) FROM dialogs
|
||
WHERE account = :account AND opponent_key != :excludeOpponentKey
|
||
"""
|
||
)
|
||
fun getTotalUnreadCountExcludingFlow(account: String, excludeOpponentKey: String): Flow<Int>
|
||
|
||
/** 🚀 Получить последнее сообщение диалога по dialog_key (использует индекс) */
|
||
@Query(
|
||
"""
|
||
SELECT * FROM messages
|
||
WHERE account = :account AND dialog_key = :dialogKey
|
||
ORDER BY timestamp DESC, id DESC LIMIT 1
|
||
"""
|
||
)
|
||
suspend fun getLastMessageByDialogKey(account: String, dialogKey: String): MessageEntity?
|
||
|
||
/** 🚀 Количество непрочитанных входящих сообщений от оппонента */
|
||
@Query(
|
||
"""
|
||
SELECT COUNT(*) FROM messages
|
||
WHERE account = :account
|
||
AND from_public_key = :opponentKey
|
||
AND to_public_key = :account
|
||
AND from_me = 0
|
||
AND read = 0
|
||
"""
|
||
)
|
||
suspend fun countUnreadFromOpponent(account: String, opponentKey: String): Int
|
||
|
||
/** 🚀 Проверить есть ли исходящие сообщения к оппоненту */
|
||
@Query(
|
||
"""
|
||
SELECT EXISTS(
|
||
SELECT 1 FROM messages
|
||
WHERE account = :account
|
||
AND from_public_key = :account
|
||
AND to_public_key = :opponentKey
|
||
AND from_me = 1
|
||
LIMIT 1
|
||
)
|
||
"""
|
||
)
|
||
suspend fun hasSentToOpponent(account: String, opponentKey: String): Boolean
|
||
|
||
/**
|
||
* 🚀 ОПТИМИЗИРОВАННЫЙ updateDialogFromMessages Заменяет монолитный SQL с 9 коррелированными
|
||
* подзапросами на:
|
||
* - 3 индексированных запроса (getLastMessageByDialogKey, countUnreadFromOpponent,
|
||
* hasSentToOpponent)
|
||
* - 1 SELECT существующего диалога для сохранения метаданных
|
||
* - 1 INSERT OR REPLACE Всё обёрнуто в транзакцию для консистентности. Использует dialog_key с
|
||
* индексом (account, dialog_key, timestamp) вместо OR-условий.
|
||
*/
|
||
@Transaction
|
||
suspend fun updateDialogFromMessages(account: String, opponentKey: String) {
|
||
val dialogKey =
|
||
if (account < opponentKey) "$account:$opponentKey" else "$opponentKey:$account"
|
||
|
||
// 1. Получаем последнее сообщение — O(1) по индексу (account, dialog_key, timestamp)
|
||
val lastMsg = getLastMessageByDialogKey(account, dialogKey) ?: return
|
||
|
||
// 2. Получаем существующий диалог для сохранения метаданных (online, verified, title...)
|
||
val existing = getDialog(account, opponentKey)
|
||
|
||
// 3. Считаем непрочитанные — O(N) по индексу (account, from_public_key, to_public_key,
|
||
// timestamp)
|
||
val unread = countUnreadFromOpponent(account, opponentKey)
|
||
|
||
// 4. Проверяем были ли исходящие — O(1)
|
||
val hasSent = hasSentToOpponent(account, opponentKey)
|
||
|
||
// 5. Один INSERT OR REPLACE с вычисленными данными
|
||
insertDialog(
|
||
DialogEntity(
|
||
id = existing?.id ?: 0,
|
||
account = account,
|
||
opponentKey = opponentKey,
|
||
opponentTitle = existing?.opponentTitle ?: "",
|
||
opponentUsername = existing?.opponentUsername ?: "",
|
||
lastMessage = lastMsg.plainMessage,
|
||
lastMessageTimestamp = lastMsg.timestamp,
|
||
unreadCount = unread,
|
||
isOnline = existing?.isOnline ?: 0,
|
||
lastSeen = existing?.lastSeen ?: 0,
|
||
verified = existing?.verified ?: 0,
|
||
iHaveSent = if (hasSent) 1 else (existing?.iHaveSent ?: 0),
|
||
lastMessageFromMe = lastMsg.fromMe,
|
||
lastMessageDelivered = if (lastMsg.fromMe == 1) lastMsg.delivered else 0,
|
||
lastMessageRead = if (lastMsg.fromMe == 1) lastMsg.read else 0,
|
||
lastMessageAttachments = lastMsg.attachments
|
||
)
|
||
)
|
||
}
|
||
|
||
/**
|
||
* 📁 ОПТИМИЗИРОВАННЫЙ updateSavedMessagesDialogFromMessages Для saved messages (opponentKey ==
|
||
* account)
|
||
*/
|
||
@Transaction
|
||
suspend fun updateSavedMessagesDialogFromMessages(account: String) {
|
||
val dialogKey = "$account:$account"
|
||
|
||
val lastMsg = getLastMessageByDialogKey(account, dialogKey) ?: return
|
||
val existing = getDialog(account, account)
|
||
|
||
insertDialog(
|
||
DialogEntity(
|
||
id = existing?.id ?: 0,
|
||
account = account,
|
||
opponentKey = account,
|
||
opponentTitle = existing?.opponentTitle ?: "",
|
||
opponentUsername = existing?.opponentUsername ?: "",
|
||
lastMessage = lastMsg.plainMessage,
|
||
lastMessageTimestamp = lastMsg.timestamp,
|
||
unreadCount = 0,
|
||
isOnline = existing?.isOnline ?: 0,
|
||
lastSeen = existing?.lastSeen ?: 0,
|
||
verified = existing?.verified ?: 0,
|
||
iHaveSent = 1,
|
||
lastMessageFromMe = 1,
|
||
lastMessageDelivered = 1,
|
||
lastMessageRead = 1,
|
||
lastMessageAttachments = lastMsg.attachments
|
||
)
|
||
)
|
||
}
|
||
}
|