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

@@ -186,3 +186,185 @@ class PacketOnlineSubscribe : Packet() {
return stream
}
}
// ============================================================================
// MESSAGE PACKETS - Как в React Native версии
// ============================================================================
/**
* Типы вложений
*/
enum class AttachmentType(val value: Int) {
IMAGE(0), // Изображение
MESSAGES(1), // Reply (цитата сообщения)
FILE(2); // Файл
companion object {
fun fromInt(value: Int) = entries.firstOrNull { it.value == value } ?: IMAGE
}
}
/**
* Статус доставки сообщения
*/
enum class DeliveryStatus(val value: Int) {
WAITING(0), // Ожидает отправки
DELIVERED(1), // Доставлено
ERROR(2); // Ошибка
companion object {
fun fromInt(value: Int) = entries.firstOrNull { it.value == value } ?: WAITING
}
}
/**
* Вложение к сообщению
*/
data class MessageAttachment(
val id: String,
val blob: String, // Base64 данные или пусто для CDN
val type: AttachmentType,
val preview: String = "", // Метаданные: "UUID::metadata" или "filesize::filename"
val width: Int = 0,
val height: Int = 0
)
/**
* Message packet (ID: 0x06)
* Отправка и получение сообщений
*/
class PacketMessage : Packet() {
var fromPublicKey: String = ""
var toPublicKey: String = ""
var content: String = "" // Зашифрованный текст
var chachaKey: String = "" // RSA зашифрованный ключ
var timestamp: Long = 0
var privateKey: String = "" // Hash приватного ключа (для авторизации)
var messageId: String = ""
var attachments: List<MessageAttachment> = emptyList()
override fun getPacketId(): Int = 0x06
override fun receive(stream: Stream) {
fromPublicKey = stream.readString()
toPublicKey = stream.readString()
content = stream.readString()
chachaKey = stream.readString()
timestamp = stream.readInt64()
privateKey = stream.readString()
messageId = stream.readString()
val attachmentCount = stream.readInt8()
val attachmentsList = mutableListOf<MessageAttachment>()
for (i in 0 until attachmentCount) {
attachmentsList.add(MessageAttachment(
id = stream.readString(),
preview = stream.readString(),
blob = stream.readString(),
type = AttachmentType.fromInt(stream.readInt8())
))
}
attachments = attachmentsList
}
override fun send(): Stream {
val stream = Stream()
stream.writeInt16(getPacketId())
stream.writeString(fromPublicKey)
stream.writeString(toPublicKey)
stream.writeString(content)
stream.writeString(chachaKey)
stream.writeInt64(timestamp)
stream.writeString(privateKey)
stream.writeString(messageId)
stream.writeInt8(attachments.size)
for (attachment in attachments) {
stream.writeString(attachment.id)
stream.writeString(attachment.preview)
stream.writeString(attachment.blob)
stream.writeInt8(attachment.type.value)
}
return stream
}
}
/**
* Read packet (ID: 0x07)
* Уведомление о прочтении сообщения
*/
class PacketRead : Packet() {
var messageId: String = ""
var fromPublicKey: String = ""
var toPublicKey: String = ""
var privateKey: String = ""
override fun getPacketId(): Int = 0x07
override fun receive(stream: Stream) {
messageId = stream.readString()
fromPublicKey = stream.readString()
toPublicKey = stream.readString()
}
override fun send(): Stream {
val stream = Stream()
stream.writeInt16(getPacketId())
stream.writeString(messageId)
stream.writeString(fromPublicKey)
stream.writeString(toPublicKey)
stream.writeString(privateKey)
return stream
}
}
/**
* Delivery packet (ID: 0x08)
* Уведомление о доставке сообщения
*/
class PacketDelivery : Packet() {
var messageId: String = ""
var toPublicKey: String = ""
override fun getPacketId(): Int = 0x08
override fun receive(stream: Stream) {
messageId = stream.readString()
toPublicKey = stream.readString()
}
override fun send(): Stream {
val stream = Stream()
stream.writeInt16(getPacketId())
stream.writeString(messageId)
stream.writeString(toPublicKey)
return stream
}
}
/**
* Typing packet (ID: 0x0B)
* Уведомление "печатает..."
*/
class PacketTyping : Packet() {
var fromPublicKey: String = ""
var toPublicKey: String = ""
var privateKey: String = ""
override fun getPacketId(): Int = 0x0B
override fun receive(stream: Stream) {
fromPublicKey = stream.readString()
toPublicKey = stream.readString()
}
override fun send(): Stream {
val stream = Stream()
stream.writeInt16(getPacketId())
stream.writeString(fromPublicKey)
stream.writeString(toPublicKey)
stream.writeString(privateKey)
return stream
}
}

