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:
k1ngsterr1
2026-01-10 22:15:27 +05:00
parent 286706188b
commit 6014d23d69
12 changed files with 1643 additions and 142 deletions

View File

@@ -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
)
}