feat: Enhance logging and state management in ChatsList and MessageRepository for improved user experience

This commit is contained in:
k1ngsterr1
2026-01-18 21:48:02 +05:00
parent f141145245
commit 89d639a474
4 changed files with 51 additions and 10 deletions

View File

@@ -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)}...")
} }
} }

View File

@@ -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,

View File

@@ -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()

View File

@@ -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
} }