feat: Enhance connection handling and add debug logs feature; improve user experience and troubleshooting
This commit is contained in:
@@ -813,7 +813,11 @@ fun ChatDetailScreen(
|
||||
// Закрываем клавиатуру перед открытием меню
|
||||
keyboardController?.hide()
|
||||
focusManager.clearFocus()
|
||||
showMenu = true
|
||||
// Даём клавиатуре время закрыться перед показом bottom sheet
|
||||
scope.launch {
|
||||
delay(150) // Задержка для плавного закрытия клавиатуры
|
||||
showMenu = true
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
|
||||
@@ -43,7 +43,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
* Вызывается при удалении диалога
|
||||
*/
|
||||
fun clearDialogCache(dialogKey: String) {
|
||||
android.util.Log.d(TAG, "🗑️ Clearing ChatViewModel cache for: $dialogKey")
|
||||
dialogMessagesCache.remove(dialogKey)
|
||||
}
|
||||
|
||||
@@ -52,10 +51,8 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
* Удаляет все ключи содержащие этот publicKey
|
||||
*/
|
||||
fun clearCacheForOpponent(opponentKey: String) {
|
||||
android.util.Log.d(TAG, "🗑️ Clearing ChatViewModel cache for opponent: ${opponentKey.take(16)}...")
|
||||
val keysToRemove = dialogMessagesCache.keys.filter { it.contains(opponentKey) }
|
||||
keysToRemove.forEach {
|
||||
android.util.Log.d(TAG, "🗑️ Removing cache key: $it")
|
||||
dialogMessagesCache.remove(it)
|
||||
}
|
||||
}
|
||||
@@ -212,7 +209,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
dialogDao.updateDialogFromMessages(account, opponentKey ?: return@launch)
|
||||
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e(TAG, "Error adding latest message", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -342,7 +338,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
lastReadMessageTimestamp = 0L
|
||||
readReceiptSentForCurrentDialog = false
|
||||
isDialogActive = true // 🔥 Диалог активен!
|
||||
android.util.Log.d("ChatViewModel", "✅ Dialog active flag set to TRUE in openDialog")
|
||||
|
||||
// 📨 Применяем Forward сообщения СРАЗУ после сброса
|
||||
if (hasForward) {
|
||||
@@ -378,9 +373,7 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
* Как setCurrentDialogPublicKeyView("") в архиве
|
||||
*/
|
||||
fun closeDialog() {
|
||||
android.util.Log.d("ChatViewModel", "🔒 CLOSE DIALOG")
|
||||
isDialogActive = false
|
||||
android.util.Log.d("ChatViewModel", "❌ Dialog active flag set to FALSE")
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -474,10 +467,7 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
launch(Dispatchers.IO) {
|
||||
// 👁️ Отмечаем как прочитанные ТОЛЬКО если диалог активен
|
||||
if (isDialogActive) {
|
||||
android.util.Log.d("ChatViewModel", "📖 Marking dialog as read (dialog is active)")
|
||||
messageDao.markDialogAsRead(account, dialogKey)
|
||||
} else {
|
||||
android.util.Log.d("ChatViewModel", "⏸️ NOT marking as read (dialog not active)")
|
||||
}
|
||||
// 🔥 Пересчитываем счетчики из messages
|
||||
dialogDao.updateDialogFromMessages(account, opponent)
|
||||
@@ -545,10 +535,7 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
// 👁️ Фоновые операции - НЕ помечаем как прочитанные если диалог неактивен!
|
||||
if (isDialogActive) {
|
||||
android.util.Log.d("ChatViewModel", "📖 Marking dialog as read in refresh (dialog is active)")
|
||||
messageDao.markDialogAsRead(account, dialogKey)
|
||||
} else {
|
||||
android.util.Log.d("ChatViewModel", "⏸️ NOT marking as read in refresh (dialog not active)")
|
||||
}
|
||||
// 🔥 Пересчитываем счетчики из messages
|
||||
dialogDao.updateDialogFromMessages(account, opponent)
|
||||
|
||||
@@ -202,6 +202,7 @@ fun ChatsListScreen(
|
||||
|
||||
// Status dialog state
|
||||
var showStatusDialog by remember { mutableStateOf(false) }
|
||||
val debugLogs by ProtocolManager.debugLogs.collectAsState()
|
||||
|
||||
// <20> FCM токен диалог
|
||||
var showFcmDialog by remember { mutableStateOf(false) }
|
||||
@@ -239,14 +240,33 @@ fun ChatsListScreen(
|
||||
}
|
||||
*/
|
||||
|
||||
// Status dialog
|
||||
// Status dialog with logs
|
||||
if (showStatusDialog) {
|
||||
val clipboardManager = androidx.compose.ui.platform.LocalClipboardManager.current
|
||||
val scrollState = rememberScrollState()
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = { showStatusDialog = false },
|
||||
title = { Text("Connection Status", fontWeight = FontWeight.Bold) },
|
||||
title = {
|
||||
Text(
|
||||
"Connection Status & Logs",
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = textColor
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Column {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(max = 500.dp)
|
||||
) {
|
||||
// Status indicator
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 12.dp)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(12.dp)
|
||||
@@ -268,14 +288,118 @@ fun ChatsListScreen(
|
||||
ProtocolState.HANDSHAKING -> "Authenticating..."
|
||||
ProtocolState.AUTHENTICATED -> "Authenticated"
|
||||
},
|
||||
fontSize = 16.sp
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = textColor
|
||||
)
|
||||
}
|
||||
|
||||
Divider(
|
||||
color = if (isDarkTheme) Color(0xFF424242) else Color(0xFFE0E0E0),
|
||||
modifier = Modifier.padding(vertical = 8.dp)
|
||||
)
|
||||
|
||||
// Logs header with copy button
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 8.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
"Debug Logs:",
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = secondaryTextColor
|
||||
)
|
||||
TextButton(
|
||||
onClick = {
|
||||
val logsText = debugLogs.joinToString("\n")
|
||||
clipboardManager.setText(androidx.compose.ui.text.AnnotatedString(logsText))
|
||||
android.widget.Toast.makeText(
|
||||
context,
|
||||
"Logs copied to clipboard!",
|
||||
android.widget.Toast.LENGTH_SHORT
|
||||
).show()
|
||||
},
|
||||
enabled = debugLogs.isNotEmpty()
|
||||
) {
|
||||
Text(
|
||||
"Copy All",
|
||||
fontSize = 12.sp,
|
||||
color = if (debugLogs.isNotEmpty()) PrimaryBlue else Color.Gray
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Logs content
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f, fill = false)
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(if (isDarkTheme) Color(0xFF1A1A1A) else Color(0xFFF5F5F5))
|
||||
.padding(8.dp)
|
||||
) {
|
||||
if (debugLogs.isEmpty()) {
|
||||
Text(
|
||||
"No logs available.\nLogs are disabled by default for performance.\n\nEnable with:\nProtocolManager.enableUILogs(true)",
|
||||
fontSize = 12.sp,
|
||||
color = secondaryTextColor,
|
||||
modifier = Modifier.padding(8.dp)
|
||||
)
|
||||
} else {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.verticalScroll(scrollState)
|
||||
) {
|
||||
debugLogs.forEach { log ->
|
||||
Text(
|
||||
log,
|
||||
fontSize = 11.sp,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = textColor,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 2.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enable/Disable logs button
|
||||
TextButton(
|
||||
onClick = {
|
||||
ProtocolManager.enableUILogs(!debugLogs.isNotEmpty())
|
||||
android.widget.Toast.makeText(
|
||||
context,
|
||||
if (debugLogs.isEmpty()) "Logs enabled" else "Logs disabled",
|
||||
android.widget.Toast.LENGTH_SHORT
|
||||
).show()
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 8.dp)
|
||||
) {
|
||||
Text(
|
||||
if (debugLogs.isEmpty()) "⚠️ Enable Logs" else "Disable Logs",
|
||||
fontSize = 12.sp,
|
||||
color = if (debugLogs.isEmpty()) Color(0xFFFFC107) else Color.Gray
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
Button(onClick = { showStatusDialog = false }) {
|
||||
Text("OK")
|
||||
Button(
|
||||
onClick = { showStatusDialog = false },
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = PrimaryBlue
|
||||
)
|
||||
) {
|
||||
Text("Close", color = Color.White)
|
||||
}
|
||||
},
|
||||
containerColor = if (isDarkTheme) Color(0xFF212121) else Color.White
|
||||
|
||||
@@ -141,11 +141,9 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
|
||||
|
||||
// 📬 Подписываемся на requests (запросы от новых пользователей)
|
||||
viewModelScope.launch {
|
||||
android.util.Log.d("ChatsVM", "📬 Subscribing to requests for publicKey=${publicKey.take(16)}...")
|
||||
dialogDao.getRequestsFlow(publicKey)
|
||||
.flowOn(Dispatchers.IO)
|
||||
.map { requestsList ->
|
||||
android.util.Log.d("ChatsVM", "📬 Received ${requestsList.size} requests from DB")
|
||||
requestsList.map { dialog ->
|
||||
// 🔥 Загружаем информацию о пользователе если её нет
|
||||
if (dialog.opponentTitle.isEmpty() || dialog.opponentTitle == dialog.opponentKey) {
|
||||
@@ -267,10 +265,6 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
|
||||
if (currentAccount.isEmpty()) return
|
||||
|
||||
try {
|
||||
android.util.Log.d("ChatsVM", "🗑️ ========== DELETE START ==========")
|
||||
android.util.Log.d("ChatsVM", "🗑️ opponentKey=${opponentKey}")
|
||||
android.util.Log.d("ChatsVM", "🗑️ currentAccount=${currentAccount}")
|
||||
|
||||
// 🚀 Сразу обновляем UI - удаляем диалог из локального списка
|
||||
_dialogs.value = _dialogs.value.filter { it.opponentKey != opponentKey }
|
||||
// 🔥 Также удаляем из requests!
|
||||
@@ -278,15 +272,12 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
|
||||
// 🔥 Обновляем счетчик requests
|
||||
_requestsCount.value = _requests.value.size
|
||||
|
||||
android.util.Log.d("ChatsVM", "🗑️ UI updated: dialogs=${_dialogs.value.size}, requests=${_requests.value.size}")
|
||||
|
||||
// Вычисляем правильный dialog_key (отсортированная комбинация ключей)
|
||||
val dialogKey = if (currentAccount < opponentKey) {
|
||||
"$currentAccount:$opponentKey"
|
||||
} else {
|
||||
"$opponentKey:$currentAccount"
|
||||
}
|
||||
android.util.Log.d("ChatsVM", "🗑️ dialogKey=$dialogKey")
|
||||
|
||||
// 🗑️ 1. Очищаем ВСЕ кэши сообщений
|
||||
MessageRepository.getInstance(getApplication()).clearDialogCache(opponentKey)
|
||||
@@ -295,14 +286,12 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
|
||||
|
||||
// 🗑️ 2. Проверяем сколько сообщений в БД до удаления
|
||||
val messageCountBefore = database.messageDao().getMessageCount(currentAccount, dialogKey)
|
||||
android.util.Log.d("ChatsVM", "🗑️ Messages BEFORE delete: $messageCountBefore")
|
||||
|
||||
// 🗑️ 3. Удаляем все сообщения из диалога по dialog_key
|
||||
val deletedByDialogKey = database.messageDao().deleteDialog(
|
||||
account = currentAccount,
|
||||
dialogKey = dialogKey
|
||||
)
|
||||
android.util.Log.d("ChatsVM", "🗑️ Deleted by dialogKey: $deletedByDialogKey")
|
||||
|
||||
// 🗑️ 4. Также удаляем по from/to ключам (на всякий случай - старые сообщения)
|
||||
val deletedBetweenUsers = database.messageDao().deleteMessagesBetweenUsers(
|
||||
@@ -310,11 +299,9 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
|
||||
user1 = opponentKey,
|
||||
user2 = currentAccount
|
||||
)
|
||||
android.util.Log.d("ChatsVM", "🗑️ Deleted between users: $deletedBetweenUsers")
|
||||
|
||||
// 🗑️ 5. Проверяем сколько сообщений осталось
|
||||
val messageCountAfter = database.messageDao().getMessageCount(currentAccount, dialogKey)
|
||||
android.util.Log.d("ChatsVM", "🗑️ Messages AFTER delete: $messageCountAfter")
|
||||
|
||||
// 🗑️ 6. Удаляем диалог из таблицы dialogs
|
||||
database.dialogDao().deleteDialog(
|
||||
@@ -324,12 +311,8 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
|
||||
|
||||
// 🗑️ 7. Проверяем что диалог удален
|
||||
val dialogAfter = database.dialogDao().getDialog(currentAccount, opponentKey)
|
||||
android.util.Log.d("ChatsVM", "🗑️ Dialog after delete: ${dialogAfter?.opponentKey ?: "NULL (deleted)"}")
|
||||
|
||||
android.util.Log.d("ChatsVM", "🗑️ ========== DELETE COMPLETE ==========")
|
||||
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("ChatsVM", "🗑️ DELETE ERROR: ${e.message}", e)
|
||||
// В случае ошибки - возвращаем диалог обратно (откатываем оптимистичное обновление)
|
||||
// Flow обновится автоматически из БД
|
||||
}
|
||||
@@ -378,8 +361,6 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
|
||||
// 🔥 ВАЖНО: Используем хеш ключа, как в MessageRepository.requestUserInfo
|
||||
val privateKeyHash = CryptoManager.generatePrivateKeyHash(currentUserPrivateKey)
|
||||
|
||||
android.util.Log.d("ChatsVM", "📬 Requesting user info for: ${publicKey.take(16)}...")
|
||||
|
||||
// Запрашиваем информацию о пользователе с сервера
|
||||
val packet = PacketSearch().apply {
|
||||
this.privateKey = privateKeyHash
|
||||
@@ -387,7 +368,6 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
|
||||
}
|
||||
ProtocolManager.send(packet)
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("ChatsVM", "📬 Error loading user info: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user