View File

@@ -1,6 +1,9 @@
package com.rosetta.messenger.network
import android.content.Context
import android.util.Log
import com.rosetta.messenger.data.MessageRepository
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -18,11 +21,17 @@ object ProtocolManager {
private const val SERVER_ADDRESS = "ws://46.28.71.12:3000"
private var protocol: Protocol? = null
private var messageRepository: MessageRepository? = null
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
// Debug logs for dev console
private val _debugLogs = MutableStateFlow<List<String>>(emptyList())
val debugLogs: StateFlow<List<String>> = _debugLogs.asStateFlow()
// Typing status
private val _typingUsers = MutableStateFlow<Set<String>>(emptySet())
val typingUsers: StateFlow<Set<String>> = _typingUsers.asStateFlow()
private val dateFormat = SimpleDateFormat("HH:mm:ss.SSS", Locale.getDefault())
fun addLog(message: String) {
@@ -36,6 +45,62 @@ object ProtocolManager {
_debugLogs.value = emptyList()
}
/**
* Инициализация с контекстом для доступа к MessageRepository
*/
fun initialize(context: Context) {
messageRepository = MessageRepository.getInstance(context)
setupPacketHandlers()
}
/**
* Настройка обработчиков пакетов
*/
private fun setupPacketHandlers() {
// Обработчик входящих сообщений (0x06)
waitPacket(0x06) { packet ->
val messagePacket = packet as PacketMessage
addLog("📩 Incoming message from ${messagePacket.fromPublicKey.take(16)}...")
scope.launch {
messageRepository?.handleIncomingMessage(messagePacket)
}
}
// Обработчик доставки (0x08)
waitPacket(0x08) { packet ->
val deliveryPacket = packet as PacketDelivery
addLog("✓ Delivered: ${deliveryPacket.messageId.take(16)}...")
scope.launch {
messageRepository?.handleDelivery(deliveryPacket)
}
}
// Обработчик прочтения (0x07)
waitPacket(0x07) { packet ->
val readPacket = packet as PacketRead
addLog("✓✓ Read: ${readPacket.messageId.take(16)}...")
scope.launch {
messageRepository?.handleRead(readPacket)
}
}
// Обработчик typing (0x0B)
waitPacket(0x0B) { packet ->
val typingPacket = packet as PacketTyping
addLog("⌨️ Typing: ${typingPacket.fromPublicKey.take(16)}...")
// Добавляем в set и удаляем через 3 секунды
_typingUsers.value = _typingUsers.value + typingPacket.fromPublicKey
scope.launch {
delay(3000)
_typingUsers.value = _typingUsers.value - typingPacket.fromPublicKey
}
}
}
/**
* Get or create Protocol instance
*/
@@ -79,10 +144,17 @@ object ProtocolManager {
}
/**
* Send packet
* Send packet (simplified)
*/
fun send(packet: Packet) {
getProtocol().sendPacket(packet)
}
/**
* Send packet (legacy name)
*/
fun sendPacket(packet: Packet) {
getProtocol().sendPacket(packet)
send(packet)
}
/**
@@ -113,6 +185,7 @@ object ProtocolManager {
fun destroy() {
protocol?.destroy()
protocol = null
scope.cancel()
}
/**