From 662d6244529f77ff0bbe3d82740b75f6f99cfd7a Mon Sep 17 00:00:00 2001 From: benya Date: Mon, 6 Apr 2026 00:07:13 +0300 Subject: [PATCH] fix: soften realtime connection status in chat subtitle - show Connecting immediately when opening realtime - delay Reconnecting subtitle updates to avoid noisy transient states - keep normal chat presence text during short reconnects --- .../messenger/ui/chat/ChatViewModel.kt | 73 ++++++++++++++++--- 1 file changed, 62 insertions(+), 11 deletions(-) diff --git a/android/app/src/main/java/ru/daemonlord/messenger/ui/chat/ChatViewModel.kt b/android/app/src/main/java/ru/daemonlord/messenger/ui/chat/ChatViewModel.kt index c645e1f..e1a5280 100644 --- a/android/app/src/main/java/ru/daemonlord/messenger/ui/chat/ChatViewModel.kt +++ b/android/app/src/main/java/ru/daemonlord/messenger/ui/chat/ChatViewModel.kt @@ -93,6 +93,8 @@ class ChatViewModel @Inject constructor( private var membersLoadKey: String? = null private var privateChatRelationKey: Long? = null private var typingResetJob: Job? = null + private var reconnectSubtitleJob: Job? = null + private var latestRealtimeConnectionState: RealtimeConnectionState = RealtimeConnectionState.Disconnected init { activeChatTracker.setActiveChat(chatId) @@ -918,17 +920,63 @@ class ChatViewModel @Inject constructor( private fun observeRealtimeState() { viewModelScope.launch { realtimeManager.connectionState.collectLatest { connectionState -> - _uiState.update { - val isConnecting = connectionState == RealtimeConnectionState.Connecting || - connectionState == RealtimeConnectionState.Reconnecting - it.copy( - isRealtimeConnecting = isConnecting, - chatSubtitle = resolveDisplayedSubtitle( - baseSubtitle = it.baseChatSubtitle, - isRealtimeConnecting = isConnecting, - isTyping = it.isTyping, - ), - ) + latestRealtimeConnectionState = connectionState + reconnectSubtitleJob?.cancel() + when (connectionState) { + RealtimeConnectionState.Connecting -> { + _uiState.update { + it.copy( + isRealtimeConnecting = true, + chatSubtitle = resolveDisplayedSubtitle( + baseSubtitle = it.baseChatSubtitle, + isRealtimeConnecting = true, + isTyping = it.isTyping, + ), + ) + } + } + + RealtimeConnectionState.Reconnecting -> { + _uiState.update { + it.copy( + isRealtimeConnecting = false, + chatSubtitle = resolveDisplayedSubtitle( + baseSubtitle = it.baseChatSubtitle, + isRealtimeConnecting = false, + isTyping = it.isTyping, + ), + ) + } + reconnectSubtitleJob = viewModelScope.launch { + delay(RECONNECT_SUBTITLE_DELAY_MS) + if (latestRealtimeConnectionState != RealtimeConnectionState.Reconnecting) return@launch + _uiState.update { + it.copy( + isRealtimeConnecting = true, + chatSubtitle = resolveDisplayedSubtitle( + baseSubtitle = it.baseChatSubtitle, + isRealtimeConnecting = true, + isTyping = it.isTyping, + ), + ) + } + } + } + + RealtimeConnectionState.Connected, + RealtimeConnectionState.Disconnected, + -> { + _uiState.update { + it.copy( + isRealtimeConnecting = false, + chatSubtitle = resolveDisplayedSubtitle( + baseSubtitle = it.baseChatSubtitle, + isRealtimeConnecting = false, + isTyping = it.isTyping, + ), + ) + } + } } } } @@ -1232,6 +1280,8 @@ class ChatViewModel @Inject constructor( } override fun onCleared() { + typingResetJob?.cancel() + reconnectSubtitleJob?.cancel() activeChatTracker.clearActiveChat(chatId) super.onCleared() } @@ -1239,5 +1289,6 @@ class ChatViewModel @Inject constructor( private companion object { const val MESSAGES_PAGE_SIZE = 50 const val MIN_VOICE_DURATION_MS = 1_000L + const val RECONNECT_SUBTITLE_DELAY_MS = 2_500L } }