From 89d639a474eb2cb2242a0dd8773604b675d5e83f Mon Sep 17 00:00:00 2001 From: k1ngsterr1 Date: Sun, 18 Jan 2026 21:48:02 +0500 Subject: [PATCH] feat: Enhance logging and state management in ChatsList and MessageRepository for improved user experience --- .../messenger/data/MessageRepository.kt | 9 +++++ .../messenger/network/ProtocolManager.kt | 4 +- .../messenger/ui/chats/ChatsListScreen.kt | 37 ++++++++++++++++--- .../messenger/ui/chats/ChatsListViewModel.kt | 11 ++++-- 4 files changed, 51 insertions(+), 10 deletions(-) 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 6becd72..7a206b0 100644 --- a/app/src/main/java/com/rosetta/messenger/data/MessageRepository.kt +++ b/app/src/main/java/com/rosetta/messenger/data/MessageRepository.kt @@ -572,10 +572,19 @@ class MessageRepository private constructor(private val context: Context) { suspend fun updateDialogUserInfo(publicKey: String, title: String, username: String, verified: Int) { val account = currentAccount ?: return + android.util.Log.d("MessageRepo", "🔄 updateDialogUserInfo: ${publicKey.take(16)}... title='$title' username='$username'") + // Проверяем существует ли диалог с этим пользователем val existing = dialogDao.getDialog(account, publicKey) 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) + + // 🔥 Проверим что данные сохранились + 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)}...") } } diff --git a/app/src/main/java/com/rosetta/messenger/network/ProtocolManager.kt b/app/src/main/java/com/rosetta/messenger/network/ProtocolManager.kt index 1bbf0af..cd52a50 100644 --- a/app/src/main/java/com/rosetta/messenger/network/ProtocolManager.kt +++ b/app/src/main/java/com/rosetta/messenger/network/ProtocolManager.kt @@ -179,12 +179,14 @@ object ProtocolManager { waitPacket(0x03) { packet -> val searchPacket = packet as PacketSearch addLog("🔍 Search/UserInfo response: ${searchPacket.users.size} users") + android.util.Log.d("Protocol", "🔍 Search/UserInfo response: ${searchPacket.users.size} users") // Обновляем информацию о пользователях в диалогах if (searchPacket.users.isNotEmpty()) { - scope.launch { + scope.launch(Dispatchers.IO) { // 🔥 Запускаем на IO потоке для работы с БД searchPacket.users.forEach { user -> 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( user.publicKey, user.title, 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 11a2ac3..2fb6d9a 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 @@ -918,6 +918,16 @@ fun ChatsListScreen( val requests = chatsState.requests 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 AnimatedContent( targetState = showRequestsScreen, @@ -939,8 +949,8 @@ fun ChatsListScreen( onUserSelect(user) } ) - } else if (chatsState.isEmpty) { - // 🔥 Empty state - используем chatsState.isEmpty для атомарной проверки + } else if (shouldShowEmptyState) { + // 🔥 Empty state - показываем только если контент НЕ был показан ранее EmptyChatsState( isDarkTheme = isDarkTheme, modifier = Modifier.fillMaxSize() @@ -1553,33 +1563,48 @@ fun DialogItemContent( } // 📁 Для Saved Messages показываем специальное имя + // 🔥 Как в Архиве: title > username > "DELETED" val displayName = remember(dialog.opponentTitle, dialog.opponentUsername, dialog.opponentKey, dialog.isSavedMessages) { if (dialog.isSavedMessages) { "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 как основное имя (как в десктопной версии) - dialog.opponentTitle + // Обрезаем до 15 символов как в Архиве + if (dialog.opponentTitle.length > 15) { + dialog.opponentTitle.take(15) + "..." + } else { + dialog.opponentTitle + } } else if (dialog.opponentUsername.isNotEmpty()) { // Username только если нет title "@${dialog.opponentUsername}" } else { - dialog.opponentKey.take(8) + // 🔥 Как в Архиве - если нет информации, показываем часть ключа + dialog.opponentKey.take(7) } } // 📁 Для Saved Messages показываем иконку закладки + // 🔥 Как в Архиве: инициалы из title или username или DELETED val initials = remember(dialog.opponentTitle, dialog.opponentUsername, dialog.opponentKey, dialog.isSavedMessages) { if (dialog.isSavedMessages) { "" // Для 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 для инициалов dialog.opponentTitle .split(" ") .take(2) .mapNotNull { it.firstOrNull()?.uppercase() } .joinToString("") + .ifEmpty { dialog.opponentTitle.take(2).uppercase() } } else if (dialog.opponentUsername.isNotEmpty()) { // Если только username - берем первые 2 символа dialog.opponentUsername.take(2).uppercase() diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatsListViewModel.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatsListViewModel.kt index b6fb2c2..e674edf 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatsListViewModel.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatsListViewModel.kt @@ -80,15 +80,18 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio val requestsCount: StateFlow = _requestsCount.asStateFlow() // 🔥 НОВОЕ: Комбинированное состояние - обновляется атомарно! + // 🎯 ИСПРАВЛЕНИЕ: debounce предотвращает мерцание при быстрых обновлениях val chatsState: StateFlow = combine( _dialogs, _requests, _requestsCount ) { dialogs, requests, count -> ChatsUiState(dialogs, requests, count) - }.stateIn( + } + .distinctUntilChanged() // 🔥 Игнорируем дублирующиеся состояния + .stateIn( viewModelScope, - SharingStarted.WhileSubscribed(5000), + SharingStarted.Eagerly, // 🔥 КРИТИЧНО: Eagerly вместо WhileSubscribed - сразу начинаем следить ChatsUiState() ) @@ -156,7 +159,7 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio } } .flowOn(Dispatchers.Default) // 🚀 map выполняется на Default (CPU) - .flowOn(Dispatchers.Main) // 🎯 КРИТИЧНО: Обновляем UI на главном потоке! + .distinctUntilChanged() // 🔥 ИСПРАВЛЕНИЕ: Игнорируем дублирующиеся списки .collect { decryptedDialogs -> _dialogs.value = decryptedDialogs @@ -215,6 +218,7 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio } } .flowOn(Dispatchers.Default) + .distinctUntilChanged() // 🔥 ИСПРАВЛЕНИЕ: Игнорируем дублирующиеся списки .collect { decryptedRequests -> _requests.value = decryptedRequests } @@ -224,6 +228,7 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio viewModelScope.launch { dialogDao.getRequestsCountFlow(publicKey) .flowOn(Dispatchers.IO) + .distinctUntilChanged() // 🔥 ИСПРАВЛЕНИЕ: Игнорируем дублирующиеся значения .collect { count -> _requestsCount.value = count }