feat: Update RosettaDatabase to include Message and Dialog entities, increment version to 2
feat: Implement message packets for sending and receiving messages, including delivery and read notifications feat: Enhance ProtocolManager to handle message sending, delivery, and typing status with appropriate logging feat: Refactor ChatDetailScreen to utilize ChatViewModel for managing chat state and message input feat: Create ChatViewModel to manage chat messages, input state, and packet listeners for incoming messages build: Add KSP plugin for annotation processing and configure Java 17 for the build environment
This commit is contained in:
@@ -0,0 +1,305 @@
|
||||
package com.rosetta.messenger.database
|
||||
|
||||
import androidx.room.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* 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, // Расшифрованный текст (для быстрого доступа)
|
||||
|
||||
@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 // Верифицирован
|
||||
)
|
||||
|
||||
/**
|
||||
* DAO для работы с сообщениями
|
||||
*/
|
||||
@Dao
|
||||
interface MessageDao {
|
||||
|
||||
/**
|
||||
* Вставка нового сообщения
|
||||
*/
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insertMessage(message: MessageEntity): Long
|
||||
|
||||
/**
|
||||
* Вставка нескольких сообщений
|
||||
*/
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
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>
|
||||
|
||||
/**
|
||||
* Получить сообщения диалога как 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>>
|
||||
|
||||
/**
|
||||
* Получить последние 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)
|
||||
|
||||
/**
|
||||
* Обновить статус прочтения
|
||||
*/
|
||||
@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)
|
||||
|
||||
/**
|
||||
* Удалить все сообщения диалога
|
||||
*/
|
||||
@Query("DELETE FROM messages WHERE account = :account AND dialog_key = :dialogKey")
|
||||
suspend fun deleteDialog(account: String, dialogKey: String)
|
||||
|
||||
/**
|
||||
* Количество непрочитанных сообщений в диалоге
|
||||
*/
|
||||
@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
|
||||
}
|
||||
|
||||
/**
|
||||
* DAO для работы с диалогами
|
||||
*/
|
||||
@Dao
|
||||
interface DialogDao {
|
||||
|
||||
/**
|
||||
* Вставка/обновление диалога
|
||||
*/
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insertDialog(dialog: DialogEntity): Long
|
||||
|
||||
/**
|
||||
* Получить все диалоги отсортированные по последнему сообщению
|
||||
*/
|
||||
@Query("""
|
||||
SELECT * FROM dialogs
|
||||
WHERE account = :account
|
||||
ORDER BY last_message_timestamp DESC
|
||||
""")
|
||||
fun getDialogsFlow(account: String): Flow<List<DialogEntity>>
|
||||
|
||||
/**
|
||||
* Получить диалог
|
||||
*/
|
||||
@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
|
||||
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("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
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user