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 2767199..b9a3e9a 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 @@ -328,6 +328,9 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { * Открыть диалог */ fun openDialog(publicKey: String, title: String = "", username: String = "") { + Log.d(TAG, "🚪 openDialog START: publicKey=$publicKey, title=$title") + Log.d(TAG, "🔍 Current state: opponentKey=$opponentKey, myPublicKey=$myPublicKey") + // 🔥 ВСЕГДА перезагружаем данные - не кешируем, т.к. диалог мог быть удалён // if (opponentKey == publicKey) { // return @@ -345,9 +348,11 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { val forwardMessages = ForwardManager.getForwardMessagesForChat(publicKey) val hasForward = forwardMessages.isNotEmpty() if (hasForward) { + Log.d(TAG, "📨 Has forward messages: ${forwardMessages.size}") } // Сбрасываем состояние + Log.d(TAG, "🔄 Resetting state, setting messages to empty") _messages.value = emptyList() _opponentOnline.value = false _opponentTyping.value = false @@ -359,6 +364,8 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { subscribedToOnlineStatus = false // 🔥 Сбрасываем флаг подписки при смене диалога isDialogActive = true // 🔥 Диалог активен! + Log.d(TAG, "📊 State after reset: messagesCount=${_messages.value.size}, isDialogActive=$isDialogActive") + // 📨 Применяем Forward сообщения СРАЗУ после сброса if (hasForward) { // Конвертируем ForwardMessage в ReplyMessage @@ -414,17 +421,25 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { val opponent = opponentKey ?: return val dialogKey = getDialogKey(account, opponent) + Log.d(TAG, "📥 loadMessagesFromDatabase START: account=$account, opponent=$opponent, dialogKey=$dialogKey") + // 📁 Проверяем является ли это Saved Messages val isSavedMessages = (opponent == account) - if (isLoadingMessages) return + if (isLoadingMessages) { + Log.d(TAG, "⚠️ loadMessagesFromDatabase SKIP - already loading") + return + } isLoadingMessages = true loadingJob = viewModelScope.launch(Dispatchers.IO) { try { // 🔥 МГНОВЕННАЯ загрузка из кэша если есть! val cachedMessages = dialogMessagesCache[dialogKey] + Log.d(TAG, "📦 Cache check: dialogKey=$dialogKey, cachedCount=${cachedMessages?.size ?: 0}") + if (cachedMessages != null && cachedMessages.isNotEmpty()) { + Log.d(TAG, "✅ Using cached messages: ${cachedMessages.size}") withContext(Dispatchers.Main.immediate) { _messages.value = cachedMessages _isLoading.value = false @@ -445,8 +460,11 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { messageDao.getMessageCount(account, dialogKey) } + Log.d(TAG, "📊 DB message count: totalCount=$totalCount, isSavedMessages=$isSavedMessages") + if (totalCount == 0) { // Пустой диалог - сразу показываем пустое состояние без скелетона + Log.d(TAG, "📭 Empty dialog - showing empty state") withContext(Dispatchers.Main.immediate) { _messages.value = emptyList() _isLoading.value = false @@ -457,20 +475,30 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { // 🔥 Есть сообщения - показываем скелетон и загружаем с задержкой для анимации if (delayMs > 0) { + Log.d(TAG, "⏳ Delaying load for ${delayMs}ms") withContext(Dispatchers.Main.immediate) { _isLoading.value = true // Показываем скелетон } delay(delayMs) + Log.d(TAG, "✅ Delay finished, starting DB load") } // 🔥 Получаем первую страницу - используем специальный метод для saved messages - val entities = if (isSavedMessages) { - messageDao.getMessagesForSavedDialog(account, limit = PAGE_SIZE, offset = 0) - } else { - messageDao.getMessages(account, dialogKey, limit = PAGE_SIZE, offset = 0) + Log.d(TAG, "🔍 Querying DB for messages...") + val entities = try { + if (isSavedMessages) { + messageDao.getMessagesForSavedDialog(account, limit = PAGE_SIZE, offset = 0) + } else { + messageDao.getMessages(account, dialogKey, limit = PAGE_SIZE, offset = 0) + } + } catch (e: android.database.sqlite.SQLiteBlobTooBigException) { + // 🔥 WORKAROUND: Если сообщение слишком большое - загружаем по одному + Log.w(TAG, "⚠️ SQLiteBlobTooBigException - loading messages one by one") + loadMessagesOneByOne(account, dialogKey, isSavedMessages, PAGE_SIZE) } + Log.d(TAG, "📋 Loaded entities from DB: count=${entities.size}") hasMoreMessages = entities.size >= PAGE_SIZE currentOffset = entities.size @@ -489,9 +517,11 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { } } + Log.d(TAG, "✅ Decrypted messages: count=${messages.size}") // 🔥 Сохраняем в кэш для мгновенной повторной загрузки! dialogMessagesCache[dialogKey] = messages.toList() + Log.d(TAG, "💾 Saved to cache: dialogKey=$dialogKey, count=${messages.size}") // 🔥 СРАЗУ обновляем UI - пользователь видит сообщения мгновенно // НО сохраняем оптимистичные сообщения (SENDING), которые ещё не в БД @@ -528,6 +558,9 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { isLoadingMessages = false } catch (e: Exception) { + Log.e(TAG, "❌ Error loading messages from DB", e) + Log.e(TAG, "❌ Exception details: ${e.message}") + Log.e(TAG, "❌ Stack trace: ${e.stackTraceToString()}") withContext(Dispatchers.Main.immediate) { _isLoading.value = false } @@ -536,6 +569,43 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { } } + /** + * 🔥 WORKAROUND: Загрузка сообщений по одному (для случаев когда сообщение слишком большое) + * Пропускает сообщения которые не могут быть загружены из-за размера + */ + private suspend fun loadMessagesOneByOne( + account: String, + dialogKey: String, + isSavedMessages: Boolean, + limit: Int + ): List { + val result = mutableListOf() + var offset = 0 + var loadedCount = 0 + + while (loadedCount < limit && offset < limit * 2) { // limit * 2 чтобы не зациклиться + try { + val batch = if (isSavedMessages) { + messageDao.getMessagesForSavedDialog(account, limit = 1, offset = offset) + } else { + messageDao.getMessages(account, dialogKey, limit = 1, offset = offset) + } + + if (batch.isEmpty()) break + + result.addAll(batch) + loadedCount++ + } catch (e: android.database.sqlite.SQLiteBlobTooBigException) { + Log.w(TAG, "⚠️ Skipping message at offset $offset - too big") + // Пропускаем это сообщение + } + offset++ + } + + Log.d(TAG, "📦 Loaded $loadedCount messages one by one (skipped ${offset - loadedCount})") + return result + } + /** * 🔥 Фоновое обновление сообщений из БД (проверка новых) * Вызывается когда кэш уже отображён, но нужно проверить есть ли новые сообщения