feat: Enhance connection handling and add debug logs feature; improve user experience and troubleshooting
This commit is contained in:
@@ -85,28 +85,16 @@ class KeyboardTransitionCoordinator {
|
|||||||
hideKeyboard: () -> Unit,
|
hideKeyboard: () -> Unit,
|
||||||
showEmoji: () -> Unit
|
showEmoji: () -> Unit
|
||||||
) {
|
) {
|
||||||
Log.d(TAG, "═══════════════════════════════════════════════════════")
|
|
||||||
Log.d(TAG, "📱 requestShowEmoji() START")
|
|
||||||
Log.d(TAG, "🔄 Keyboard → Emoji transition started")
|
|
||||||
Log.d(TAG, " 📊 Current state:")
|
|
||||||
Log.d(TAG, " - currentState=$currentState")
|
|
||||||
Log.d(TAG, " - keyboardHeight=$keyboardHeight, emojiHeight=$emojiHeight")
|
|
||||||
Log.d(TAG, " - maxKeyboardHeight=$maxKeyboardHeight")
|
|
||||||
|
|
||||||
currentState = TransitionState.KEYBOARD_TO_EMOJI
|
currentState = TransitionState.KEYBOARD_TO_EMOJI
|
||||||
isTransitioning = true
|
isTransitioning = true
|
||||||
|
|
||||||
// 🔥 Гарантируем что emojiHeight = maxKeyboardHeight (не меняется при закрытии клавиатуры)
|
// 🔥 Гарантируем что emojiHeight = maxKeyboardHeight (не меняется при закрытии клавиатуры)
|
||||||
if (maxKeyboardHeight > 0.dp) {
|
if (maxKeyboardHeight > 0.dp) {
|
||||||
emojiHeight = maxKeyboardHeight
|
emojiHeight = maxKeyboardHeight
|
||||||
Log.d(TAG, " 📌 Locked emojiHeight to maxKeyboardHeight: $emojiHeight")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔥 ПОКАЗЫВАЕМ EMOJI СРАЗУ! Не ждем закрытия клавиатуры
|
// 🔥 ПОКАЗЫВАЕМ EMOJI СРАЗУ! Не ждем закрытия клавиатуры
|
||||||
Log.d(TAG, " 🚀 IMMEDIATELY showing emoji (no waiting for keyboard close)")
|
|
||||||
showEmoji()
|
showEmoji()
|
||||||
isEmojiVisible = true
|
|
||||||
Log.d(TAG, " ✅ showEmoji() called, isEmojiVisible=true")
|
|
||||||
|
|
||||||
// Теперь скрываем клавиатуру (она будет закрываться синхронно с появлением emoji)
|
// Теперь скрываем клавиатуру (она будет закрываться синхронно с появлением emoji)
|
||||||
Log.d(TAG, " ⌨️ Hiding keyboard...")
|
Log.d(TAG, " ⌨️ Hiding keyboard...")
|
||||||
@@ -114,7 +102,6 @@ class KeyboardTransitionCoordinator {
|
|||||||
hideKeyboard()
|
hideKeyboard()
|
||||||
Log.d(TAG, " ✅ hideKeyboard() completed")
|
Log.d(TAG, " ✅ hideKeyboard() completed")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "❌ Error hiding keyboard", e)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isKeyboardVisible = false
|
isKeyboardVisible = false
|
||||||
@@ -123,8 +110,6 @@ class KeyboardTransitionCoordinator {
|
|||||||
|
|
||||||
// Очищаем pending callback - больше не нужен
|
// Очищаем pending callback - больше не нужен
|
||||||
pendingShowEmojiCallback = null
|
pendingShowEmojiCallback = null
|
||||||
|
|
||||||
Log.d(TAG, "✅ requestShowEmoji() completed")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============ Главный метод: Emoji → Keyboard ============
|
// ============ Главный метод: Emoji → Keyboard ============
|
||||||
@@ -137,33 +122,18 @@ class KeyboardTransitionCoordinator {
|
|||||||
showKeyboard: () -> Unit,
|
showKeyboard: () -> Unit,
|
||||||
hideEmoji: () -> Unit
|
hideEmoji: () -> Unit
|
||||||
) {
|
) {
|
||||||
Log.d(TAG, "═══════════════════════════════════════════════════════")
|
|
||||||
Log.d(TAG, "⌨️ requestShowKeyboard() START")
|
|
||||||
Log.d(TAG, "🔄 Emoji → Keyboard transition started")
|
|
||||||
Log.d(TAG, " 📊 Current state:")
|
|
||||||
Log.d(TAG, " - currentState=$currentState")
|
|
||||||
Log.d(TAG, " - keyboardHeight=$keyboardHeight, emojiHeight=$emojiHeight")
|
|
||||||
Log.d(TAG, " - isTransitioning=$isTransitioning")
|
|
||||||
Log.d(TAG, " - isKeyboardVisible=$isKeyboardVisible, isEmojiVisible=$isEmojiVisible")
|
|
||||||
Log.d(TAG, " - pendingShowEmojiCallback=${if (pendingShowEmojiCallback != null) "EXISTS" else "null"}")
|
|
||||||
|
|
||||||
// 🔥 Отменяем pending emoji callback если он есть (предотвращаем конфликт)
|
// 🔥 Отменяем pending emoji callback если он есть (предотвращаем конфликт)
|
||||||
if (pendingShowEmojiCallback != null) {
|
if (pendingShowEmojiCallback != null) {
|
||||||
Log.d(TAG, "⚠️ Cancelling pending emoji callback (switching to keyboard)")
|
|
||||||
pendingShowEmojiCallback = null
|
pendingShowEmojiCallback = null
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d(TAG, " 📞 Setting currentState = EMOJI_TO_KEYBOARD")
|
|
||||||
currentState = TransitionState.EMOJI_TO_KEYBOARD
|
currentState = TransitionState.EMOJI_TO_KEYBOARD
|
||||||
isTransitioning = true
|
isTransitioning = true
|
||||||
|
|
||||||
// Шаг 1: Показать системную клавиатуру
|
// Шаг 1: Показать системную клавиатуру
|
||||||
Log.d(TAG, " 📞 Calling showKeyboard()...")
|
|
||||||
try {
|
try {
|
||||||
showKeyboard()
|
showKeyboard()
|
||||||
Log.d(TAG, " ✅ showKeyboard() completed")
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "❌ Error showing keyboard", e)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Шаг 2: Через небольшую задержку скрыть emoji
|
// Шаг 2: Через небольшую задержку скрыть emoji
|
||||||
@@ -177,10 +147,8 @@ class KeyboardTransitionCoordinator {
|
|||||||
Handler(Looper.getMainLooper()).postDelayed({
|
Handler(Looper.getMainLooper()).postDelayed({
|
||||||
currentState = TransitionState.IDLE
|
currentState = TransitionState.IDLE
|
||||||
isTransitioning = false
|
isTransitioning = false
|
||||||
Log.d(TAG, "✅ Keyboard visible, emoji hidden")
|
|
||||||
}, TRANSITION_DURATION)
|
}, TRANSITION_DURATION)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "❌ Error in requestShowKeyboard transition", e)
|
|
||||||
currentState = TransitionState.IDLE
|
currentState = TransitionState.IDLE
|
||||||
isTransitioning = false
|
isTransitioning = false
|
||||||
}
|
}
|
||||||
@@ -193,8 +161,6 @@ class KeyboardTransitionCoordinator {
|
|||||||
* Открыть только emoji панель (без клавиатуры).
|
* Открыть только emoji панель (без клавиатуры).
|
||||||
*/
|
*/
|
||||||
fun openEmojiOnly(showEmoji: () -> Unit) {
|
fun openEmojiOnly(showEmoji: () -> Unit) {
|
||||||
Log.d(TAG, "😊 Opening emoji panel only")
|
|
||||||
|
|
||||||
currentState = TransitionState.EMOJI_OPENING
|
currentState = TransitionState.EMOJI_OPENING
|
||||||
isTransitioning = true
|
isTransitioning = true
|
||||||
|
|
||||||
@@ -209,7 +175,6 @@ class KeyboardTransitionCoordinator {
|
|||||||
Handler(Looper.getMainLooper()).postDelayed({
|
Handler(Looper.getMainLooper()).postDelayed({
|
||||||
currentState = TransitionState.IDLE
|
currentState = TransitionState.IDLE
|
||||||
isTransitioning = false
|
isTransitioning = false
|
||||||
Log.d(TAG, "✅ Emoji panel opened")
|
|
||||||
}, TRANSITION_DURATION)
|
}, TRANSITION_DURATION)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,8 +182,6 @@ class KeyboardTransitionCoordinator {
|
|||||||
* Закрыть emoji панель.
|
* Закрыть emoji панель.
|
||||||
*/
|
*/
|
||||||
fun closeEmoji(hideEmoji: () -> Unit) {
|
fun closeEmoji(hideEmoji: () -> Unit) {
|
||||||
Log.d(TAG, "😊 Closing emoji panel")
|
|
||||||
|
|
||||||
currentState = TransitionState.EMOJI_CLOSING
|
currentState = TransitionState.EMOJI_CLOSING
|
||||||
isTransitioning = true
|
isTransitioning = true
|
||||||
|
|
||||||
@@ -228,7 +191,6 @@ class KeyboardTransitionCoordinator {
|
|||||||
Handler(Looper.getMainLooper()).postDelayed({
|
Handler(Looper.getMainLooper()).postDelayed({
|
||||||
currentState = TransitionState.IDLE
|
currentState = TransitionState.IDLE
|
||||||
isTransitioning = false
|
isTransitioning = false
|
||||||
Log.d(TAG, "✅ Emoji panel closed")
|
|
||||||
}, TRANSITION_DURATION)
|
}, TRANSITION_DURATION)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,8 +198,6 @@ class KeyboardTransitionCoordinator {
|
|||||||
* Закрыть системную клавиатуру.
|
* Закрыть системную клавиатуру.
|
||||||
*/
|
*/
|
||||||
fun closeKeyboard(hideKeyboard: () -> Unit) {
|
fun closeKeyboard(hideKeyboard: () -> Unit) {
|
||||||
Log.d(TAG, "⌨️ Closing keyboard")
|
|
||||||
|
|
||||||
currentState = TransitionState.KEYBOARD_CLOSING
|
currentState = TransitionState.KEYBOARD_CLOSING
|
||||||
isTransitioning = true
|
isTransitioning = true
|
||||||
|
|
||||||
@@ -247,7 +207,6 @@ class KeyboardTransitionCoordinator {
|
|||||||
Handler(Looper.getMainLooper()).postDelayed({
|
Handler(Looper.getMainLooper()).postDelayed({
|
||||||
currentState = TransitionState.IDLE
|
currentState = TransitionState.IDLE
|
||||||
isTransitioning = false
|
isTransitioning = false
|
||||||
Log.d(TAG, "✅ Keyboard closed")
|
|
||||||
}, TRANSITION_DURATION)
|
}, TRANSITION_DURATION)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,7 +221,6 @@ class KeyboardTransitionCoordinator {
|
|||||||
|
|
||||||
// Логируем раз в 50ms ИЛИ при значительном изменении высоты (>5dp)
|
// Логируем раз в 50ms ИЛИ при значительном изменении высоты (>5dp)
|
||||||
if (heightChanged && (now - lastLogTime > 50 || lastLoggedHeight < 0)) {
|
if (heightChanged && (now - lastLogTime > 50 || lastLoggedHeight < 0)) {
|
||||||
Log.d(TAG, "⌨️ KB: ${height.value.toInt()}dp, emoji: ${emojiHeight.value.toInt()}dp, visible=$isKeyboardVisible")
|
|
||||||
lastLogTime = now
|
lastLogTime = now
|
||||||
lastLoggedHeight = height.value
|
lastLoggedHeight = height.value
|
||||||
}
|
}
|
||||||
@@ -351,7 +309,6 @@ class KeyboardTransitionCoordinator {
|
|||||||
* Сброс состояния (для отладки).
|
* Сброс состояния (для отладки).
|
||||||
*/
|
*/
|
||||||
fun reset() {
|
fun reset() {
|
||||||
Log.d(TAG, "🔄 Reset coordinator state")
|
|
||||||
currentState = TransitionState.IDLE
|
currentState = TransitionState.IDLE
|
||||||
isTransitioning = false
|
isTransitioning = false
|
||||||
isKeyboardVisible = false
|
isKeyboardVisible = false
|
||||||
@@ -363,14 +320,6 @@ class KeyboardTransitionCoordinator {
|
|||||||
* Логирование текущего состояния.
|
* Логирование текущего состояния.
|
||||||
*/
|
*/
|
||||||
fun logState() {
|
fun logState() {
|
||||||
Log.d(TAG, """
|
|
||||||
📊 Coordinator State:
|
|
||||||
- state: $currentState
|
|
||||||
- transitioning: $isTransitioning
|
|
||||||
- keyboardVisible: $isKeyboardVisible (height=$keyboardHeight)
|
|
||||||
- emojiVisible: $isEmojiVisible (height=$emojiHeight)
|
|
||||||
- progress: $transitionProgress
|
|
||||||
""".trimIndent())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -91,10 +91,6 @@ class MainActivity : ComponentActivity() {
|
|||||||
val notificationPermissionLauncher = rememberLauncherForActivityResult(
|
val notificationPermissionLauncher = rememberLauncherForActivityResult(
|
||||||
contract = ActivityResultContracts.RequestPermission(),
|
contract = ActivityResultContracts.RequestPermission(),
|
||||||
onResult = { isGranted ->
|
onResult = { isGranted ->
|
||||||
Log.d(TAG, "📬 Notification permission result: $isGranted")
|
|
||||||
if (!isGranted) {
|
|
||||||
Log.w(TAG, "⚠️ User denied notification permission")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -107,10 +103,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
) == PackageManager.PERMISSION_GRANTED
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
|
|
||||||
if (!hasPermission) {
|
if (!hasPermission) {
|
||||||
Log.d(TAG, "📬 Requesting notification permission...")
|
|
||||||
notificationPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
|
notificationPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
|
||||||
} else {
|
|
||||||
Log.d(TAG, "✅ Notification permission already granted")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -289,13 +282,10 @@ class MainActivity : ComponentActivity() {
|
|||||||
// Получаем FCM токен
|
// Получаем FCM токен
|
||||||
FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
|
FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
|
||||||
if (!task.isSuccessful) {
|
if (!task.isSuccessful) {
|
||||||
Log.e(TAG, "❌ Failed to get FCM token", task.exception)
|
|
||||||
return@addOnCompleteListener
|
return@addOnCompleteListener
|
||||||
}
|
}
|
||||||
|
|
||||||
val token = task.result
|
val token = task.result
|
||||||
Log.d(TAG, "🔔 FCM token (short): ${token?.take(20)}...")
|
|
||||||
Log.d(TAG, "🔔 FCM token (FULL): $token")
|
|
||||||
|
|
||||||
// Сохраняем токен локально
|
// Сохраняем токен локально
|
||||||
token?.let { saveFcmToken(it) }
|
token?.let { saveFcmToken(it) }
|
||||||
@@ -304,9 +294,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
// (см. вызов sendFcmTokenToServer в onAccountLogin)
|
// (см. вызов sendFcmTokenToServer в onAccountLogin)
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d(TAG, "✅ Firebase initialized successfully")
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "❌ Error initializing Firebase", e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,7 +304,6 @@ class MainActivity : ComponentActivity() {
|
|||||||
private fun saveFcmToken(token: String) {
|
private fun saveFcmToken(token: String) {
|
||||||
val prefs = getSharedPreferences("rosetta_prefs", MODE_PRIVATE)
|
val prefs = getSharedPreferences("rosetta_prefs", MODE_PRIVATE)
|
||||||
prefs.edit().putString("fcm_token", token).apply()
|
prefs.edit().putString("fcm_token", token).apply()
|
||||||
Log.d(TAG, "💾 FCM token saved locally")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -330,38 +317,27 @@ class MainActivity : ComponentActivity() {
|
|||||||
val token = prefs.getString("fcm_token", null)
|
val token = prefs.getString("fcm_token", null)
|
||||||
|
|
||||||
if (token == null) {
|
if (token == null) {
|
||||||
Log.d(TAG, "⚠️ Cannot send FCM token: Token not found")
|
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔥 КРИТИЧНО: Ждем пока протокол станет AUTHENTICATED
|
// 🔥 КРИТИЧНО: Ждем пока протокол станет AUTHENTICATED
|
||||||
var waitAttempts = 0
|
var waitAttempts = 0
|
||||||
while (ProtocolManager.state.value != ProtocolState.AUTHENTICATED && waitAttempts < 50) {
|
while (ProtocolManager.state.value != ProtocolState.AUTHENTICATED && waitAttempts < 50) {
|
||||||
Log.d(TAG, "⏳ Waiting for protocol to be authenticated... (attempt ${waitAttempts + 1}/50)")
|
|
||||||
delay(100) // Ждем 100ms
|
delay(100) // Ждем 100ms
|
||||||
waitAttempts++
|
waitAttempts++
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ProtocolManager.state.value != ProtocolState.AUTHENTICATED) {
|
if (ProtocolManager.state.value != ProtocolState.AUTHENTICATED) {
|
||||||
Log.e(TAG, "❌ Cannot send FCM token: Protocol not authenticated after 5 seconds")
|
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d(TAG, "📤 Sending FCM token to server (new format 0x10)...")
|
|
||||||
Log.d(TAG, " Token (short): ${token.take(20)}...")
|
|
||||||
Log.d(TAG, " Token (FULL): $token")
|
|
||||||
Log.d(TAG, " Action: SUBSCRIBE")
|
|
||||||
Log.d(TAG, " Protocol state: ${ProtocolManager.state.value}")
|
|
||||||
|
|
||||||
val packet = PacketPushNotification().apply {
|
val packet = PacketPushNotification().apply {
|
||||||
this.notificationsToken = token
|
this.notificationsToken = token
|
||||||
this.action = PushNotificationAction.SUBSCRIBE
|
this.action = PushNotificationAction.SUBSCRIBE
|
||||||
}
|
}
|
||||||
|
|
||||||
ProtocolManager.send(packet)
|
ProtocolManager.send(packet)
|
||||||
Log.d(TAG, "✅ FCM token sent to server (packet ID: 0x10)")
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "❌ Error sending FCM token to server", e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -151,10 +151,8 @@ class MessageRepository private constructor(private val context: Context) {
|
|||||||
attachments: List<MessageAttachment> = emptyList(),
|
attachments: List<MessageAttachment> = emptyList(),
|
||||||
replyToMessageId: String? = null
|
replyToMessageId: String? = null
|
||||||
): Message {
|
): Message {
|
||||||
android.util.Log.d("MessageRepo", "📤 sendMessage START: to=${toPublicKey.take(16)}...")
|
|
||||||
val account = currentAccount ?: throw IllegalStateException("Not initialized")
|
val account = currentAccount ?: throw IllegalStateException("Not initialized")
|
||||||
val privateKey = currentPrivateKey ?: throw IllegalStateException("Not initialized")
|
val privateKey = currentPrivateKey ?: throw IllegalStateException("Not initialized")
|
||||||
android.util.Log.d("MessageRepo", "📤 sendMessage: account=${account.take(16)}...")
|
|
||||||
|
|
||||||
val messageId = UUID.randomUUID().toString().replace("-", "").take(32)
|
val messageId = UUID.randomUUID().toString().replace("-", "").take(32)
|
||||||
val timestamp = System.currentTimeMillis()
|
val timestamp = System.currentTimeMillis()
|
||||||
@@ -220,7 +218,6 @@ class MessageRepository private constructor(private val context: Context) {
|
|||||||
|
|
||||||
// 🔥 Отмечаем что я отправлял сообщения в этот диалог (перемещает из requests в chats)
|
// 🔥 Отмечаем что я отправлял сообщения в этот диалог (перемещает из requests в chats)
|
||||||
val updatedRows = dialogDao.markIHaveSent(account, toPublicKey)
|
val updatedRows = dialogDao.markIHaveSent(account, toPublicKey)
|
||||||
android.util.Log.d("MessageRepo", "📤 MARKED i_have_sent=1 for opponent=${toPublicKey.take(16)}..., updatedRows=$updatedRows")
|
|
||||||
|
|
||||||
// Отправляем пакет
|
// Отправляем пакет
|
||||||
val packet = PacketMessage().apply {
|
val packet = PacketMessage().apply {
|
||||||
@@ -316,17 +313,14 @@ class MessageRepository private constructor(private val context: Context) {
|
|||||||
|
|
||||||
// ✅ Проверяем существование перед вставкой (защита от дубликатов)
|
// ✅ Проверяем существование перед вставкой (защита от дубликатов)
|
||||||
val stillExists = messageDao.messageExists(account, messageId)
|
val stillExists = messageDao.messageExists(account, messageId)
|
||||||
android.util.Log.d("MessageRepo", "📥 INCOMING: messageId=${messageId.take(16)}..., stillExists=$stillExists")
|
|
||||||
|
|
||||||
if (!stillExists) {
|
if (!stillExists) {
|
||||||
// Сохраняем в БД только если сообщения нет
|
// Сохраняем в БД только если сообщения нет
|
||||||
messageDao.insertMessage(entity)
|
messageDao.insertMessage(entity)
|
||||||
android.util.Log.d("MessageRepo", "📥 INSERTED message with read=0, fromMe=0")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновляем диалог ПОСЛЕ вставки сообщения
|
// Обновляем диалог ПОСЛЕ вставки сообщения
|
||||||
updateDialog(packet.fromPublicKey, plainText, packet.timestamp, incrementUnread = true)
|
updateDialog(packet.fromPublicKey, plainText, packet.timestamp, incrementUnread = true)
|
||||||
android.util.Log.d("MessageRepo", "📥 Dialog updated")
|
|
||||||
|
|
||||||
// 🔥 Запрашиваем информацию о пользователе для отображения имени вместо ключа
|
// 🔥 Запрашиваем информацию о пользователе для отображения имени вместо ключа
|
||||||
requestUserInfo(packet.fromPublicKey)
|
requestUserInfo(packet.fromPublicKey)
|
||||||
@@ -479,25 +473,20 @@ class MessageRepository private constructor(private val context: Context) {
|
|||||||
val account = currentAccount ?: return
|
val account = currentAccount ?: return
|
||||||
val privateKey = currentPrivateKey ?: return
|
val privateKey = currentPrivateKey ?: return
|
||||||
|
|
||||||
android.util.Log.d("MessageRepo", "📊 updateDialog: opponent=${opponentKey.take(16)}..., incrementUnread=$incrementUnread")
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 🔥 КРИТИЧНО: Сначала считаем реальное количество непрочитанных из messages
|
// 🔥 КРИТИЧНО: Сначала считаем реальное количество непрочитанных из messages
|
||||||
val unreadCount = messageDao.getUnreadCountForDialog(account, opponentKey)
|
val unreadCount = messageDao.getUnreadCountForDialog(account, opponentKey)
|
||||||
android.util.Log.d("MessageRepo", "📊 unreadCount from DB: $unreadCount")
|
|
||||||
|
|
||||||
// 🔒 Шифруем lastMessage
|
// 🔒 Шифруем lastMessage
|
||||||
val encryptedLastMessage = CryptoManager.encryptWithPassword(lastMessage, privateKey)
|
val encryptedLastMessage = CryptoManager.encryptWithPassword(lastMessage, privateKey)
|
||||||
|
|
||||||
// Проверяем существует ли диалог
|
// Проверяем существует ли диалог
|
||||||
val existing = dialogDao.getDialog(account, opponentKey)
|
val existing = dialogDao.getDialog(account, opponentKey)
|
||||||
android.util.Log.d("MessageRepo", "📊 existing dialog: ${existing != null}, currentUnread=${existing?.unreadCount}")
|
|
||||||
|
|
||||||
if (existing != null) {
|
if (existing != null) {
|
||||||
// Обновляем существующий диалог
|
// Обновляем существующий диалог
|
||||||
dialogDao.updateLastMessage(account, opponentKey, encryptedLastMessage, timestamp)
|
dialogDao.updateLastMessage(account, opponentKey, encryptedLastMessage, timestamp)
|
||||||
dialogDao.updateUnreadCount(account, opponentKey, unreadCount)
|
dialogDao.updateUnreadCount(account, opponentKey, unreadCount)
|
||||||
android.util.Log.d("MessageRepo", "📊 UPDATED dialog unread to: $unreadCount")
|
|
||||||
} else {
|
} else {
|
||||||
// Создаем новый диалог
|
// Создаем новый диалог
|
||||||
dialogDao.insertDialog(DialogEntity(
|
dialogDao.insertDialog(DialogEntity(
|
||||||
@@ -507,11 +496,9 @@ class MessageRepository private constructor(private val context: Context) {
|
|||||||
lastMessageTimestamp = timestamp,
|
lastMessageTimestamp = timestamp,
|
||||||
unreadCount = unreadCount
|
unreadCount = unreadCount
|
||||||
))
|
))
|
||||||
android.util.Log.d("MessageRepo", "📊 CREATED new dialog with unread: $unreadCount")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
android.util.Log.e("MessageRepo", "📊 ERROR in updateDialog: ${e.message}")
|
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -539,15 +526,10 @@ class MessageRepository private constructor(private val context: Context) {
|
|||||||
suspend fun updateDialogUserInfo(publicKey: String, title: String, username: String, verified: Int) {
|
suspend fun updateDialogUserInfo(publicKey: String, title: String, username: String, verified: Int) {
|
||||||
val account = currentAccount ?: return
|
val account = currentAccount ?: return
|
||||||
|
|
||||||
android.util.Log.d("MessageRepo", "📋 updateDialogUserInfo: publicKey=${publicKey.take(16)}..., title=$title, username=$username")
|
|
||||||
|
|
||||||
// Проверяем существует ли диалог с этим пользователем
|
// Проверяем существует ли диалог с этим пользователем
|
||||||
val existing = dialogDao.getDialog(account, publicKey)
|
val existing = dialogDao.getDialog(account, publicKey)
|
||||||
if (existing != null) {
|
if (existing != null) {
|
||||||
android.util.Log.d("MessageRepo", "📋 Updating existing dialog info for ${publicKey.take(16)}...")
|
|
||||||
dialogDao.updateOpponentInfo(account, publicKey, title, username, verified)
|
dialogDao.updateOpponentInfo(account, publicKey, title, username, verified)
|
||||||
} else {
|
|
||||||
android.util.Log.d("MessageRepo", "📋 Dialog not found for ${publicKey.take(16)}..., skipping")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -558,14 +540,12 @@ class MessageRepository private constructor(private val context: Context) {
|
|||||||
*/
|
*/
|
||||||
fun clearDialogCache(opponentKey: String) {
|
fun clearDialogCache(opponentKey: String) {
|
||||||
val dialogKey = getDialogKey(opponentKey)
|
val dialogKey = getDialogKey(opponentKey)
|
||||||
android.util.Log.d("MessageRepo", "🗑️ clearDialogCache: dialogKey=$dialogKey")
|
|
||||||
|
|
||||||
// Сначала устанавливаем пустой список чтобы все подписчики увидели
|
// Сначала устанавливаем пустой список чтобы все подписчики увидели
|
||||||
messageCache[dialogKey]?.value = emptyList()
|
messageCache[dialogKey]?.value = emptyList()
|
||||||
|
|
||||||
// Затем удаляем из кэша
|
// Затем удаляем из кэша
|
||||||
messageCache.remove(dialogKey)
|
messageCache.remove(dialogKey)
|
||||||
android.util.Log.d("MessageRepo", "🗑️ Cache cleared for dialogKey=$dialogKey")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -194,6 +194,18 @@ class Protocol(
|
|||||||
val currentState = _state.value
|
val currentState = _state.value
|
||||||
log("🔌 CONNECT CALLED: currentState=$currentState, reconnectAttempts=$reconnectAttempts, isConnecting=$isConnecting")
|
log("🔌 CONNECT CALLED: currentState=$currentState, reconnectAttempts=$reconnectAttempts, isConnecting=$isConnecting")
|
||||||
|
|
||||||
|
// КРИТИЧНО: Если уже подключены и аутентифицированы - не переподключаемся!
|
||||||
|
if (currentState == ProtocolState.AUTHENTICATED || currentState == ProtocolState.HANDSHAKING) {
|
||||||
|
log("✅ Already authenticated or handshaking, skipping connect()")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// КРИТИЧНО: Если уже CONNECTED и есть credentials - тоже пропускаем
|
||||||
|
if (currentState == ProtocolState.CONNECTED && webSocket != null) {
|
||||||
|
log("✅ Already connected with valid socket, skipping connect()")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// КРИТИЧНО: проверяем флаг isConnecting, а не только состояние
|
// КРИТИЧНО: проверяем флаг isConnecting, а не только состояние
|
||||||
if (isConnecting || currentState == ProtocolState.CONNECTING) {
|
if (isConnecting || currentState == ProtocolState.CONNECTING) {
|
||||||
log("⚠️ Already connecting, skipping... (preventing duplicate connect)")
|
log("⚠️ Already connecting, skipping... (preventing duplicate connect)")
|
||||||
|
|||||||
@@ -45,14 +45,11 @@ class RosettaFirebaseMessagingService : FirebaseMessagingService() {
|
|||||||
*/
|
*/
|
||||||
override fun onNewToken(token: String) {
|
override fun onNewToken(token: String) {
|
||||||
super.onNewToken(token)
|
super.onNewToken(token)
|
||||||
Log.d(TAG, "🔔 New FCM token (short): ${token.take(20)}...")
|
|
||||||
Log.d(TAG, "🔔 New FCM token (FULL): $token")
|
|
||||||
|
|
||||||
// Сохраняем токен локально
|
// Сохраняем токен локально
|
||||||
saveFcmToken(token)
|
saveFcmToken(token)
|
||||||
|
|
||||||
// 📤 Токен будет отправлен на сервер после успешного логина в MainActivity
|
// 📤 Токен будет отправлен на сервер после успешного логина в MainActivity
|
||||||
Log.d(TAG, "💾 FCM token saved. Will be sent to server after login.")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -60,11 +57,9 @@ class RosettaFirebaseMessagingService : FirebaseMessagingService() {
|
|||||||
*/
|
*/
|
||||||
override fun onMessageReceived(remoteMessage: RemoteMessage) {
|
override fun onMessageReceived(remoteMessage: RemoteMessage) {
|
||||||
super.onMessageReceived(remoteMessage)
|
super.onMessageReceived(remoteMessage)
|
||||||
Log.d(TAG, "📬 Push notification received from: ${remoteMessage.from}")
|
|
||||||
|
|
||||||
// Обрабатываем data payload
|
// Обрабатываем data payload
|
||||||
remoteMessage.data.isNotEmpty().let {
|
remoteMessage.data.isNotEmpty().let {
|
||||||
Log.d(TAG, "📦 Message data payload: ${remoteMessage.data}")
|
|
||||||
|
|
||||||
val type = remoteMessage.data["type"]
|
val type = remoteMessage.data["type"]
|
||||||
val senderPublicKey = remoteMessage.data["sender_public_key"]
|
val senderPublicKey = remoteMessage.data["sender_public_key"]
|
||||||
@@ -78,17 +73,14 @@ class RosettaFirebaseMessagingService : FirebaseMessagingService() {
|
|||||||
}
|
}
|
||||||
"message_read" -> {
|
"message_read" -> {
|
||||||
// Сообщение прочитано - можно обновить UI если приложение открыто
|
// Сообщение прочитано - можно обновить UI если приложение открыто
|
||||||
Log.d(TAG, "📖 Message read by $senderPublicKey")
|
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
Log.d(TAG, "⚠️ Unknown notification type: $type")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обрабатываем notification payload (если есть)
|
// Обрабатываем notification payload (если есть)
|
||||||
remoteMessage.notification?.let {
|
remoteMessage.notification?.let {
|
||||||
Log.d(TAG, "📨 Message Notification Body: ${it.body}")
|
|
||||||
showSimpleNotification(it.title ?: "Rosetta", it.body ?: "New message")
|
showSimpleNotification(it.title ?: "Rosetta", it.body ?: "New message")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -181,6 +173,5 @@ class RosettaFirebaseMessagingService : FirebaseMessagingService() {
|
|||||||
private fun saveFcmToken(token: String) {
|
private fun saveFcmToken(token: String) {
|
||||||
val prefs = getSharedPreferences("rosetta_prefs", Context.MODE_PRIVATE)
|
val prefs = getSharedPreferences("rosetta_prefs", Context.MODE_PRIVATE)
|
||||||
prefs.edit().putString("fcm_token", token).apply()
|
prefs.edit().putString("fcm_token", token).apply()
|
||||||
Log.d(TAG, "💾 FCM token saved locally")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -813,7 +813,11 @@ fun ChatDetailScreen(
|
|||||||
// Закрываем клавиатуру перед открытием меню
|
// Закрываем клавиатуру перед открытием меню
|
||||||
keyboardController?.hide()
|
keyboardController?.hide()
|
||||||
focusManager.clearFocus()
|
focusManager.clearFocus()
|
||||||
|
// Даём клавиатуре время закрыться перед показом bottom sheet
|
||||||
|
scope.launch {
|
||||||
|
delay(150) // Задержка для плавного закрытия клавиатуры
|
||||||
showMenu = true
|
showMenu = true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(48.dp)
|
.size(48.dp)
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
* Вызывается при удалении диалога
|
* Вызывается при удалении диалога
|
||||||
*/
|
*/
|
||||||
fun clearDialogCache(dialogKey: String) {
|
fun clearDialogCache(dialogKey: String) {
|
||||||
android.util.Log.d(TAG, "🗑️ Clearing ChatViewModel cache for: $dialogKey")
|
|
||||||
dialogMessagesCache.remove(dialogKey)
|
dialogMessagesCache.remove(dialogKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,10 +51,8 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
* Удаляет все ключи содержащие этот publicKey
|
* Удаляет все ключи содержащие этот publicKey
|
||||||
*/
|
*/
|
||||||
fun clearCacheForOpponent(opponentKey: String) {
|
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) }
|
val keysToRemove = dialogMessagesCache.keys.filter { it.contains(opponentKey) }
|
||||||
keysToRemove.forEach {
|
keysToRemove.forEach {
|
||||||
android.util.Log.d(TAG, "🗑️ Removing cache key: $it")
|
|
||||||
dialogMessagesCache.remove(it)
|
dialogMessagesCache.remove(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -212,7 +209,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
dialogDao.updateDialogFromMessages(account, opponentKey ?: return@launch)
|
dialogDao.updateDialogFromMessages(account, opponentKey ?: return@launch)
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} 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
|
lastReadMessageTimestamp = 0L
|
||||||
readReceiptSentForCurrentDialog = false
|
readReceiptSentForCurrentDialog = false
|
||||||
isDialogActive = true // 🔥 Диалог активен!
|
isDialogActive = true // 🔥 Диалог активен!
|
||||||
android.util.Log.d("ChatViewModel", "✅ Dialog active flag set to TRUE in openDialog")
|
|
||||||
|
|
||||||
// 📨 Применяем Forward сообщения СРАЗУ после сброса
|
// 📨 Применяем Forward сообщения СРАЗУ после сброса
|
||||||
if (hasForward) {
|
if (hasForward) {
|
||||||
@@ -378,9 +373,7 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
* Как setCurrentDialogPublicKeyView("") в архиве
|
* Как setCurrentDialogPublicKeyView("") в архиве
|
||||||
*/
|
*/
|
||||||
fun closeDialog() {
|
fun closeDialog() {
|
||||||
android.util.Log.d("ChatViewModel", "🔒 CLOSE DIALOG")
|
|
||||||
isDialogActive = false
|
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) {
|
launch(Dispatchers.IO) {
|
||||||
// 👁️ Отмечаем как прочитанные ТОЛЬКО если диалог активен
|
// 👁️ Отмечаем как прочитанные ТОЛЬКО если диалог активен
|
||||||
if (isDialogActive) {
|
if (isDialogActive) {
|
||||||
android.util.Log.d("ChatViewModel", "📖 Marking dialog as read (dialog is active)")
|
|
||||||
messageDao.markDialogAsRead(account, dialogKey)
|
messageDao.markDialogAsRead(account, dialogKey)
|
||||||
} else {
|
|
||||||
android.util.Log.d("ChatViewModel", "⏸️ NOT marking as read (dialog not active)")
|
|
||||||
}
|
}
|
||||||
// 🔥 Пересчитываем счетчики из messages
|
// 🔥 Пересчитываем счетчики из messages
|
||||||
dialogDao.updateDialogFromMessages(account, opponent)
|
dialogDao.updateDialogFromMessages(account, opponent)
|
||||||
@@ -545,10 +535,7 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
|
|
||||||
// 👁️ Фоновые операции - НЕ помечаем как прочитанные если диалог неактивен!
|
// 👁️ Фоновые операции - НЕ помечаем как прочитанные если диалог неактивен!
|
||||||
if (isDialogActive) {
|
if (isDialogActive) {
|
||||||
android.util.Log.d("ChatViewModel", "📖 Marking dialog as read in refresh (dialog is active)")
|
|
||||||
messageDao.markDialogAsRead(account, dialogKey)
|
messageDao.markDialogAsRead(account, dialogKey)
|
||||||
} else {
|
|
||||||
android.util.Log.d("ChatViewModel", "⏸️ NOT marking as read in refresh (dialog not active)")
|
|
||||||
}
|
}
|
||||||
// 🔥 Пересчитываем счетчики из messages
|
// 🔥 Пересчитываем счетчики из messages
|
||||||
dialogDao.updateDialogFromMessages(account, opponent)
|
dialogDao.updateDialogFromMessages(account, opponent)
|
||||||
|
|||||||
@@ -202,6 +202,7 @@ fun ChatsListScreen(
|
|||||||
|
|
||||||
// Status dialog state
|
// Status dialog state
|
||||||
var showStatusDialog by remember { mutableStateOf(false) }
|
var showStatusDialog by remember { mutableStateOf(false) }
|
||||||
|
val debugLogs by ProtocolManager.debugLogs.collectAsState()
|
||||||
|
|
||||||
// <20> FCM токен диалог
|
// <20> FCM токен диалог
|
||||||
var showFcmDialog by remember { mutableStateOf(false) }
|
var showFcmDialog by remember { mutableStateOf(false) }
|
||||||
@@ -239,14 +240,33 @@ fun ChatsListScreen(
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Status dialog
|
// Status dialog with logs
|
||||||
if (showStatusDialog) {
|
if (showStatusDialog) {
|
||||||
|
val clipboardManager = androidx.compose.ui.platform.LocalClipboardManager.current
|
||||||
|
val scrollState = rememberScrollState()
|
||||||
|
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = { showStatusDialog = false },
|
onDismissRequest = { showStatusDialog = false },
|
||||||
title = { Text("Connection Status", fontWeight = FontWeight.Bold) },
|
title = {
|
||||||
|
Text(
|
||||||
|
"Connection Status & Logs",
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = textColor
|
||||||
|
)
|
||||||
|
},
|
||||||
text = {
|
text = {
|
||||||
Column {
|
Column(
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.heightIn(max = 500.dp)
|
||||||
|
) {
|
||||||
|
// Status indicator
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 12.dp)
|
||||||
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(12.dp)
|
.size(12.dp)
|
||||||
@@ -268,14 +288,118 @@ fun ChatsListScreen(
|
|||||||
ProtocolState.HANDSHAKING -> "Authenticating..."
|
ProtocolState.HANDSHAKING -> "Authenticating..."
|
||||||
ProtocolState.AUTHENTICATED -> "Authenticated"
|
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 = {
|
confirmButton = {
|
||||||
Button(onClick = { showStatusDialog = false }) {
|
Button(
|
||||||
Text("OK")
|
onClick = { showStatusDialog = false },
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = PrimaryBlue
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Text("Close", color = Color.White)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
containerColor = if (isDarkTheme) Color(0xFF212121) else Color.White
|
containerColor = if (isDarkTheme) Color(0xFF212121) else Color.White
|
||||||
|
|||||||
@@ -141,11 +141,9 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
|
|||||||
|
|
||||||
// 📬 Подписываемся на requests (запросы от новых пользователей)
|
// 📬 Подписываемся на requests (запросы от новых пользователей)
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
android.util.Log.d("ChatsVM", "📬 Subscribing to requests for publicKey=${publicKey.take(16)}...")
|
|
||||||
dialogDao.getRequestsFlow(publicKey)
|
dialogDao.getRequestsFlow(publicKey)
|
||||||
.flowOn(Dispatchers.IO)
|
.flowOn(Dispatchers.IO)
|
||||||
.map { requestsList ->
|
.map { requestsList ->
|
||||||
android.util.Log.d("ChatsVM", "📬 Received ${requestsList.size} requests from DB")
|
|
||||||
requestsList.map { dialog ->
|
requestsList.map { dialog ->
|
||||||
// 🔥 Загружаем информацию о пользователе если её нет
|
// 🔥 Загружаем информацию о пользователе если её нет
|
||||||
if (dialog.opponentTitle.isEmpty() || dialog.opponentTitle == dialog.opponentKey) {
|
if (dialog.opponentTitle.isEmpty() || dialog.opponentTitle == dialog.opponentKey) {
|
||||||
@@ -267,10 +265,6 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
|
|||||||
if (currentAccount.isEmpty()) return
|
if (currentAccount.isEmpty()) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
android.util.Log.d("ChatsVM", "🗑️ ========== DELETE START ==========")
|
|
||||||
android.util.Log.d("ChatsVM", "🗑️ opponentKey=${opponentKey}")
|
|
||||||
android.util.Log.d("ChatsVM", "🗑️ currentAccount=${currentAccount}")
|
|
||||||
|
|
||||||
// 🚀 Сразу обновляем UI - удаляем диалог из локального списка
|
// 🚀 Сразу обновляем UI - удаляем диалог из локального списка
|
||||||
_dialogs.value = _dialogs.value.filter { it.opponentKey != opponentKey }
|
_dialogs.value = _dialogs.value.filter { it.opponentKey != opponentKey }
|
||||||
// 🔥 Также удаляем из requests!
|
// 🔥 Также удаляем из requests!
|
||||||
@@ -278,15 +272,12 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
|
|||||||
// 🔥 Обновляем счетчик requests
|
// 🔥 Обновляем счетчик requests
|
||||||
_requestsCount.value = _requests.value.size
|
_requestsCount.value = _requests.value.size
|
||||||
|
|
||||||
android.util.Log.d("ChatsVM", "🗑️ UI updated: dialogs=${_dialogs.value.size}, requests=${_requests.value.size}")
|
|
||||||
|
|
||||||
// Вычисляем правильный dialog_key (отсортированная комбинация ключей)
|
// Вычисляем правильный dialog_key (отсортированная комбинация ключей)
|
||||||
val dialogKey = if (currentAccount < opponentKey) {
|
val dialogKey = if (currentAccount < opponentKey) {
|
||||||
"$currentAccount:$opponentKey"
|
"$currentAccount:$opponentKey"
|
||||||
} else {
|
} else {
|
||||||
"$opponentKey:$currentAccount"
|
"$opponentKey:$currentAccount"
|
||||||
}
|
}
|
||||||
android.util.Log.d("ChatsVM", "🗑️ dialogKey=$dialogKey")
|
|
||||||
|
|
||||||
// 🗑️ 1. Очищаем ВСЕ кэши сообщений
|
// 🗑️ 1. Очищаем ВСЕ кэши сообщений
|
||||||
MessageRepository.getInstance(getApplication()).clearDialogCache(opponentKey)
|
MessageRepository.getInstance(getApplication()).clearDialogCache(opponentKey)
|
||||||
@@ -295,14 +286,12 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
|
|||||||
|
|
||||||
// 🗑️ 2. Проверяем сколько сообщений в БД до удаления
|
// 🗑️ 2. Проверяем сколько сообщений в БД до удаления
|
||||||
val messageCountBefore = database.messageDao().getMessageCount(currentAccount, dialogKey)
|
val messageCountBefore = database.messageDao().getMessageCount(currentAccount, dialogKey)
|
||||||
android.util.Log.d("ChatsVM", "🗑️ Messages BEFORE delete: $messageCountBefore")
|
|
||||||
|
|
||||||
// 🗑️ 3. Удаляем все сообщения из диалога по dialog_key
|
// 🗑️ 3. Удаляем все сообщения из диалога по dialog_key
|
||||||
val deletedByDialogKey = database.messageDao().deleteDialog(
|
val deletedByDialogKey = database.messageDao().deleteDialog(
|
||||||
account = currentAccount,
|
account = currentAccount,
|
||||||
dialogKey = dialogKey
|
dialogKey = dialogKey
|
||||||
)
|
)
|
||||||
android.util.Log.d("ChatsVM", "🗑️ Deleted by dialogKey: $deletedByDialogKey")
|
|
||||||
|
|
||||||
// 🗑️ 4. Также удаляем по from/to ключам (на всякий случай - старые сообщения)
|
// 🗑️ 4. Также удаляем по from/to ключам (на всякий случай - старые сообщения)
|
||||||
val deletedBetweenUsers = database.messageDao().deleteMessagesBetweenUsers(
|
val deletedBetweenUsers = database.messageDao().deleteMessagesBetweenUsers(
|
||||||
@@ -310,11 +299,9 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
|
|||||||
user1 = opponentKey,
|
user1 = opponentKey,
|
||||||
user2 = currentAccount
|
user2 = currentAccount
|
||||||
)
|
)
|
||||||
android.util.Log.d("ChatsVM", "🗑️ Deleted between users: $deletedBetweenUsers")
|
|
||||||
|
|
||||||
// 🗑️ 5. Проверяем сколько сообщений осталось
|
// 🗑️ 5. Проверяем сколько сообщений осталось
|
||||||
val messageCountAfter = database.messageDao().getMessageCount(currentAccount, dialogKey)
|
val messageCountAfter = database.messageDao().getMessageCount(currentAccount, dialogKey)
|
||||||
android.util.Log.d("ChatsVM", "🗑️ Messages AFTER delete: $messageCountAfter")
|
|
||||||
|
|
||||||
// 🗑️ 6. Удаляем диалог из таблицы dialogs
|
// 🗑️ 6. Удаляем диалог из таблицы dialogs
|
||||||
database.dialogDao().deleteDialog(
|
database.dialogDao().deleteDialog(
|
||||||
@@ -324,12 +311,8 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
|
|||||||
|
|
||||||
// 🗑️ 7. Проверяем что диалог удален
|
// 🗑️ 7. Проверяем что диалог удален
|
||||||
val dialogAfter = database.dialogDao().getDialog(currentAccount, opponentKey)
|
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) {
|
} catch (e: Exception) {
|
||||||
android.util.Log.e("ChatsVM", "🗑️ DELETE ERROR: ${e.message}", e)
|
|
||||||
// В случае ошибки - возвращаем диалог обратно (откатываем оптимистичное обновление)
|
// В случае ошибки - возвращаем диалог обратно (откатываем оптимистичное обновление)
|
||||||
// Flow обновится автоматически из БД
|
// Flow обновится автоматически из БД
|
||||||
}
|
}
|
||||||
@@ -378,8 +361,6 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
|
|||||||
// 🔥 ВАЖНО: Используем хеш ключа, как в MessageRepository.requestUserInfo
|
// 🔥 ВАЖНО: Используем хеш ключа, как в MessageRepository.requestUserInfo
|
||||||
val privateKeyHash = CryptoManager.generatePrivateKeyHash(currentUserPrivateKey)
|
val privateKeyHash = CryptoManager.generatePrivateKeyHash(currentUserPrivateKey)
|
||||||
|
|
||||||
android.util.Log.d("ChatsVM", "📬 Requesting user info for: ${publicKey.take(16)}...")
|
|
||||||
|
|
||||||
// Запрашиваем информацию о пользователе с сервера
|
// Запрашиваем информацию о пользователе с сервера
|
||||||
val packet = PacketSearch().apply {
|
val packet = PacketSearch().apply {
|
||||||
this.privateKey = privateKeyHash
|
this.privateKey = privateKeyHash
|
||||||
@@ -387,7 +368,6 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
|
|||||||
}
|
}
|
||||||
ProtocolManager.send(packet)
|
ProtocolManager.send(packet)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
android.util.Log.e("ChatsVM", "📬 Error loading user info: ${e.message}")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user