diff --git a/app/src/main/java/com/rosetta/messenger/MainActivity.kt b/app/src/main/java/com/rosetta/messenger/MainActivity.kt index 78d41c2..aab8f16 100644 --- a/app/src/main/java/com/rosetta/messenger/MainActivity.kt +++ b/app/src/main/java/com/rosetta/messenger/MainActivity.kt @@ -170,6 +170,8 @@ class MainActivity : ComponentActivity() { targetState = when { showSplash -> "splash" showOnboarding && hasExistingAccount == false -> "onboarding" + // 🔥 КРИТИЧНО: если currentAccount != null - сразу на main без ожидания isLoggedIn + currentAccount != null -> "main" isLoggedIn != true && hasExistingAccount == false -> "auth_new" isLoggedIn != true && hasExistingAccount == true -> "auth_unlock" else -> "main" @@ -210,7 +212,13 @@ class MainActivity : ComponentActivity() { currentAccount = account hasExistingAccount = true // Save as last logged account - account?.let { accountManager.setLastLoggedPublicKey(it.publicKey) } + account?.let { + accountManager.setLastLoggedPublicKey(it.publicKey) + // 🔥 КРИТИЧНО: Сразу устанавливаем флаг логина чтобы не было Loading экрана + scope.launch { + accountManager.setCurrentAccount(it.publicKey) + } + } // 📤 Отправляем FCM токен на сервер после успешной аутентификации account?.let { sendFcmTokenToServer(it) } @@ -354,33 +362,22 @@ fun MainScreen( onToggleTheme: () -> Unit = {}, onLogout: () -> Unit = {} ) { - // 🔥 КРИТИЧНО: Если account == null, показываем загрузку вместо мокового аккаунта - if (account == null) { - Box( - modifier = Modifier - .fillMaxSize() - .background(if (isDarkTheme) Color(0xFF1B1B1B) else Color.White), - contentAlignment = Alignment.Center - ) { - Column(horizontalAlignment = Alignment.CenterHorizontally) { - CircularProgressIndicator() - Spacer(modifier = Modifier.height(16.dp)) - Text( - "Loading account...", - color = if (isDarkTheme) Color.White else Color.Black - ) - } - } - return - } + // 🔥 Используем моковый аккаунт если account == null (для предотвращения крашей) + val effectiveAccount = account ?: DecryptedAccount( + publicKey = "loading", + privateKey = "loading", + seedPhrase = emptyList(), + privateKeyHash = "loading", + name = "Loading..." + ) - val accountName = account.name - val accountPhone = account.publicKey.take(16).let { + val accountName = effectiveAccount.name + val accountPhone = effectiveAccount.publicKey.take(16).let { "+${it.take(1)} ${it.substring(1, 4)} ${it.substring(4, 7)}${it.substring(7)}" } - val accountPublicKey = account.publicKey - val accountPrivateKey = account.privateKey - val privateKeyHash = account.privateKeyHash + val accountPublicKey = effectiveAccount.publicKey + val accountPrivateKey = effectiveAccount.privateKey + val privateKeyHash = effectiveAccount.privateKeyHash // Состояние протокола для передачи в SearchScreen val protocolState by ProtocolManager.state.collectAsState() diff --git a/app/src/main/java/com/rosetta/messenger/data/MessageRepository.kt b/app/src/main/java/com/rosetta/messenger/data/MessageRepository.kt index 2d37170..6becd72 100644 --- a/app/src/main/java/com/rosetta/messenger/data/MessageRepository.kt +++ b/app/src/main/java/com/rosetta/messenger/data/MessageRepository.kt @@ -368,6 +368,9 @@ class MessageRepository private constructor(private val context: Context) { // Обновляем кэш val dialogKey = getDialogKey(packet.toPublicKey) updateMessageStatus(dialogKey, packet.messageId, DeliveryStatus.DELIVERED) + + // 🔥 КРИТИЧНО: Обновляем диалог чтобы lastMessageDelivered обновился + dialogDao.updateDialogFromMessages(account, packet.toPublicKey) } /** @@ -389,6 +392,9 @@ class MessageRepository private constructor(private val context: Context) { else msg } } + + // 🔥 КРИТИЧНО: Обновляем диалог чтобы lastMessageRead обновился + dialogDao.updateDialogFromMessages(account, packet.fromPublicKey) } /** diff --git a/app/src/main/java/com/rosetta/messenger/database/MessageEntities.kt b/app/src/main/java/com/rosetta/messenger/database/MessageEntities.kt index 07e4eb3..11a743f 100644 --- a/app/src/main/java/com/rosetta/messenger/database/MessageEntities.kt +++ b/app/src/main/java/com/rosetta/messenger/database/MessageEntities.kt @@ -549,6 +549,7 @@ interface DialogDao { COALESCE( (SELECT delivered FROM messages WHERE account = :account + AND from_me = 1 AND ((from_public_key = :opponentKey AND to_public_key = :account) OR (from_public_key = :account AND to_public_key = :opponentKey)) ORDER BY timestamp DESC LIMIT 1), @@ -557,6 +558,7 @@ interface DialogDao { COALESCE( (SELECT read FROM messages WHERE account = :account + AND from_me = 1 AND ((from_public_key = :opponentKey AND to_public_key = :account) OR (from_public_key = :account AND to_public_key = :opponentKey)) ORDER BY timestamp DESC LIMIT 1), @@ -639,22 +641,8 @@ interface DialogDao { ) AS verified, 1 AS i_have_sent, 1 AS last_message_from_me, - COALESCE( - (SELECT delivered FROM messages - WHERE account = :account - AND from_public_key = :account - AND to_public_key = :account - ORDER BY timestamp DESC LIMIT 1), - 0 - ) AS last_message_delivered, - COALESCE( - (SELECT read FROM messages - WHERE account = :account - AND from_public_key = :account - AND to_public_key = :account - ORDER BY timestamp DESC LIMIT 1), - 0 - ) AS last_message_read + 1 AS last_message_delivered, + 1 AS last_message_read WHERE EXISTS ( SELECT 1 FROM messages WHERE account = :account 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 5c7a99e..5350fb9 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 @@ -1648,47 +1648,62 @@ fun DialogItemContent( ) { // Показываем статус только для исходящих сообщений if (dialog.lastMessageFromMe == 1) { - when (dialog.lastMessageDelivered) { - 2 -> { - // ERROR - показываем иконку ошибки - Icon( - imageVector = Icons.Outlined.ErrorOutline, - contentDescription = "Sending failed", - tint = Color(0xFFFF3B30), // iOS красный - modifier = Modifier.size(16.dp) - ) - Spacer(modifier = Modifier.width(4.dp)) - } - 1 -> { - // DELIVERED - две галочки - val checkmarkColor = if (dialog.lastMessageRead == 1) { - PrimaryBlue // прочитано - синие - } else { - secondaryTextColor.copy(alpha = 0.6f) // доставлено - серые + // 📁 Для Saved Messages всегда синие двойные галочки (прочитано) + if (dialog.isSavedMessages) { + Icon( + imageVector = Icons.Default.DoneAll, + contentDescription = null, + tint = PrimaryBlue, + modifier = Modifier.size(16.dp) + ) + Spacer(modifier = Modifier.width(4.dp)) + } else { + // 🔥 ЛОГИКА КАК В ДЕСКТОПЕ: + // Если delivered == DELIVERED: + // - unreadCount > 0 → одна галочка (есть непрочитанные входящие, собеседник не ответил) + // - unreadCount == 0 → две галочки (нет непрочитанных, собеседник видел/ответил) + when { + dialog.lastMessageDelivered == 2 -> { + // ERROR - показываем иконку ошибки + Icon( + imageVector = Icons.Outlined.ErrorOutline, + contentDescription = "Sending failed", + tint = Color(0xFFFF3B30), // iOS красный + modifier = Modifier.size(16.dp) + ) + Spacer(modifier = Modifier.width(4.dp)) + } + dialog.lastMessageDelivered == 1 -> { + // DELIVERED - смотрим на unreadCount + if (dialog.unreadCount > 0) { + // Есть непрочитанные входящие → одна серая галочка + Icon( + imageVector = Icons.Default.Done, + contentDescription = null, + tint = secondaryTextColor.copy(alpha = 0.6f), + modifier = Modifier.size(16.dp) + ) + } else { + // Нет непрочитанных → две синие галочки (прочитано) + Icon( + imageVector = Icons.Default.DoneAll, + contentDescription = null, + tint = PrimaryBlue, + modifier = Modifier.size(16.dp) + ) + } + Spacer(modifier = Modifier.width(4.dp)) + } + else -> { + // SENT/WAITING - одна серая галочка + Icon( + imageVector = Icons.Default.Done, + contentDescription = null, + tint = secondaryTextColor.copy(alpha = 0.6f), + modifier = Modifier.size(16.dp) + ) + Spacer(modifier = Modifier.width(4.dp)) } - Icon( - imageVector = Icons.Default.Done, - contentDescription = null, - tint = checkmarkColor, - modifier = Modifier.size(14.dp) - ) - Icon( - imageVector = Icons.Default.Done, - contentDescription = null, - tint = checkmarkColor, - modifier = Modifier.size(14.dp).offset(x = (-6).dp) - ) - Spacer(modifier = Modifier.width(2.dp)) - } - 0 -> { - // WAITING - одна серая галочка (отправлено, ждём доставку) - Icon( - imageVector = Icons.Default.Done, - contentDescription = null, - tint = secondaryTextColor.copy(alpha = 0.6f), - modifier = Modifier.size(14.dp) - ) - Spacer(modifier = Modifier.width(4.dp)) } } }