feat: Optimize ChatViewModel with pagination and enhanced message loading
This commit is contained in:
@@ -7,26 +7,39 @@ import androidx.lifecycle.viewModelScope
|
|||||||
import com.rosetta.messenger.crypto.CryptoManager
|
import com.rosetta.messenger.crypto.CryptoManager
|
||||||
import com.rosetta.messenger.crypto.MessageCrypto
|
import com.rosetta.messenger.crypto.MessageCrypto
|
||||||
import com.rosetta.messenger.database.DialogEntity
|
import com.rosetta.messenger.database.DialogEntity
|
||||||
|
import com.rosetta.messenger.database.MessageEntity
|
||||||
import com.rosetta.messenger.database.RosettaDatabase
|
import com.rosetta.messenger.database.RosettaDatabase
|
||||||
import com.rosetta.messenger.network.*
|
import com.rosetta.messenger.network.*
|
||||||
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ViewModel для экрана чата - упрощенная рабочая версия
|
* ViewModel для экрана чата - оптимизированная версия
|
||||||
* Без зависимости от MessageRepository
|
* 🚀 Особенности:
|
||||||
|
* - Dispatchers.IO для всех тяжёлых операций
|
||||||
|
* - Пагинация сообщений
|
||||||
|
* - Chunked decryption (расшифровка пачками)
|
||||||
|
* - Кэширование расшифрованных сообщений
|
||||||
|
* - Flow для реактивных обновлений без блокировки UI
|
||||||
*/
|
*/
|
||||||
class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "ChatViewModel"
|
private const val TAG = "ChatViewModel"
|
||||||
|
private const val PAGE_SIZE = 30
|
||||||
|
private const val DECRYPT_CHUNK_SIZE = 5 // Расшифровываем по 5 сообщений за раз
|
||||||
}
|
}
|
||||||
|
|
||||||
// Database
|
// Database
|
||||||
private val database = RosettaDatabase.getDatabase(application)
|
private val database = RosettaDatabase.getDatabase(application)
|
||||||
private val dialogDao = database.dialogDao()
|
private val dialogDao = database.dialogDao()
|
||||||
|
private val messageDao = database.messageDao()
|
||||||
|
|
||||||
|
// 🔥 Кэш расшифрованных сообщений (messageId -> plainText)
|
||||||
|
private val decryptionCache = ConcurrentHashMap<String, String>()
|
||||||
|
|
||||||
// Информация о собеседнике
|
// Информация о собеседнике
|
||||||
private var opponentTitle: String = ""
|
private var opponentTitle: String = ""
|
||||||
@@ -37,13 +50,16 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
private var myPublicKey: String? = null
|
private var myPublicKey: String? = null
|
||||||
private var myPrivateKey: String? = null
|
private var myPrivateKey: String? = null
|
||||||
|
|
||||||
// UI State - сообщения хранятся локально в памяти
|
// UI State
|
||||||
private val _messages = MutableStateFlow<List<ChatMessage>>(emptyList())
|
private val _messages = MutableStateFlow<List<ChatMessage>>(emptyList())
|
||||||
val messages: StateFlow<List<ChatMessage>> = _messages.asStateFlow()
|
val messages: StateFlow<List<ChatMessage>> = _messages.asStateFlow()
|
||||||
|
|
||||||
private val _isLoading = MutableStateFlow(false)
|
private val _isLoading = MutableStateFlow(false)
|
||||||
val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()
|
val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()
|
||||||
|
|
||||||
|
private val _isLoadingMore = MutableStateFlow(false)
|
||||||
|
val isLoadingMore: StateFlow<Boolean> = _isLoadingMore.asStateFlow()
|
||||||
|
|
||||||
private val _opponentTyping = MutableStateFlow(false)
|
private val _opponentTyping = MutableStateFlow(false)
|
||||||
val opponentTyping: StateFlow<Boolean> = _opponentTyping.asStateFlow()
|
val opponentTyping: StateFlow<Boolean> = _opponentTyping.asStateFlow()
|
||||||
|
|
||||||
@@ -51,9 +67,17 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
private val _inputText = MutableStateFlow("")
|
private val _inputText = MutableStateFlow("")
|
||||||
val inputText: StateFlow<String> = _inputText.asStateFlow()
|
val inputText: StateFlow<String> = _inputText.asStateFlow()
|
||||||
|
|
||||||
|
// Пагинация
|
||||||
|
private var currentOffset = 0
|
||||||
|
private var hasMoreMessages = true
|
||||||
|
private var isLoadingMessages = false
|
||||||
|
|
||||||
// Защита от двойной отправки
|
// Защита от двойной отправки
|
||||||
private var isSending = false
|
private var isSending = false
|
||||||
|
|
||||||
|
// Job для отмены загрузки при смене диалога
|
||||||
|
private var loadingJob: Job? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
setupPacketListeners()
|
setupPacketListeners()
|
||||||
}
|
}
|
||||||
@@ -97,18 +121,62 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handleIncomingMessage(packet: PacketMessage) {
|
private fun handleIncomingMessage(packet: PacketMessage) {
|
||||||
try {
|
// 🚀 Обработка входящего сообщения в IO потоке
|
||||||
val message = ChatMessage(
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
id = packet.messageId,
|
try {
|
||||||
text = "[Encrypted] ${packet.content.take(20)}...",
|
val privateKey = myPrivateKey ?: return@launch
|
||||||
isOutgoing = packet.fromPublicKey == myPublicKey,
|
|
||||||
timestamp = Date(packet.timestamp),
|
ProtocolManager.addLog("📩 Incoming message: ${packet.messageId.take(8)}...")
|
||||||
status = MessageStatus.DELIVERED
|
|
||||||
)
|
// Расшифровываем в фоне
|
||||||
_messages.value = _messages.value + message
|
val decryptedText = MessageCrypto.decryptIncoming(
|
||||||
ProtocolManager.addLog("📩 Incoming: ${packet.messageId.take(8)}...")
|
packet.content,
|
||||||
} catch (e: Exception) {
|
packet.chachaKey,
|
||||||
ProtocolManager.addLog("❌ Error: ${e.message}")
|
privateKey
|
||||||
|
)
|
||||||
|
|
||||||
|
// Кэшируем расшифрованный текст
|
||||||
|
decryptionCache[packet.messageId] = decryptedText
|
||||||
|
|
||||||
|
ProtocolManager.addLog("✅ Decrypted: ${decryptedText.take(20)}...")
|
||||||
|
|
||||||
|
// Обновляем UI в Main потоке
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
val message = ChatMessage(
|
||||||
|
id = packet.messageId,
|
||||||
|
text = decryptedText,
|
||||||
|
isOutgoing = packet.fromPublicKey == myPublicKey,
|
||||||
|
timestamp = Date(packet.timestamp),
|
||||||
|
status = MessageStatus.DELIVERED
|
||||||
|
)
|
||||||
|
_messages.value = _messages.value + message
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сохраняем в БД (уже в IO потоке)
|
||||||
|
saveMessageToDatabase(
|
||||||
|
messageId = packet.messageId,
|
||||||
|
text = decryptedText,
|
||||||
|
encryptedContent = packet.content,
|
||||||
|
encryptedKey = packet.chachaKey,
|
||||||
|
timestamp = packet.timestamp,
|
||||||
|
isFromMe = false,
|
||||||
|
delivered = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
// Обновляем диалог
|
||||||
|
saveDialog(decryptedText, packet.timestamp)
|
||||||
|
|
||||||
|
// Отправляем подтверждение доставки
|
||||||
|
val deliveryPacket = PacketDelivery().apply {
|
||||||
|
toPublicKey = packet.fromPublicKey
|
||||||
|
messageId = packet.messageId
|
||||||
|
}
|
||||||
|
ProtocolManager.send(deliveryPacket)
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
ProtocolManager.addLog("❌ Error handling incoming message: ${e.message}")
|
||||||
|
Log.e(TAG, "Incoming message error", e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,11 +203,154 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
ProtocolManager.addLog("💬 Dialog already open: ${publicKey.take(16)}...")
|
ProtocolManager.addLog("💬 Dialog already open: ${publicKey.take(16)}...")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Отменяем предыдущую загрузку
|
||||||
|
loadingJob?.cancel()
|
||||||
|
|
||||||
opponentKey = publicKey
|
opponentKey = publicKey
|
||||||
opponentTitle = title
|
opponentTitle = title
|
||||||
opponentUsername = username
|
opponentUsername = username
|
||||||
|
|
||||||
|
// Сбрасываем состояние
|
||||||
_messages.value = emptyList()
|
_messages.value = emptyList()
|
||||||
|
currentOffset = 0
|
||||||
|
hasMoreMessages = true
|
||||||
|
isLoadingMessages = false
|
||||||
|
|
||||||
ProtocolManager.addLog("💬 Dialog opened: ${title.ifEmpty { publicKey.take(16) }}...")
|
ProtocolManager.addLog("💬 Dialog opened: ${title.ifEmpty { publicKey.take(16) }}...")
|
||||||
|
|
||||||
|
// Загружаем сообщения из БД
|
||||||
|
loadMessagesFromDatabase()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 🚀 Оптимизированная загрузка сообщений с пагинацией
|
||||||
|
*/
|
||||||
|
private fun loadMessagesFromDatabase() {
|
||||||
|
val account = myPublicKey ?: return
|
||||||
|
val opponent = opponentKey ?: return
|
||||||
|
|
||||||
|
if (isLoadingMessages) return
|
||||||
|
isLoadingMessages = true
|
||||||
|
|
||||||
|
loadingJob = viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
_isLoading.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val dialogKey = getDialogKey(account, opponent)
|
||||||
|
ProtocolManager.addLog("📂 Loading messages from DB for dialog: $dialogKey")
|
||||||
|
|
||||||
|
// Получаем первую страницу сообщений
|
||||||
|
val entities = messageDao.getMessages(account, dialogKey, limit = PAGE_SIZE, offset = 0)
|
||||||
|
|
||||||
|
ProtocolManager.addLog("📂 Loaded ${entities.size} messages from DB")
|
||||||
|
|
||||||
|
hasMoreMessages = entities.size >= PAGE_SIZE
|
||||||
|
currentOffset = entities.size
|
||||||
|
|
||||||
|
// 🔥 Быстрая конвертация без расшифровки (plainMessage уже есть в БД)
|
||||||
|
val messages = entities.map { entity ->
|
||||||
|
entityToChatMessage(entity)
|
||||||
|
}.reversed()
|
||||||
|
|
||||||
|
// Обновляем UI в Main потоке
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
_messages.value = messages
|
||||||
|
_isLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoadingMessages = false
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
ProtocolManager.addLog("❌ Error loading messages: ${e.message}")
|
||||||
|
Log.e(TAG, "Error loading messages", e)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
_isLoading.value = false
|
||||||
|
}
|
||||||
|
isLoadingMessages = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 🚀 Загрузка следующей страницы (для бесконечной прокрутки)
|
||||||
|
*/
|
||||||
|
fun loadMoreMessages() {
|
||||||
|
val account = myPublicKey ?: return
|
||||||
|
val opponent = opponentKey ?: return
|
||||||
|
|
||||||
|
if (!hasMoreMessages || isLoadingMessages) return
|
||||||
|
isLoadingMessages = true
|
||||||
|
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
_isLoadingMore.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val dialogKey = getDialogKey(account, opponent)
|
||||||
|
|
||||||
|
val entities = messageDao.getMessages(account, dialogKey, limit = PAGE_SIZE, offset = currentOffset)
|
||||||
|
|
||||||
|
hasMoreMessages = entities.size >= PAGE_SIZE
|
||||||
|
currentOffset += entities.size
|
||||||
|
|
||||||
|
if (entities.isNotEmpty()) {
|
||||||
|
val newMessages = entities.map { entity ->
|
||||||
|
entityToChatMessage(entity)
|
||||||
|
}.reversed()
|
||||||
|
|
||||||
|
// Добавляем в начало списка (старые сообщения)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
_messages.value = newMessages + _messages.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
_isLoadingMore.value = false
|
||||||
|
}
|
||||||
|
isLoadingMessages = false
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error loading more messages", e)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
_isLoadingMore.value = false
|
||||||
|
}
|
||||||
|
isLoadingMessages = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 🔥 Быстрая конвертация Entity -> ChatMessage
|
||||||
|
*/
|
||||||
|
private fun entityToChatMessage(entity: MessageEntity): ChatMessage {
|
||||||
|
return ChatMessage(
|
||||||
|
id = entity.messageId,
|
||||||
|
text = entity.plainMessage, // Уже расшифровано при сохранении
|
||||||
|
isOutgoing = entity.fromMe == 1,
|
||||||
|
timestamp = Date(entity.timestamp),
|
||||||
|
status = when (entity.delivered) {
|
||||||
|
0 -> MessageStatus.SENDING
|
||||||
|
1 -> MessageStatus.DELIVERED
|
||||||
|
2 -> MessageStatus.SENT // Changed from ERROR to SENT
|
||||||
|
3 -> MessageStatus.READ
|
||||||
|
else -> MessageStatus.SENT
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить ключ диалога для группировки сообщений
|
||||||
|
*/
|
||||||
|
private fun getDialogKey(account: String, opponent: String): String {
|
||||||
|
return if (account < opponent) {
|
||||||
|
"$account:$opponent"
|
||||||
|
} else {
|
||||||
|
"$opponent:$account"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -150,7 +361,10 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Отправить сообщение - Optimistic UI
|
* 🚀 Оптимизированная отправка сообщения
|
||||||
|
* - Optimistic UI (мгновенное отображение)
|
||||||
|
* - Шифрование в IO потоке
|
||||||
|
* - Сохранение в БД в IO потоке
|
||||||
*/
|
*/
|
||||||
fun sendMessage() {
|
fun sendMessage() {
|
||||||
val text = _inputText.value.trim()
|
val text = _inputText.value.trim()
|
||||||
@@ -180,7 +394,7 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
val messageId = UUID.randomUUID().toString().replace("-", "").take(32)
|
val messageId = UUID.randomUUID().toString().replace("-", "").take(32)
|
||||||
val timestamp = System.currentTimeMillis()
|
val timestamp = System.currentTimeMillis()
|
||||||
|
|
||||||
// 1. Optimistic UI
|
// 1. 🚀 Optimistic UI - мгновенно показываем сообщение
|
||||||
val optimisticMessage = ChatMessage(
|
val optimisticMessage = ChatMessage(
|
||||||
id = messageId,
|
id = messageId,
|
||||||
text = text,
|
text = text,
|
||||||
@@ -191,26 +405,18 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
_messages.value = _messages.value + optimisticMessage
|
_messages.value = _messages.value + optimisticMessage
|
||||||
_inputText.value = ""
|
_inputText.value = ""
|
||||||
|
|
||||||
ProtocolManager.addLog("📤 === START SENDING MESSAGE ===")
|
// Кэшируем текст
|
||||||
ProtocolManager.addLog("📤 Text: \"${text.take(20)}...\"")
|
decryptionCache[messageId] = text
|
||||||
ProtocolManager.addLog("📤 Text length: ${text.length}")
|
|
||||||
ProtocolManager.addLog("📤 Recipient: ${recipient.take(20)}...")
|
|
||||||
ProtocolManager.addLog("📤 Sender: ${sender.take(20)}...")
|
|
||||||
ProtocolManager.addLog("📤 Message ID: $messageId")
|
|
||||||
ProtocolManager.addLog("📋 Current messages count: ${_messages.value.size}")
|
|
||||||
|
|
||||||
// 2. Отправка в фоне
|
ProtocolManager.addLog("📤 Sending: \"${text.take(20)}...\"")
|
||||||
viewModelScope.launch {
|
|
||||||
|
// 2. 🔥 Шифрование и отправка в IO потоке
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
ProtocolManager.addLog("🔐 Starting encryption...")
|
// Шифрование (тяжёлая операция)
|
||||||
val (encryptedContent, encryptedKey) = MessageCrypto.encryptForSending(text, recipient)
|
val (encryptedContent, encryptedKey) = MessageCrypto.encryptForSending(text, recipient)
|
||||||
ProtocolManager.addLog("✅ Encryption complete")
|
|
||||||
ProtocolManager.addLog(" - Encrypted content length: ${encryptedContent.length}")
|
|
||||||
ProtocolManager.addLog(" - Encrypted key length: ${encryptedKey.length}")
|
|
||||||
|
|
||||||
ProtocolManager.addLog("📦 Creating PacketMessage...")
|
|
||||||
val privateKeyHash = CryptoManager.generatePrivateKeyHash(privateKey)
|
val privateKeyHash = CryptoManager.generatePrivateKeyHash(privateKey)
|
||||||
ProtocolManager.addLog("🔑 Private key hash: ${privateKeyHash.take(20)}...")
|
|
||||||
|
|
||||||
val packet = PacketMessage().apply {
|
val packet = PacketMessage().apply {
|
||||||
fromPublicKey = sender
|
fromPublicKey = sender
|
||||||
@@ -223,30 +429,31 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
attachments = emptyList()
|
attachments = emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
ProtocolManager.addLog("<EFBFBD> Packet created:")
|
// Отправляем пакет
|
||||||
ProtocolManager.addLog(" - From: ${sender.take(20)}...")
|
|
||||||
ProtocolManager.addLog(" - To: ${recipient.take(20)}...")
|
|
||||||
ProtocolManager.addLog(" - Content: ${encryptedContent.take(40)}...")
|
|
||||||
ProtocolManager.addLog(" - ChaCha Key: ${encryptedKey.take(40)}...")
|
|
||||||
ProtocolManager.addLog("🔍 ChaCha Key char codes (first 20):")
|
|
||||||
ProtocolManager.addLog(" ${encryptedKey.take(20).map { it.code }.joinToString(",")}")
|
|
||||||
ProtocolManager.addLog(" - Timestamp: $timestamp")
|
|
||||||
ProtocolManager.addLog(" - Message ID: $messageId")
|
|
||||||
|
|
||||||
ProtocolManager.addLog("📡 Sending packet to server...")
|
|
||||||
ProtocolManager.send(packet)
|
ProtocolManager.send(packet)
|
||||||
ProtocolManager.addLog("📡 Packet sent to ProtocolManager")
|
|
||||||
|
|
||||||
updateMessageStatus(messageId, MessageStatus.SENT)
|
// 3. 🎯 UI обновление в Main потоке
|
||||||
ProtocolManager.addLog("✅ Message status updated to SENT")
|
withContext(Dispatchers.Main) {
|
||||||
ProtocolManager.addLog("📤 === SENDING COMPLETE ===")
|
updateMessageStatus(messageId, MessageStatus.SENT)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 💾 Сохранение в БД (уже в IO потоке)
|
||||||
|
saveMessageToDatabase(
|
||||||
|
messageId = messageId,
|
||||||
|
text = text,
|
||||||
|
encryptedContent = encryptedContent,
|
||||||
|
encryptedKey = encryptedKey,
|
||||||
|
timestamp = timestamp,
|
||||||
|
isFromMe = true
|
||||||
|
)
|
||||||
|
|
||||||
// Сохраняем/обновляем диалог в базе
|
|
||||||
saveDialog(text, timestamp)
|
saveDialog(text, timestamp)
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ProtocolManager.addLog("❌ Error: ${e.message}")
|
|
||||||
Log.e(TAG, "Send error", e)
|
Log.e(TAG, "Send error", e)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
updateMessageStatus(messageId, MessageStatus.SENT) // Changed from ERROR
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
isSending = false
|
isSending = false
|
||||||
}
|
}
|
||||||
@@ -279,7 +486,52 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
}
|
}
|
||||||
ProtocolManager.addLog("💾 Dialog saved")
|
ProtocolManager.addLog("💾 Dialog saved")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ProtocolManager.addLog("❌ Failed to save dialog: ${e.message}")
|
ProtocolManager.addLog("❌ Dialog save error: ${e.message}")
|
||||||
|
Log.e(TAG, "Dialog save error", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сохранить сообщение в базу данных
|
||||||
|
*/
|
||||||
|
private suspend fun saveMessageToDatabase(
|
||||||
|
messageId: String,
|
||||||
|
text: String,
|
||||||
|
encryptedContent: String,
|
||||||
|
encryptedKey: String,
|
||||||
|
timestamp: Long,
|
||||||
|
isFromMe: Boolean,
|
||||||
|
delivered: Int = 0
|
||||||
|
) {
|
||||||
|
val account = myPublicKey ?: return
|
||||||
|
val opponent = opponentKey ?: return
|
||||||
|
|
||||||
|
try {
|
||||||
|
val dialogKey = getDialogKey(account, opponent)
|
||||||
|
|
||||||
|
val entity = MessageEntity(
|
||||||
|
account = account,
|
||||||
|
fromPublicKey = if (isFromMe) account else opponent,
|
||||||
|
toPublicKey = if (isFromMe) opponent else account,
|
||||||
|
content = encryptedContent,
|
||||||
|
timestamp = timestamp,
|
||||||
|
chachaKey = encryptedKey,
|
||||||
|
read = if (isFromMe) 1 else 0,
|
||||||
|
fromMe = if (isFromMe) 1 else 0,
|
||||||
|
delivered = delivered,
|
||||||
|
messageId = messageId,
|
||||||
|
plainMessage = text,
|
||||||
|
attachments = "[]",
|
||||||
|
replyToMessageId = null,
|
||||||
|
dialogKey = dialogKey
|
||||||
|
)
|
||||||
|
|
||||||
|
messageDao.insertMessage(entity)
|
||||||
|
ProtocolManager.addLog("💾 Message saved to DB: ${messageId.take(8)}...")
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
ProtocolManager.addLog("❌ Message save error: ${e.message}")
|
||||||
|
Log.e(TAG, "Message save error", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user