feat: Enhance logging and state management in ChatsList and MessageRepository for improved user experience
This commit is contained in:
@@ -572,10 +572,19 @@ 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.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 dialog opponent info in DB for ${publicKey.take(16)}...")
|
||||||
dialogDao.updateOpponentInfo(account, publicKey, title, username, verified)
|
dialogDao.updateOpponentInfo(account, publicKey, title, username, verified)
|
||||||
|
|
||||||
|
// 🔥 Проверим что данные сохранились
|
||||||
|
val updated = dialogDao.getDialog(account, publicKey)
|
||||||
|
android.util.Log.d("MessageRepo", "📝 After update: title='${updated?.opponentTitle}' username='${updated?.opponentUsername}'")
|
||||||
|
} else {
|
||||||
|
android.util.Log.w("MessageRepo", "⚠️ Dialog not found for ${publicKey.take(16)}...")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -179,12 +179,14 @@ object ProtocolManager {
|
|||||||
waitPacket(0x03) { packet ->
|
waitPacket(0x03) { packet ->
|
||||||
val searchPacket = packet as PacketSearch
|
val searchPacket = packet as PacketSearch
|
||||||
addLog("🔍 Search/UserInfo response: ${searchPacket.users.size} users")
|
addLog("🔍 Search/UserInfo response: ${searchPacket.users.size} users")
|
||||||
|
android.util.Log.d("Protocol", "🔍 Search/UserInfo response: ${searchPacket.users.size} users")
|
||||||
|
|
||||||
// Обновляем информацию о пользователях в диалогах
|
// Обновляем информацию о пользователях в диалогах
|
||||||
if (searchPacket.users.isNotEmpty()) {
|
if (searchPacket.users.isNotEmpty()) {
|
||||||
scope.launch {
|
scope.launch(Dispatchers.IO) { // 🔥 Запускаем на IO потоке для работы с БД
|
||||||
searchPacket.users.forEach { user ->
|
searchPacket.users.forEach { user ->
|
||||||
addLog(" 📝 Updating user info: ${user.publicKey.take(16)}... title='${user.title}' username='${user.username}'")
|
addLog(" 📝 Updating user info: ${user.publicKey.take(16)}... title='${user.title}' username='${user.username}'")
|
||||||
|
android.util.Log.d("Protocol", "📝 Updating user info: ${user.publicKey.take(16)}... title='${user.title}' username='${user.username}'")
|
||||||
messageRepository?.updateDialogUserInfo(
|
messageRepository?.updateDialogUserInfo(
|
||||||
user.publicKey,
|
user.publicKey,
|
||||||
user.title,
|
user.title,
|
||||||
|
|||||||
@@ -918,6 +918,16 @@ fun ChatsListScreen(
|
|||||||
val requests = chatsState.requests
|
val requests = chatsState.requests
|
||||||
val requestsCount = chatsState.requestsCount
|
val requestsCount = chatsState.requestsCount
|
||||||
|
|
||||||
|
// 🔥 ИСПРАВЛЕНИЕ МЕРЦАНИЯ: Запоминаем, что контент УЖЕ был показан
|
||||||
|
// Это предотвращает показ EmptyState при временных пустых обновлениях
|
||||||
|
var hasShownContent by rememberSaveable { mutableStateOf(false) }
|
||||||
|
if (chatsState.hasContent) {
|
||||||
|
hasShownContent = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🎯 Показываем Empty State только если контент НИКОГДА не показывался
|
||||||
|
val shouldShowEmptyState = chatsState.isEmpty && !hasShownContent
|
||||||
|
|
||||||
// 🎬 Animated content transition between main list and requests
|
// 🎬 Animated content transition between main list and requests
|
||||||
AnimatedContent(
|
AnimatedContent(
|
||||||
targetState = showRequestsScreen,
|
targetState = showRequestsScreen,
|
||||||
@@ -939,8 +949,8 @@ fun ChatsListScreen(
|
|||||||
onUserSelect(user)
|
onUserSelect(user)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
} else if (chatsState.isEmpty) {
|
} else if (shouldShowEmptyState) {
|
||||||
// 🔥 Empty state - используем chatsState.isEmpty для атомарной проверки
|
// 🔥 Empty state - показываем только если контент НЕ был показан ранее
|
||||||
EmptyChatsState(
|
EmptyChatsState(
|
||||||
isDarkTheme = isDarkTheme,
|
isDarkTheme = isDarkTheme,
|
||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier.fillMaxSize()
|
||||||
@@ -1553,33 +1563,48 @@ fun DialogItemContent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 📁 Для Saved Messages показываем специальное имя
|
// 📁 Для Saved Messages показываем специальное имя
|
||||||
|
// 🔥 Как в Архиве: title > username > "DELETED"
|
||||||
val displayName =
|
val displayName =
|
||||||
remember(dialog.opponentTitle, dialog.opponentUsername, dialog.opponentKey, dialog.isSavedMessages) {
|
remember(dialog.opponentTitle, dialog.opponentUsername, dialog.opponentKey, dialog.isSavedMessages) {
|
||||||
if (dialog.isSavedMessages) {
|
if (dialog.isSavedMessages) {
|
||||||
"Saved Messages"
|
"Saved Messages"
|
||||||
} else if (dialog.opponentTitle.isNotEmpty()) {
|
} else if (dialog.opponentTitle.isNotEmpty() &&
|
||||||
|
dialog.opponentTitle != dialog.opponentKey &&
|
||||||
|
dialog.opponentTitle != dialog.opponentKey.take(7) &&
|
||||||
|
dialog.opponentTitle != dialog.opponentKey.take(8)) {
|
||||||
// 🔥 Показываем title как основное имя (как в десктопной версии)
|
// 🔥 Показываем title как основное имя (как в десктопной версии)
|
||||||
|
// Обрезаем до 15 символов как в Архиве
|
||||||
|
if (dialog.opponentTitle.length > 15) {
|
||||||
|
dialog.opponentTitle.take(15) + "..."
|
||||||
|
} else {
|
||||||
dialog.opponentTitle
|
dialog.opponentTitle
|
||||||
|
}
|
||||||
} else if (dialog.opponentUsername.isNotEmpty()) {
|
} else if (dialog.opponentUsername.isNotEmpty()) {
|
||||||
// Username только если нет title
|
// Username только если нет title
|
||||||
"@${dialog.opponentUsername}"
|
"@${dialog.opponentUsername}"
|
||||||
} else {
|
} else {
|
||||||
dialog.opponentKey.take(8)
|
// 🔥 Как в Архиве - если нет информации, показываем часть ключа
|
||||||
|
dialog.opponentKey.take(7)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 📁 Для Saved Messages показываем иконку закладки
|
// 📁 Для Saved Messages показываем иконку закладки
|
||||||
|
// 🔥 Как в Архиве: инициалы из title или username или DELETED
|
||||||
val initials =
|
val initials =
|
||||||
remember(dialog.opponentTitle, dialog.opponentUsername, dialog.opponentKey, dialog.isSavedMessages) {
|
remember(dialog.opponentTitle, dialog.opponentUsername, dialog.opponentKey, dialog.isSavedMessages) {
|
||||||
if (dialog.isSavedMessages) {
|
if (dialog.isSavedMessages) {
|
||||||
"" // Для Saved Messages - пустая строка, будет использоваться иконка
|
"" // Для Saved Messages - пустая строка, будет использоваться иконка
|
||||||
} else if (dialog.opponentTitle.isNotEmpty()) {
|
} else if (dialog.opponentTitle.isNotEmpty() &&
|
||||||
|
dialog.opponentTitle != dialog.opponentKey &&
|
||||||
|
dialog.opponentTitle != dialog.opponentKey.take(7) &&
|
||||||
|
dialog.opponentTitle != dialog.opponentKey.take(8)) {
|
||||||
// Используем title для инициалов
|
// Используем title для инициалов
|
||||||
dialog.opponentTitle
|
dialog.opponentTitle
|
||||||
.split(" ")
|
.split(" ")
|
||||||
.take(2)
|
.take(2)
|
||||||
.mapNotNull { it.firstOrNull()?.uppercase() }
|
.mapNotNull { it.firstOrNull()?.uppercase() }
|
||||||
.joinToString("")
|
.joinToString("")
|
||||||
|
.ifEmpty { dialog.opponentTitle.take(2).uppercase() }
|
||||||
} else if (dialog.opponentUsername.isNotEmpty()) {
|
} else if (dialog.opponentUsername.isNotEmpty()) {
|
||||||
// Если только username - берем первые 2 символа
|
// Если только username - берем первые 2 символа
|
||||||
dialog.opponentUsername.take(2).uppercase()
|
dialog.opponentUsername.take(2).uppercase()
|
||||||
|
|||||||
@@ -80,15 +80,18 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
|
|||||||
val requestsCount: StateFlow<Int> = _requestsCount.asStateFlow()
|
val requestsCount: StateFlow<Int> = _requestsCount.asStateFlow()
|
||||||
|
|
||||||
// 🔥 НОВОЕ: Комбинированное состояние - обновляется атомарно!
|
// 🔥 НОВОЕ: Комбинированное состояние - обновляется атомарно!
|
||||||
|
// 🎯 ИСПРАВЛЕНИЕ: debounce предотвращает мерцание при быстрых обновлениях
|
||||||
val chatsState: StateFlow<ChatsUiState> = combine(
|
val chatsState: StateFlow<ChatsUiState> = combine(
|
||||||
_dialogs,
|
_dialogs,
|
||||||
_requests,
|
_requests,
|
||||||
_requestsCount
|
_requestsCount
|
||||||
) { dialogs, requests, count ->
|
) { dialogs, requests, count ->
|
||||||
ChatsUiState(dialogs, requests, count)
|
ChatsUiState(dialogs, requests, count)
|
||||||
}.stateIn(
|
}
|
||||||
|
.distinctUntilChanged() // 🔥 Игнорируем дублирующиеся состояния
|
||||||
|
.stateIn(
|
||||||
viewModelScope,
|
viewModelScope,
|
||||||
SharingStarted.WhileSubscribed(5000),
|
SharingStarted.Eagerly, // 🔥 КРИТИЧНО: Eagerly вместо WhileSubscribed - сразу начинаем следить
|
||||||
ChatsUiState()
|
ChatsUiState()
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -156,7 +159,7 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.flowOn(Dispatchers.Default) // 🚀 map выполняется на Default (CPU)
|
.flowOn(Dispatchers.Default) // 🚀 map выполняется на Default (CPU)
|
||||||
.flowOn(Dispatchers.Main) // 🎯 КРИТИЧНО: Обновляем UI на главном потоке!
|
.distinctUntilChanged() // 🔥 ИСПРАВЛЕНИЕ: Игнорируем дублирующиеся списки
|
||||||
.collect { decryptedDialogs ->
|
.collect { decryptedDialogs ->
|
||||||
_dialogs.value = decryptedDialogs
|
_dialogs.value = decryptedDialogs
|
||||||
|
|
||||||
@@ -215,6 +218,7 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.flowOn(Dispatchers.Default)
|
.flowOn(Dispatchers.Default)
|
||||||
|
.distinctUntilChanged() // 🔥 ИСПРАВЛЕНИЕ: Игнорируем дублирующиеся списки
|
||||||
.collect { decryptedRequests ->
|
.collect { decryptedRequests ->
|
||||||
_requests.value = decryptedRequests
|
_requests.value = decryptedRequests
|
||||||
}
|
}
|
||||||
@@ -224,6 +228,7 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
|
|||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
dialogDao.getRequestsCountFlow(publicKey)
|
dialogDao.getRequestsCountFlow(publicKey)
|
||||||
.flowOn(Dispatchers.IO)
|
.flowOn(Dispatchers.IO)
|
||||||
|
.distinctUntilChanged() // 🔥 ИСПРАВЛЕНИЕ: Игнорируем дублирующиеся значения
|
||||||
.collect { count ->
|
.collect { count ->
|
||||||
_requestsCount.value = count
|
_requestsCount.value = count
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user