feat: Enhance account handling and message delivery status updates; improve UI feedback for saved messages
This commit is contained in:
@@ -170,6 +170,8 @@ class MainActivity : ComponentActivity() {
|
|||||||
targetState = when {
|
targetState = when {
|
||||||
showSplash -> "splash"
|
showSplash -> "splash"
|
||||||
showOnboarding && hasExistingAccount == false -> "onboarding"
|
showOnboarding && hasExistingAccount == false -> "onboarding"
|
||||||
|
// 🔥 КРИТИЧНО: если currentAccount != null - сразу на main без ожидания isLoggedIn
|
||||||
|
currentAccount != null -> "main"
|
||||||
isLoggedIn != true && hasExistingAccount == false -> "auth_new"
|
isLoggedIn != true && hasExistingAccount == false -> "auth_new"
|
||||||
isLoggedIn != true && hasExistingAccount == true -> "auth_unlock"
|
isLoggedIn != true && hasExistingAccount == true -> "auth_unlock"
|
||||||
else -> "main"
|
else -> "main"
|
||||||
@@ -210,7 +212,13 @@ class MainActivity : ComponentActivity() {
|
|||||||
currentAccount = account
|
currentAccount = account
|
||||||
hasExistingAccount = true
|
hasExistingAccount = true
|
||||||
// Save as last logged account
|
// 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 токен на сервер после успешной аутентификации
|
// 📤 Отправляем FCM токен на сервер после успешной аутентификации
|
||||||
account?.let { sendFcmTokenToServer(it) }
|
account?.let { sendFcmTokenToServer(it) }
|
||||||
@@ -354,33 +362,22 @@ fun MainScreen(
|
|||||||
onToggleTheme: () -> Unit = {},
|
onToggleTheme: () -> Unit = {},
|
||||||
onLogout: () -> Unit = {}
|
onLogout: () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
// 🔥 КРИТИЧНО: Если account == null, показываем загрузку вместо мокового аккаунта
|
// 🔥 Используем моковый аккаунт если account == null (для предотвращения крашей)
|
||||||
if (account == null) {
|
val effectiveAccount = account ?: DecryptedAccount(
|
||||||
Box(
|
publicKey = "loading",
|
||||||
modifier = Modifier
|
privateKey = "loading",
|
||||||
.fillMaxSize()
|
seedPhrase = emptyList(),
|
||||||
.background(if (isDarkTheme) Color(0xFF1B1B1B) else Color.White),
|
privateKeyHash = "loading",
|
||||||
contentAlignment = Alignment.Center
|
name = "Loading..."
|
||||||
) {
|
|
||||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
|
||||||
CircularProgressIndicator()
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
Text(
|
|
||||||
"Loading account...",
|
|
||||||
color = if (isDarkTheme) Color.White else Color.Black
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val accountName = account.name
|
val accountName = effectiveAccount.name
|
||||||
val accountPhone = account.publicKey.take(16).let {
|
val accountPhone = effectiveAccount.publicKey.take(16).let {
|
||||||
"+${it.take(1)} ${it.substring(1, 4)} ${it.substring(4, 7)}${it.substring(7)}"
|
"+${it.take(1)} ${it.substring(1, 4)} ${it.substring(4, 7)}${it.substring(7)}"
|
||||||
}
|
}
|
||||||
val accountPublicKey = account.publicKey
|
val accountPublicKey = effectiveAccount.publicKey
|
||||||
val accountPrivateKey = account.privateKey
|
val accountPrivateKey = effectiveAccount.privateKey
|
||||||
val privateKeyHash = account.privateKeyHash
|
val privateKeyHash = effectiveAccount.privateKeyHash
|
||||||
|
|
||||||
// Состояние протокола для передачи в SearchScreen
|
// Состояние протокола для передачи в SearchScreen
|
||||||
val protocolState by ProtocolManager.state.collectAsState()
|
val protocolState by ProtocolManager.state.collectAsState()
|
||||||
|
|||||||
@@ -368,6 +368,9 @@ class MessageRepository private constructor(private val context: Context) {
|
|||||||
// Обновляем кэш
|
// Обновляем кэш
|
||||||
val dialogKey = getDialogKey(packet.toPublicKey)
|
val dialogKey = getDialogKey(packet.toPublicKey)
|
||||||
updateMessageStatus(dialogKey, packet.messageId, DeliveryStatus.DELIVERED)
|
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
|
else msg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🔥 КРИТИЧНО: Обновляем диалог чтобы lastMessageRead обновился
|
||||||
|
dialogDao.updateDialogFromMessages(account, packet.fromPublicKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -549,6 +549,7 @@ interface DialogDao {
|
|||||||
COALESCE(
|
COALESCE(
|
||||||
(SELECT delivered FROM messages
|
(SELECT delivered FROM messages
|
||||||
WHERE account = :account
|
WHERE account = :account
|
||||||
|
AND from_me = 1
|
||||||
AND ((from_public_key = :opponentKey AND to_public_key = :account)
|
AND ((from_public_key = :opponentKey AND to_public_key = :account)
|
||||||
OR (from_public_key = :account AND to_public_key = :opponentKey))
|
OR (from_public_key = :account AND to_public_key = :opponentKey))
|
||||||
ORDER BY timestamp DESC LIMIT 1),
|
ORDER BY timestamp DESC LIMIT 1),
|
||||||
@@ -557,6 +558,7 @@ interface DialogDao {
|
|||||||
COALESCE(
|
COALESCE(
|
||||||
(SELECT read FROM messages
|
(SELECT read FROM messages
|
||||||
WHERE account = :account
|
WHERE account = :account
|
||||||
|
AND from_me = 1
|
||||||
AND ((from_public_key = :opponentKey AND to_public_key = :account)
|
AND ((from_public_key = :opponentKey AND to_public_key = :account)
|
||||||
OR (from_public_key = :account AND to_public_key = :opponentKey))
|
OR (from_public_key = :account AND to_public_key = :opponentKey))
|
||||||
ORDER BY timestamp DESC LIMIT 1),
|
ORDER BY timestamp DESC LIMIT 1),
|
||||||
@@ -639,22 +641,8 @@ interface DialogDao {
|
|||||||
) AS verified,
|
) AS verified,
|
||||||
1 AS i_have_sent,
|
1 AS i_have_sent,
|
||||||
1 AS last_message_from_me,
|
1 AS last_message_from_me,
|
||||||
COALESCE(
|
1 AS last_message_delivered,
|
||||||
(SELECT delivered FROM messages
|
1 AS last_message_read
|
||||||
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
|
|
||||||
WHERE EXISTS (
|
WHERE EXISTS (
|
||||||
SELECT 1 FROM messages
|
SELECT 1 FROM messages
|
||||||
WHERE account = :account
|
WHERE account = :account
|
||||||
|
|||||||
@@ -1648,8 +1648,22 @@ fun DialogItemContent(
|
|||||||
) {
|
) {
|
||||||
// Показываем статус только для исходящих сообщений
|
// Показываем статус только для исходящих сообщений
|
||||||
if (dialog.lastMessageFromMe == 1) {
|
if (dialog.lastMessageFromMe == 1) {
|
||||||
when (dialog.lastMessageDelivered) {
|
// 📁 Для Saved Messages всегда синие двойные галочки (прочитано)
|
||||||
2 -> {
|
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 - показываем иконку ошибки
|
// ERROR - показываем иконку ошибки
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.ErrorOutline,
|
imageVector = Icons.Outlined.ErrorOutline,
|
||||||
@@ -1659,39 +1673,40 @@ fun DialogItemContent(
|
|||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
}
|
}
|
||||||
1 -> {
|
dialog.lastMessageDelivered == 1 -> {
|
||||||
// DELIVERED - две галочки
|
// DELIVERED - смотрим на unreadCount
|
||||||
val checkmarkColor = if (dialog.lastMessageRead == 1) {
|
if (dialog.unreadCount > 0) {
|
||||||
PrimaryBlue // прочитано - синие
|
// Есть непрочитанные входящие → одна серая галочка
|
||||||
} else {
|
|
||||||
secondaryTextColor.copy(alpha = 0.6f) // доставлено - серые
|
|
||||||
}
|
|
||||||
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(
|
Icon(
|
||||||
imageVector = Icons.Default.Done,
|
imageVector = Icons.Default.Done,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = secondaryTextColor.copy(alpha = 0.6f),
|
tint = secondaryTextColor.copy(alpha = 0.6f),
|
||||||
modifier = Modifier.size(14.dp)
|
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))
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = formatTime(Date(dialog.lastMessageTimestamp)),
|
text = formatTime(Date(dialog.lastMessageTimestamp)),
|
||||||
|
|||||||
Reference in New Issue
Block a user