feat: Add detailed logging for dialog management and message loading in ChatViewModel

This commit is contained in:
k1ngsterr1
2026-01-26 14:28:04 +05:00
parent d0a9431842
commit 3c08adb516

View File

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