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