feat: Optimize coroutine usage in ChatViewModel for improved performance and responsiveness & FIX LAGS
This commit is contained in:
@@ -24,7 +24,8 @@ object ProtocolManager {
|
|||||||
private var messageRepository: MessageRepository? = null
|
private var messageRepository: MessageRepository? = null
|
||||||
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||||
|
|
||||||
// Debug logs for dev console
|
// Debug logs for dev console - 🚀 ОТКЛЮЧЕНО для производительности
|
||||||
|
// Логи только в Logcat, не в StateFlow (это вызывало ANR!)
|
||||||
private val _debugLogs = MutableStateFlow<List<String>>(emptyList())
|
private val _debugLogs = MutableStateFlow<List<String>>(emptyList())
|
||||||
val debugLogs: StateFlow<List<String>> = _debugLogs.asStateFlow()
|
val debugLogs: StateFlow<List<String>> = _debugLogs.asStateFlow()
|
||||||
|
|
||||||
@@ -34,11 +35,23 @@ object ProtocolManager {
|
|||||||
|
|
||||||
private val dateFormat = SimpleDateFormat("HH:mm:ss.SSS", Locale.getDefault())
|
private val dateFormat = SimpleDateFormat("HH:mm:ss.SSS", Locale.getDefault())
|
||||||
|
|
||||||
|
// 🚀 Флаг для включения UI логов (по умолчанию ВЫКЛЮЧЕНО - это вызывало ANR!)
|
||||||
|
private var uiLogsEnabled = false
|
||||||
|
|
||||||
fun addLog(message: String) {
|
fun addLog(message: String) {
|
||||||
val timestamp = dateFormat.format(Date())
|
val timestamp = dateFormat.format(Date())
|
||||||
val logLine = "[$timestamp] $message"
|
val logLine = "[$timestamp] $message"
|
||||||
|
// Только Logcat - быстро и не блокирует UI
|
||||||
Log.d(TAG, logLine)
|
Log.d(TAG, logLine)
|
||||||
_debugLogs.value = (_debugLogs.value + logLine).takeLast(100)
|
|
||||||
|
// UI логи отключены по умолчанию - вызывали ANR из-за перекомпозиций
|
||||||
|
if (uiLogsEnabled) {
|
||||||
|
_debugLogs.value = (_debugLogs.value + logLine).takeLast(50)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun enableUILogs(enabled: Boolean) {
|
||||||
|
uiLogsEnabled = enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearLogs() {
|
fun clearLogs() {
|
||||||
|
|||||||
@@ -296,7 +296,12 @@ fun ChatDetailScreen(
|
|||||||
|
|
||||||
// Состояние показа логов
|
// Состояние показа логов
|
||||||
var showLogs by remember { mutableStateOf(false) }
|
var showLogs by remember { mutableStateOf(false) }
|
||||||
val debugLogs by com.rosetta.messenger.network.ProtocolManager.debugLogs.collectAsState()
|
// 🚀 Собираем логи ТОЛЬКО когда они показываются - иначе каждый лог вызывает перекомпозицию!
|
||||||
|
val debugLogs = if (showLogs) {
|
||||||
|
com.rosetta.messenger.network.ProtocolManager.debugLogs.collectAsState().value
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
// Состояние выпадающего меню
|
// Состояние выпадающего меню
|
||||||
var showMenu by remember { mutableStateOf(false) }
|
var showMenu by remember { mutableStateOf(false) }
|
||||||
|
|||||||
@@ -126,15 +126,11 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
// Входящие сообщения
|
// Входящие сообщения
|
||||||
ProtocolManager.waitPacket(0x06) { packet ->
|
ProtocolManager.waitPacket(0x06) { packet ->
|
||||||
val msgPacket = packet as PacketMessage
|
val msgPacket = packet as PacketMessage
|
||||||
ProtocolManager.addLog("📨 ChatVM got packet 0x06: from=${msgPacket.fromPublicKey.take(16)}, to=${msgPacket.toPublicKey.take(16)}")
|
|
||||||
ProtocolManager.addLog("📨 opponentKey=${opponentKey?.take(16) ?: "NULL"}")
|
|
||||||
if (msgPacket.fromPublicKey == opponentKey || msgPacket.toPublicKey == opponentKey) {
|
if (msgPacket.fromPublicKey == opponentKey || msgPacket.toPublicKey == opponentKey) {
|
||||||
ProtocolManager.addLog("📨 ✅ Match! Processing message...")
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
viewModelScope.launch {
|
|
||||||
handleIncomingMessage(msgPacket)
|
handleIncomingMessage(msgPacket)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ProtocolManager.addLog("📨 ❌ No match, ignoring")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,7 +144,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
updateMessageStatus(deliveryPacket.messageId, MessageStatus.DELIVERED)
|
updateMessageStatus(deliveryPacket.messageId, MessageStatus.DELIVERED)
|
||||||
}
|
}
|
||||||
ProtocolManager.addLog("✓ Delivered: ${deliveryPacket.messageId.take(8)}...")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,7 +167,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
} else msg
|
} else msg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ProtocolManager.addLog("✓✓ Read receipt from: ${readPacket.fromPublicKey.take(8)}...")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -180,34 +174,24 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
// Typing
|
// Typing
|
||||||
ProtocolManager.waitPacket(0x0B) { packet ->
|
ProtocolManager.waitPacket(0x0B) { packet ->
|
||||||
val typingPacket = packet as PacketTyping
|
val typingPacket = packet as PacketTyping
|
||||||
ProtocolManager.addLog("⌨️ TYPING received from: ${typingPacket.fromPublicKey.take(16)}...")
|
|
||||||
ProtocolManager.addLog(" My opponent: ${opponentKey?.take(16)}...")
|
|
||||||
if (typingPacket.fromPublicKey == opponentKey) {
|
if (typingPacket.fromPublicKey == opponentKey) {
|
||||||
ProtocolManager.addLog(" ✅ Match! Showing typing indicator")
|
|
||||||
showTypingIndicator()
|
showTypingIndicator()
|
||||||
} else {
|
} else {
|
||||||
ProtocolManager.addLog(" ❌ No match, ignoring")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🟢 Онлайн статус (массив publicKey+state как в React Native)
|
// 🟢 Онлайн статус (массив publicKey+state как в React Native)
|
||||||
ProtocolManager.waitPacket(0x05) { packet ->
|
ProtocolManager.waitPacket(0x05) { packet ->
|
||||||
val onlinePacket = packet as PacketOnlineState
|
val onlinePacket = packet as PacketOnlineState
|
||||||
ProtocolManager.addLog("🟢 ONLINE STATUS received: ${onlinePacket.publicKeysState.size} entries")
|
|
||||||
|
|
||||||
onlinePacket.publicKeysState.forEach { item ->
|
onlinePacket.publicKeysState.forEach { item ->
|
||||||
ProtocolManager.addLog(" Key: ${item.publicKey.take(16)}... State: ${item.state}")
|
|
||||||
ProtocolManager.addLog(" My opponent: ${opponentKey?.take(16)}...")
|
|
||||||
|
|
||||||
if (item.publicKey == opponentKey) {
|
if (item.publicKey == opponentKey) {
|
||||||
ProtocolManager.addLog(" ✅ Match! Updating UI - online: ${item.state == OnlineState.ONLINE}")
|
|
||||||
viewModelScope.launch {
|
|
||||||
_opponentOnline.value = item.state == OnlineState.ONLINE
|
_opponentOnline.value = item.state == OnlineState.ONLINE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleIncomingMessage(packet: PacketMessage) {
|
private fun handleIncomingMessage(packet: PacketMessage) {
|
||||||
// 🚀 Обработка входящего сообщения в IO потоке
|
// 🚀 Обработка входящего сообщения в IO потоке
|
||||||
@@ -216,8 +200,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
val privateKey = myPrivateKey ?: return@launch
|
val privateKey = myPrivateKey ?: return@launch
|
||||||
val account = myPublicKey ?: return@launch
|
val account = myPublicKey ?: return@launch
|
||||||
|
|
||||||
ProtocolManager.addLog("📩 Incoming message: ${packet.messageId.take(8)}...")
|
|
||||||
ProtocolManager.addLog("📎 Attachments count: ${packet.attachments.size}")
|
|
||||||
|
|
||||||
// Расшифровываем в фоне - получаем и текст и plainKeyAndNonce
|
// Расшифровываем в фоне - получаем и текст и plainKeyAndNonce
|
||||||
val decryptResult = MessageCrypto.decryptIncomingFull(
|
val decryptResult = MessageCrypto.decryptIncomingFull(
|
||||||
@@ -231,14 +213,12 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
// Кэшируем расшифрованный текст
|
// Кэшируем расшифрованный текст
|
||||||
decryptionCache[packet.messageId] = decryptedText
|
decryptionCache[packet.messageId] = decryptedText
|
||||||
|
|
||||||
ProtocolManager.addLog("✅ Decrypted: ${decryptedText.take(20)}...")
|
|
||||||
|
|
||||||
// 🔥 Парсим reply из attachments (как в React Native)
|
// 🔥 Парсим reply из attachments (как в React Native)
|
||||||
var replyData: ReplyData? = null
|
var replyData: ReplyData? = null
|
||||||
val attachmentsJson = if (packet.attachments.isNotEmpty()) {
|
val attachmentsJson = if (packet.attachments.isNotEmpty()) {
|
||||||
val jsonArray = JSONArray()
|
val jsonArray = JSONArray()
|
||||||
for (att in packet.attachments) {
|
for (att in packet.attachments) {
|
||||||
ProtocolManager.addLog("📎 Attachment type: ${att.type}, blob size: ${att.blob.length}")
|
|
||||||
|
|
||||||
// Если это MESSAGES (reply) - парсим и расшифровываем данные
|
// Если это MESSAGES (reply) - парсим и расшифровываем данные
|
||||||
var blobToStore = att.blob // По умолчанию сохраняем оригинальный blob
|
var blobToStore = att.blob // По умолчанию сохраняем оригинальный blob
|
||||||
@@ -246,7 +226,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
try {
|
try {
|
||||||
// 🔥 Сначала расшифровываем blob (он зашифрован!)
|
// 🔥 Сначала расшифровываем blob (он зашифрован!)
|
||||||
val decryptedBlob = MessageCrypto.decryptReplyBlob(att.blob, plainKeyAndNonce)
|
val decryptedBlob = MessageCrypto.decryptReplyBlob(att.blob, plainKeyAndNonce)
|
||||||
ProtocolManager.addLog("📎 Decrypted reply blob: ${decryptedBlob.take(100)}")
|
|
||||||
|
|
||||||
// 🔥 Сохраняем расшифрованный blob в БД
|
// 🔥 Сохраняем расшифрованный blob в БД
|
||||||
blobToStore = decryptedBlob
|
blobToStore = decryptedBlob
|
||||||
@@ -268,10 +247,8 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
text = replyText,
|
text = replyText,
|
||||||
isFromMe = isReplyFromMe
|
isFromMe = isReplyFromMe
|
||||||
)
|
)
|
||||||
ProtocolManager.addLog("✅ Parsed reply: from=${replyData?.senderName}, text=${replyText.take(30)}")
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ProtocolManager.addLog("❌ Failed to parse reply: ${e.message}")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,7 +274,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
)
|
)
|
||||||
// Просто добавляем как в архиве: setMessages((prev) => ([...prev, newMessage]))
|
// Просто добавляем как в архиве: setMessages((prev) => ([...prev, newMessage]))
|
||||||
_messages.value = _messages.value + message
|
_messages.value = _messages.value + message
|
||||||
ProtocolManager.addLog("✅ Added to UI: ${packet.messageId.take(8)}... text: ${decryptedText.take(20)}")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔥 Сохраняем в БД здесь (в ChatViewModel)
|
// 🔥 Сохраняем в БД здесь (в ChatViewModel)
|
||||||
@@ -308,7 +284,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
// 🔥 FIX: Если messageId пустой - генерируем новый UUID
|
// 🔥 FIX: Если messageId пустой - генерируем новый UUID
|
||||||
val finalMessageId = if (packet.messageId.isNullOrEmpty()) {
|
val finalMessageId = if (packet.messageId.isNullOrEmpty()) {
|
||||||
UUID.randomUUID().toString().replace("-", "").take(32).also {
|
UUID.randomUUID().toString().replace("-", "").take(32).also {
|
||||||
ProtocolManager.addLog("⚠️ Empty messageId from server, generated: ${it.take(8)}...")
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
packet.messageId
|
packet.messageId
|
||||||
@@ -334,7 +309,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
// (через markVisibleMessagesAsRead вызываемый из ChatDetailScreen)
|
// (через markVisibleMessagesAsRead вызываемый из ChatDetailScreen)
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ProtocolManager.addLog("❌ Error handling incoming message: ${e.message}")
|
|
||||||
Log.e(TAG, "Incoming message error", e)
|
Log.e(TAG, "Incoming message error", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -364,7 +338,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
fun setUserKeys(publicKey: String, privateKey: String) {
|
fun setUserKeys(publicKey: String, privateKey: String) {
|
||||||
myPublicKey = publicKey
|
myPublicKey = publicKey
|
||||||
myPrivateKey = privateKey
|
myPrivateKey = privateKey
|
||||||
ProtocolManager.addLog("🔑 Keys set: ${publicKey.take(16)}...")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -373,7 +346,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
fun openDialog(publicKey: String, title: String = "", username: String = "") {
|
fun openDialog(publicKey: String, title: String = "", username: String = "") {
|
||||||
// 🔥 ВСЕГДА перезагружаем данные - не кешируем, т.к. диалог мог быть удалён
|
// 🔥 ВСЕГДА перезагружаем данные - не кешируем, т.к. диалог мог быть удалён
|
||||||
// if (opponentKey == publicKey) {
|
// if (opponentKey == publicKey) {
|
||||||
// ProtocolManager.addLog("💬 Dialog already open: ${publicKey.take(16)}...")
|
|
||||||
// return
|
// return
|
||||||
// }
|
// }
|
||||||
|
|
||||||
@@ -395,7 +367,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
readReceiptSentForCurrentDialog = false
|
readReceiptSentForCurrentDialog = false
|
||||||
isDialogActive = true // 🔥 Диалог активен!
|
isDialogActive = true // 🔥 Диалог активен!
|
||||||
|
|
||||||
ProtocolManager.addLog("💬 Dialog opened: ${title.ifEmpty { publicKey.take(16) }}...")
|
|
||||||
|
|
||||||
// Подписываемся на онлайн статус
|
// Подписываемся на онлайн статус
|
||||||
subscribeToOnlineStatus()
|
subscribeToOnlineStatus()
|
||||||
@@ -411,7 +382,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
*/
|
*/
|
||||||
fun closeDialog() {
|
fun closeDialog() {
|
||||||
isDialogActive = false
|
isDialogActive = false
|
||||||
ProtocolManager.addLog("💬 Dialog closed (isDialogActive = false)")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -431,7 +401,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
// 🔥 МГНОВЕННАЯ загрузка из кэша если есть!
|
// 🔥 МГНОВЕННАЯ загрузка из кэша если есть!
|
||||||
val cachedMessages = dialogMessagesCache[dialogKey]
|
val cachedMessages = dialogMessagesCache[dialogKey]
|
||||||
if (cachedMessages != null && cachedMessages.isNotEmpty()) {
|
if (cachedMessages != null && cachedMessages.isNotEmpty()) {
|
||||||
ProtocolManager.addLog("⚡ Loading ${cachedMessages.size} messages from CACHE (instant!)")
|
|
||||||
withContext(Dispatchers.Main.immediate) {
|
withContext(Dispatchers.Main.immediate) {
|
||||||
_messages.value = cachedMessages
|
_messages.value = cachedMessages
|
||||||
_isLoading.value = false
|
_isLoading.value = false
|
||||||
@@ -452,16 +421,13 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
delay(delayMs)
|
delay(delayMs)
|
||||||
}
|
}
|
||||||
|
|
||||||
ProtocolManager.addLog("📂 Loading messages from DB for dialog: $dialogKey")
|
|
||||||
|
|
||||||
// 🔍 Проверяем общее количество сообщений в диалоге
|
// 🔍 Проверяем общее количество сообщений в диалоге
|
||||||
val totalCount = messageDao.getMessageCount(account, dialogKey)
|
val totalCount = messageDao.getMessageCount(account, dialogKey)
|
||||||
ProtocolManager.addLog("📂 Total messages in DB: $totalCount")
|
|
||||||
|
|
||||||
// 🔥 Получаем первую страницу - БЕЗ suspend задержки
|
// 🔥 Получаем первую страницу - БЕЗ suspend задержки
|
||||||
val entities = messageDao.getMessages(account, dialogKey, limit = PAGE_SIZE, offset = 0)
|
val entities = messageDao.getMessages(account, dialogKey, limit = PAGE_SIZE, offset = 0)
|
||||||
|
|
||||||
ProtocolManager.addLog("📂 Loaded ${entities.size} messages from DB (offset: 0, limit: $PAGE_SIZE)")
|
|
||||||
|
|
||||||
hasMoreMessages = entities.size >= PAGE_SIZE
|
hasMoreMessages = entities.size >= PAGE_SIZE
|
||||||
currentOffset = entities.size
|
currentOffset = entities.size
|
||||||
@@ -480,11 +446,9 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ProtocolManager.addLog("📋 Decrypted and loaded ${messages.size} messages from DB")
|
|
||||||
|
|
||||||
// 🔥 Сохраняем в кэш для мгновенной повторной загрузки!
|
// 🔥 Сохраняем в кэш для мгновенной повторной загрузки!
|
||||||
dialogMessagesCache[dialogKey] = messages.toList()
|
dialogMessagesCache[dialogKey] = messages.toList()
|
||||||
ProtocolManager.addLog("💾 Cached ${messages.size} messages for dialog $dialogKey")
|
|
||||||
|
|
||||||
// 🔥 СРАЗУ обновляем UI - пользователь видит сообщения мгновенно
|
// 🔥 СРАЗУ обновляем UI - пользователь видит сообщения мгновенно
|
||||||
// НО сохраняем оптимистичные сообщения (SENDING), которые ещё не в БД
|
// НО сохраняем оптимистичные сообщения (SENDING), которые ещё не в БД
|
||||||
@@ -501,7 +465,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
// Отмечаем как прочитанные в БД
|
// Отмечаем как прочитанные в БД
|
||||||
messageDao.markDialogAsRead(account, dialogKey)
|
messageDao.markDialogAsRead(account, dialogKey)
|
||||||
dialogDao.clearUnreadCount(account, opponent)
|
dialogDao.clearUnreadCount(account, opponent)
|
||||||
ProtocolManager.addLog("👁️ Marked all incoming messages as read in DB, cleared unread count")
|
|
||||||
|
|
||||||
// Отправляем read receipt собеседнику
|
// Отправляем read receipt собеседнику
|
||||||
if (messages.isNotEmpty()) {
|
if (messages.isNotEmpty()) {
|
||||||
@@ -515,7 +478,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
isLoadingMessages = false
|
isLoadingMessages = false
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ProtocolManager.addLog("❌ Error loading messages: ${e.message}")
|
|
||||||
Log.e(TAG, "Error loading messages", e)
|
Log.e(TAG, "Error loading messages", e)
|
||||||
withContext(Dispatchers.Main.immediate) {
|
withContext(Dispatchers.Main.immediate) {
|
||||||
_isLoading.value = false
|
_isLoading.value = false
|
||||||
@@ -550,7 +512,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
withContext(Dispatchers.Main.immediate) {
|
withContext(Dispatchers.Main.immediate) {
|
||||||
_messages.value = messages
|
_messages.value = messages
|
||||||
}
|
}
|
||||||
ProtocolManager.addLog("🔄 Refreshed: found ${messages.size - cachedMessages.size} new messages")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hasMoreMessages = entities.size >= PAGE_SIZE
|
hasMoreMessages = entities.size >= PAGE_SIZE
|
||||||
@@ -561,7 +522,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
dialogDao.clearUnreadCount(account, opponent)
|
dialogDao.clearUnreadCount(account, opponent)
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ProtocolManager.addLog("❌ Error refreshing messages: ${e.message}")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -639,7 +599,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
encryptedKey = entity.chachaKey,
|
encryptedKey = entity.chachaKey,
|
||||||
myPrivateKey = privateKey
|
myPrivateKey = privateKey
|
||||||
)
|
)
|
||||||
ProtocolManager.addLog("🔓 Decrypted from DB: ${decrypted.take(20)}...")
|
|
||||||
decrypted
|
decrypted
|
||||||
} else {
|
} else {
|
||||||
// Fallback на расшифровку plainMessage с приватным ключом
|
// Fallback на расшифровку plainMessage с приватным ключом
|
||||||
@@ -647,7 +606,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
try {
|
try {
|
||||||
CryptoManager.decryptWithPassword(entity.plainMessage, privateKey) ?: entity.plainMessage
|
CryptoManager.decryptWithPassword(entity.plainMessage, privateKey) ?: entity.plainMessage
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ProtocolManager.addLog("⚠️ plainMessage decrypt error: ${e.message}")
|
|
||||||
entity.plainMessage
|
entity.plainMessage
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -655,7 +613,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ProtocolManager.addLog("❌ Decrypt error: ${e.message}, trying plainMessage")
|
|
||||||
// Пробуем расшифровать plainMessage
|
// Пробуем расшифровать plainMessage
|
||||||
val privateKey = myPrivateKey
|
val privateKey = myPrivateKey
|
||||||
if (privateKey != null && entity.plainMessage.isNotEmpty()) {
|
if (privateKey != null && entity.plainMessage.isNotEmpty()) {
|
||||||
@@ -806,7 +763,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
_isForwardMode.value = false
|
_isForwardMode.value = false
|
||||||
ProtocolManager.addLog("📝 Reply set: ${messages.size} messages")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -826,7 +782,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
_isForwardMode.value = true
|
_isForwardMode.value = true
|
||||||
ProtocolManager.addLog("➡️ Forward set: ${messages.size} messages")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -841,34 +796,30 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
* 🔥 Удалить сообщение (для ошибки отправки)
|
* 🔥 Удалить сообщение (для ошибки отправки)
|
||||||
*/
|
*/
|
||||||
fun deleteMessage(messageId: String) {
|
fun deleteMessage(messageId: String) {
|
||||||
viewModelScope.launch {
|
// Удаляем из UI сразу на main
|
||||||
// Удаляем из UI
|
|
||||||
_messages.value = _messages.value.filter { it.id != messageId }
|
_messages.value = _messages.value.filter { it.id != messageId }
|
||||||
|
|
||||||
// Удаляем из БД
|
// Удаляем из БД в IO
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val account = myPublicKey ?: return@launch
|
val account = myPublicKey ?: return@launch
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
messageDao.deleteMessage(account, messageId)
|
messageDao.deleteMessage(account, messageId)
|
||||||
}
|
}
|
||||||
ProtocolManager.addLog("🗑️ Message deleted: ${messageId.take(8)}...")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 🔥 Повторить отправку сообщения (для ошибки)
|
* 🔥 Повторить отправку сообщения (для ошибки)
|
||||||
*/
|
*/
|
||||||
fun retryMessage(message: ChatMessage) {
|
fun retryMessage(message: ChatMessage) {
|
||||||
viewModelScope.launch {
|
|
||||||
// Удаляем старое сообщение
|
// Удаляем старое сообщение
|
||||||
deleteMessage(message.id)
|
deleteMessage(message.id)
|
||||||
|
|
||||||
// Устанавливаем текст в инпут и отправляем
|
// Устанавливаем текст в инпут и отправляем
|
||||||
_inputText.value = message.text
|
_inputText.value = message.text
|
||||||
|
|
||||||
// Небольшая задержка чтобы UI обновился
|
// Отправляем с небольшой задержкой
|
||||||
|
viewModelScope.launch {
|
||||||
delay(100)
|
delay(100)
|
||||||
sendMessage()
|
sendMessage()
|
||||||
ProtocolManager.addLog("🔄 Retrying message: ${message.text.take(20)}...")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -881,7 +832,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
*/
|
*/
|
||||||
fun sendMessage() {
|
fun sendMessage() {
|
||||||
Log.d(TAG, "🚀🚀🚀 sendMessage() CALLED 🚀🚀🚀")
|
Log.d(TAG, "🚀🚀🚀 sendMessage() CALLED 🚀🚀🚀")
|
||||||
ProtocolManager.addLog("🚀🚀🚀 sendMessage() CALLED")
|
|
||||||
|
|
||||||
val text = _inputText.value.trim()
|
val text = _inputText.value.trim()
|
||||||
val recipient = opponentKey
|
val recipient = opponentKey
|
||||||
@@ -896,35 +846,26 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
Log.d(TAG, "🔑 PrivateKey exists: ${privateKey != null}")
|
Log.d(TAG, "🔑 PrivateKey exists: ${privateKey != null}")
|
||||||
Log.d(TAG, "💬 ReplyMsgs: ${replyMsgs.size}")
|
Log.d(TAG, "💬 ReplyMsgs: ${replyMsgs.size}")
|
||||||
|
|
||||||
ProtocolManager.addLog("📝 Text: '$text'")
|
|
||||||
ProtocolManager.addLog("📧 Recipient: ${recipient?.take(16)}")
|
|
||||||
ProtocolManager.addLog("👤 Sender: ${sender?.take(16)}")
|
|
||||||
ProtocolManager.addLog("🔑 PrivateKey exists: ${privateKey != null}")
|
|
||||||
|
|
||||||
// Разрешаем отправку пустого текста если есть reply/forward
|
// Разрешаем отправку пустого текста если есть reply/forward
|
||||||
if (text.isEmpty() && replyMsgs.isEmpty()) {
|
if (text.isEmpty() && replyMsgs.isEmpty()) {
|
||||||
Log.e(TAG, "❌ Empty text and no reply")
|
Log.e(TAG, "❌ Empty text and no reply")
|
||||||
ProtocolManager.addLog("❌ Empty text and no reply")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (recipient == null) {
|
if (recipient == null) {
|
||||||
Log.e(TAG, "❌ No recipient")
|
Log.e(TAG, "❌ No recipient")
|
||||||
ProtocolManager.addLog("❌ No recipient")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (sender == null || privateKey == null) {
|
if (sender == null || privateKey == null) {
|
||||||
Log.e(TAG, "❌ No keys - sender: $sender, privateKey: $privateKey")
|
Log.e(TAG, "❌ No keys - sender: $sender, privateKey: $privateKey")
|
||||||
ProtocolManager.addLog("❌ No keys - set via setUserKeys()")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (isSending) {
|
if (isSending) {
|
||||||
Log.w(TAG, "⏳ Already sending...")
|
Log.w(TAG, "⏳ Already sending...")
|
||||||
ProtocolManager.addLog("⏳ Already sending...")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d(TAG, "✅ All checks passed, starting send...")
|
Log.d(TAG, "✅ All checks passed, starting send...")
|
||||||
ProtocolManager.addLog("✅ All checks passed, starting send...")
|
|
||||||
|
|
||||||
isSending = true
|
isSending = true
|
||||||
|
|
||||||
@@ -964,7 +905,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
// Кэшируем текст
|
// Кэшируем текст
|
||||||
decryptionCache[messageId] = text
|
decryptionCache[messageId] = text
|
||||||
|
|
||||||
ProtocolManager.addLog("📤 Sending: \"${text.take(20)}...\" with ${replyMsgsToSend.size} reply attachments")
|
|
||||||
|
|
||||||
// 2. 🔥 Шифрование и отправка в IO потоке
|
// 2. 🔥 Шифрование и отправка в IO потоке
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
@@ -1007,8 +947,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
preview = ""
|
preview = ""
|
||||||
))
|
))
|
||||||
|
|
||||||
ProtocolManager.addLog("📎 Reply attachment created: ${replyBlobPlaintext.take(50)}...")
|
|
||||||
ProtocolManager.addLog("📎 Encrypted reply blob: ${encryptedReplyBlob.take(50)}...")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val packet = PacketMessage().apply {
|
val packet = PacketMessage().apply {
|
||||||
@@ -1023,10 +961,7 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 🔥 Log packet details before sending
|
// 🔥 Log packet details before sending
|
||||||
ProtocolManager.addLog("📤📤📤 SENDING PACKET 📤📤📤")
|
|
||||||
ProtocolManager.addLog(" - Attachments count: ${packet.attachments.size}")
|
|
||||||
packet.attachments.forEach { att ->
|
packet.attachments.forEach { att ->
|
||||||
ProtocolManager.addLog(" - Attachment: type=${att.type}, id=${att.id}, blob=${att.blob.take(50)}...")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Отправляем пакет
|
// Отправляем пакет
|
||||||
@@ -1089,13 +1024,11 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
// 🔒 Шифруем lastMessage перед сохранением
|
// 🔒 Шифруем lastMessage перед сохранением
|
||||||
val encryptedLastMessage = CryptoManager.encryptWithPassword(lastMessage, privateKey)
|
val encryptedLastMessage = CryptoManager.encryptWithPassword(lastMessage, privateKey)
|
||||||
|
|
||||||
ProtocolManager.addLog("💾 Saving dialog: ${lastMessage.take(20)}... (encrypted)")
|
|
||||||
val existingDialog = dialogDao.getDialog(account, opponent)
|
val existingDialog = dialogDao.getDialog(account, opponent)
|
||||||
|
|
||||||
if (existingDialog != null) {
|
if (existingDialog != null) {
|
||||||
// Обновляем последнее сообщение
|
// Обновляем последнее сообщение
|
||||||
dialogDao.updateLastMessage(account, opponent, encryptedLastMessage, timestamp)
|
dialogDao.updateLastMessage(account, opponent, encryptedLastMessage, timestamp)
|
||||||
ProtocolManager.addLog("✅ Dialog updated (existing)")
|
|
||||||
} else {
|
} else {
|
||||||
// Создаём новый диалог
|
// Создаём новый диалог
|
||||||
dialogDao.insertDialog(DialogEntity(
|
dialogDao.insertDialog(DialogEntity(
|
||||||
@@ -1106,10 +1039,8 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
lastMessage = encryptedLastMessage,
|
lastMessage = encryptedLastMessage,
|
||||||
lastMessageTimestamp = timestamp
|
lastMessageTimestamp = timestamp
|
||||||
))
|
))
|
||||||
ProtocolManager.addLog("✅ Dialog created (new)")
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ProtocolManager.addLog("❌ Dialog save error: ${e.message}")
|
|
||||||
Log.e(TAG, "Dialog save error", e)
|
Log.e(TAG, "Dialog save error", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1134,9 +1065,7 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
// Инкрементируем непрочитанные если нужно
|
// Инкрементируем непрочитанные если нужно
|
||||||
if (incrementUnread) {
|
if (incrementUnread) {
|
||||||
dialogDao.incrementUnreadCount(account, opponentKey)
|
dialogDao.incrementUnreadCount(account, opponentKey)
|
||||||
ProtocolManager.addLog("📬 Unread incremented for: ${opponentKey.take(16)}...")
|
|
||||||
}
|
}
|
||||||
ProtocolManager.addLog("✅ Dialog updated: ${lastMessage.take(20)}...")
|
|
||||||
} else {
|
} else {
|
||||||
// Создаём новый диалог
|
// Создаём новый диалог
|
||||||
dialogDao.insertDialog(DialogEntity(
|
dialogDao.insertDialog(DialogEntity(
|
||||||
@@ -1148,10 +1077,8 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
lastMessageTimestamp = timestamp,
|
lastMessageTimestamp = timestamp,
|
||||||
unreadCount = if (incrementUnread) 1 else 0
|
unreadCount = if (incrementUnread) 1 else 0
|
||||||
))
|
))
|
||||||
ProtocolManager.addLog("✅ Dialog created (new)")
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ProtocolManager.addLog("❌ updateDialog error: ${e.message}")
|
|
||||||
Log.e(TAG, "updateDialog error", e)
|
Log.e(TAG, "updateDialog error", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1183,7 +1110,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
// 🔒 Проверяем messageId - если пустой, генерируем новый
|
// 🔒 Проверяем messageId - если пустой, генерируем новый
|
||||||
val finalMessageId = if (messageId.isEmpty()) {
|
val finalMessageId = if (messageId.isEmpty()) {
|
||||||
val generated = UUID.randomUUID().toString().replace("-", "").take(32)
|
val generated = UUID.randomUUID().toString().replace("-", "").take(32)
|
||||||
ProtocolManager.addLog("⚠️ Empty messageId detected, generated new: $generated")
|
|
||||||
generated
|
generated
|
||||||
} else {
|
} else {
|
||||||
messageId
|
messageId
|
||||||
@@ -1194,11 +1120,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
|
|
||||||
// Проверяем существует ли сообщение
|
// Проверяем существует ли сообщение
|
||||||
val exists = messageDao.messageExists(account, finalMessageId)
|
val exists = messageDao.messageExists(account, finalMessageId)
|
||||||
ProtocolManager.addLog("💾 Saving message to DB:")
|
|
||||||
ProtocolManager.addLog(" messageId: $finalMessageId")
|
|
||||||
ProtocolManager.addLog(" exists in DB: $exists")
|
|
||||||
ProtocolManager.addLog(" dialogKey: $dialogKey")
|
|
||||||
ProtocolManager.addLog(" text: ${text.take(20)}...")
|
|
||||||
|
|
||||||
val entity = MessageEntity(
|
val entity = MessageEntity(
|
||||||
account = account,
|
account = account,
|
||||||
@@ -1218,17 +1139,15 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
val insertedId = messageDao.insertMessage(entity)
|
val insertedId = messageDao.insertMessage(entity)
|
||||||
ProtocolManager.addLog("✅ Message saved with DB id: $insertedId")
|
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ProtocolManager.addLog("❌ Message save error: ${e.message}")
|
|
||||||
Log.e(TAG, "Message save error", e)
|
Log.e(TAG, "Message save error", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showTypingIndicator() {
|
private fun showTypingIndicator() {
|
||||||
_opponentTyping.value = true
|
_opponentTyping.value = true
|
||||||
viewModelScope.launch {
|
viewModelScope.launch(Dispatchers.Default) {
|
||||||
kotlinx.coroutines.delay(3000)
|
kotlinx.coroutines.delay(3000)
|
||||||
_opponentTyping.value = false
|
_opponentTyping.value = false
|
||||||
}
|
}
|
||||||
@@ -1243,15 +1162,12 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
if (now - lastTypingSentTime < TYPING_THROTTLE_MS) return
|
if (now - lastTypingSentTime < TYPING_THROTTLE_MS) return
|
||||||
|
|
||||||
val opponent = opponentKey ?: run {
|
val opponent = opponentKey ?: run {
|
||||||
ProtocolManager.addLog("❌ Typing: No opponent key")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val sender = myPublicKey ?: run {
|
val sender = myPublicKey ?: run {
|
||||||
ProtocolManager.addLog("❌ Typing: No sender key")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val privateKey = myPrivateKey ?: run {
|
val privateKey = myPrivateKey ?: run {
|
||||||
ProtocolManager.addLog("❌ Typing: No private key")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1261,10 +1177,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
try {
|
try {
|
||||||
val privateKeyHash = CryptoManager.generatePrivateKeyHash(privateKey)
|
val privateKeyHash = CryptoManager.generatePrivateKeyHash(privateKey)
|
||||||
|
|
||||||
ProtocolManager.addLog("⌨️ Sending typing...")
|
|
||||||
ProtocolManager.addLog(" From: ${sender.take(16)}...")
|
|
||||||
ProtocolManager.addLog(" To: ${opponent.take(16)}...")
|
|
||||||
ProtocolManager.addLog(" PrivateHash: ${privateKeyHash.take(16)}...")
|
|
||||||
|
|
||||||
val packet = PacketTyping().apply {
|
val packet = PacketTyping().apply {
|
||||||
this.privateKey = privateKeyHash
|
this.privateKey = privateKeyHash
|
||||||
@@ -1273,10 +1185,8 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ProtocolManager.send(packet)
|
ProtocolManager.send(packet)
|
||||||
ProtocolManager.addLog("⌨️ Typing indicator sent ✅")
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Typing send error", e)
|
Log.e(TAG, "Typing send error", e)
|
||||||
ProtocolManager.addLog("❌ Typing send error: ${e.message}")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1289,7 +1199,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
private fun sendReadReceiptToOpponent() {
|
private fun sendReadReceiptToOpponent() {
|
||||||
// 🔥 Не отправляем read receipt если диалог не активен (как в архиве)
|
// 🔥 Не отправляем read receipt если диалог не активен (как в архиве)
|
||||||
if (!isDialogActive) {
|
if (!isDialogActive) {
|
||||||
ProtocolManager.addLog("👁️ Read receipt skipped - dialog not active")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1315,7 +1224,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ProtocolManager.send(packet)
|
ProtocolManager.send(packet)
|
||||||
ProtocolManager.addLog("👁️ Read receipt sent to: ${opponent.take(8)}...")
|
|
||||||
readReceiptSentForCurrentDialog = true
|
readReceiptSentForCurrentDialog = true
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Read receipt send error", e)
|
Log.e(TAG, "Read receipt send error", e)
|
||||||
@@ -1330,7 +1238,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
fun markVisibleMessagesAsRead() {
|
fun markVisibleMessagesAsRead() {
|
||||||
// 🔥 Не читаем если диалог не активен
|
// 🔥 Не читаем если диалог не активен
|
||||||
if (!isDialogActive) {
|
if (!isDialogActive) {
|
||||||
ProtocolManager.addLog("👁️ markVisibleMessagesAsRead skipped - dialog not active")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1344,7 +1251,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
// Если timestamp не изменился - не отправляем повторно
|
// Если timestamp не изменился - не отправляем повторно
|
||||||
if (lastIncoming.timestamp.time <= lastReadMessageTimestamp) return
|
if (lastIncoming.timestamp.time <= lastReadMessageTimestamp) return
|
||||||
|
|
||||||
ProtocolManager.addLog("👁️ markVisibleMessagesAsRead: new message detected")
|
|
||||||
|
|
||||||
// Отмечаем в БД и очищаем счетчик непрочитанных
|
// Отмечаем в БД и очищаем счетчик непрочитанных
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
@@ -1352,7 +1258,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
val dialogKey = getDialogKey(account, opponent)
|
val dialogKey = getDialogKey(account, opponent)
|
||||||
messageDao.markDialogAsRead(account, dialogKey)
|
messageDao.markDialogAsRead(account, dialogKey)
|
||||||
dialogDao.clearUnreadCount(account, opponent)
|
dialogDao.clearUnreadCount(account, opponent)
|
||||||
ProtocolManager.addLog("👁️ Marked dialog as read in DB, cleared unread count")
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Mark as read error", e)
|
Log.e(TAG, "Mark as read error", e)
|
||||||
}
|
}
|
||||||
@@ -1379,10 +1284,8 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ProtocolManager.send(packet)
|
ProtocolManager.send(packet)
|
||||||
ProtocolManager.addLog("🟢 Subscribed to online status: ${opponent.take(16)}...")
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Online subscribe error", e)
|
Log.e(TAG, "Online subscribe error", e)
|
||||||
ProtocolManager.addLog("❌ Online subscribe error: ${e.message}")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -178,7 +178,6 @@ fun ChatsListScreen(
|
|||||||
|
|
||||||
// Protocol connection state
|
// Protocol connection state
|
||||||
val protocolState by ProtocolManager.state.collectAsState()
|
val protocolState by ProtocolManager.state.collectAsState()
|
||||||
val debugLogs by ProtocolManager.debugLogs.collectAsState()
|
|
||||||
|
|
||||||
// Dialogs from database
|
// Dialogs from database
|
||||||
val dialogsList by chatsViewModel.dialogs.collectAsState()
|
val dialogsList by chatsViewModel.dialogs.collectAsState()
|
||||||
|
|||||||
@@ -60,9 +60,10 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
|
|||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
dialogDao.getDialogsFlow(publicKey)
|
dialogDao.getDialogsFlow(publicKey)
|
||||||
.collect { dialogsList ->
|
.flowOn(Dispatchers.IO) // 🚀 Flow работает на IO
|
||||||
// 🔓 Расшифровываем lastMessage для каждого диалога
|
.map { dialogsList ->
|
||||||
val decryptedDialogs = dialogsList.map { dialog ->
|
// 🔓 Расшифровываем lastMessage на IO потоке (PBKDF2 - тяжелая операция!)
|
||||||
|
dialogsList.map { dialog ->
|
||||||
val decryptedLastMessage = try {
|
val decryptedLastMessage = try {
|
||||||
if (privateKey.isNotEmpty() && dialog.lastMessage.isNotEmpty()) {
|
if (privateKey.isNotEmpty() && dialog.lastMessage.isNotEmpty()) {
|
||||||
CryptoManager.decryptWithPassword(dialog.lastMessage, privateKey)
|
CryptoManager.decryptWithPassword(dialog.lastMessage, privateKey)
|
||||||
@@ -88,12 +89,13 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
|
|||||||
verified = dialog.verified
|
verified = dialog.verified
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
.flowOn(Dispatchers.Default) // 🚀 map выполняется на Default (CPU)
|
||||||
|
.collect { decryptedDialogs ->
|
||||||
_dialogs.value = decryptedDialogs
|
_dialogs.value = decryptedDialogs
|
||||||
ProtocolManager.addLog("📋 Dialogs loaded: ${decryptedDialogs.size} (lastMessages decrypted)")
|
|
||||||
|
|
||||||
// 🟢 Подписываемся на онлайн-статусы всех собеседников
|
// 🟢 Подписываемся на онлайн-статусы всех собеседников
|
||||||
subscribeToOnlineStatuses(dialogsList.map { it.opponentKey }, privateKey)
|
subscribeToOnlineStatuses(decryptedDialogs.map { it.opponentKey }, privateKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,9 +118,7 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
|
|||||||
}
|
}
|
||||||
|
|
||||||
ProtocolManager.send(packet)
|
ProtocolManager.send(packet)
|
||||||
ProtocolManager.addLog("🟢 Subscribed to ${opponentKeys.size} online statuses")
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ProtocolManager.addLog("❌ Online subscribe error: ${e.message}")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,12 +40,7 @@ class SearchUsersViewModel : ViewModel() {
|
|||||||
// Callback для обработки ответа поиска
|
// Callback для обработки ответа поиска
|
||||||
private val searchPacketHandler: (com.rosetta.messenger.network.Packet) -> Unit = { packet ->
|
private val searchPacketHandler: (com.rosetta.messenger.network.Packet) -> Unit = { packet ->
|
||||||
if (packet is PacketSearch) {
|
if (packet is PacketSearch) {
|
||||||
ProtocolManager.addLog("📥 Search response received")
|
|
||||||
ProtocolManager.addLog(" Users found: ${packet.users.size}")
|
|
||||||
packet.users.forEachIndexed { index, user ->
|
packet.users.forEachIndexed { index, user ->
|
||||||
ProtocolManager.addLog(" [$index] ${user.title.ifEmpty { "No title" }} (@${user.username.ifEmpty { "no username" }})")
|
|
||||||
ProtocolManager.addLog(" Key: ${user.publicKey.take(20)}...")
|
|
||||||
ProtocolManager.addLog(" Verified: ${user.verified}, Online: ${user.online}")
|
|
||||||
}
|
}
|
||||||
_searchResults.value = packet.users
|
_searchResults.value = packet.users
|
||||||
_isSearching.value = false
|
_isSearching.value = false
|
||||||
@@ -103,7 +98,6 @@ class SearchUsersViewModel : ViewModel() {
|
|||||||
|
|
||||||
// Проверяем состояние протокола
|
// Проверяем состояние протокола
|
||||||
if (ProtocolManager.state.value != ProtocolState.AUTHENTICATED) {
|
if (ProtocolManager.state.value != ProtocolState.AUTHENTICATED) {
|
||||||
ProtocolManager.addLog("⚠️ Search failed: Not authenticated")
|
|
||||||
_isSearching.value = false
|
_isSearching.value = false
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
@@ -115,8 +109,6 @@ class SearchUsersViewModel : ViewModel() {
|
|||||||
|
|
||||||
lastSearchedText = query
|
lastSearchedText = query
|
||||||
|
|
||||||
ProtocolManager.addLog("🔍 Searching for: \"$query\"")
|
|
||||||
ProtocolManager.addLog(" PrivateKeyHash: ${privateKeyHash.take(20)}...")
|
|
||||||
|
|
||||||
// Создаем и отправляем пакет поиска
|
// Создаем и отправляем пакет поиска
|
||||||
val packetSearch = PacketSearch().apply {
|
val packetSearch = PacketSearch().apply {
|
||||||
@@ -132,7 +124,6 @@ class SearchUsersViewModel : ViewModel() {
|
|||||||
* Открыть панель поиска
|
* Открыть панель поиска
|
||||||
*/
|
*/
|
||||||
fun expandSearch() {
|
fun expandSearch() {
|
||||||
ProtocolManager.addLog("🔎 Search panel opened")
|
|
||||||
_isSearchExpanded.value = true
|
_isSearchExpanded.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,7 +131,6 @@ class SearchUsersViewModel : ViewModel() {
|
|||||||
* Закрыть панель поиска и очистить результаты
|
* Закрыть панель поиска и очистить результаты
|
||||||
*/
|
*/
|
||||||
fun collapseSearch() {
|
fun collapseSearch() {
|
||||||
ProtocolManager.addLog("🔎 Search panel closed")
|
|
||||||
_isSearchExpanded.value = false
|
_isSearchExpanded.value = false
|
||||||
_searchQuery.value = ""
|
_searchQuery.value = ""
|
||||||
_searchResults.value = emptyList()
|
_searchResults.value = emptyList()
|
||||||
|
|||||||
Reference in New Issue
Block a user