Fix performance issue by caching typing effect settings in ChatUiState (#329)

* Fix performance issue by caching typing effect settings in ChatUiState

- Moved `AuthManager.isTypingEffectEnabled()` and `AuthManager.getTypingEffectDelayMs()` out of `ChatScreen.kt` recomposition loops.
- Stored `typingEffectEnabled` and `typingEffectDelayMs` in `ChatUiState`.
- Added `refreshSettings()` to `ChatViewModel` to fetch settings upon initialization and on returning to the chat screen (ON_START).
- Prevents expensive EncryptedSharedPreferences XML decryption occurring on every message per token stream update.

Fixes #269

* Fix performance issue by caching typing effect settings in ChatUiState

- Moved `AuthManager.isTypingEffectEnabled()` and `AuthManager.getTypingEffectDelayMs()` out of `ChatScreen.kt` recomposition loops.
- Stored `typingEffectEnabled` and `typingEffectDelayMs` in `ChatUiState`.
- Added `refreshSettings()` to `ChatViewModel` to fetch settings upon initialization and on returning to the chat screen (ON_START).
- Prevents expensive EncryptedSharedPreferences XML decryption occurring on every message per token stream update.
- Fixed `ChatViewModelTest` missing mocks for `AuthManager.isTypingEffectEnabled()` and `AuthManager.getTypingEffectDelayMs()`.
- Fixed ktlint unused import and max line length violations.

Fixes #269
This commit is contained in:
M57 authored and GitHub committed 2026-06-23 12:06:06 +03:00
1 parent b2dc5f2cd6
commit 586f463c9c
3 files changed
+24 -10

No files matched your search

@@ -104,7 +104,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import com.m57.hermescontrol.NavigationController
import com.m57.hermescontrol.R
import com.m57.hermescontrol.data.local.AuthManager
import com.m57.hermescontrol.data.ws.ConnectionStatus
import com.m57.hermescontrol.notification.NotificationHelper
import com.m57.hermescontrol.theme.StatusRed
@@ -174,6 +173,7 @@ fun ChatScreen(
androidx.lifecycle.Lifecycle.Event.ON_START -> {
NotificationHelper.setAppForeground(context, true)
NotificationHelper.stop(context)
viewModel.refreshSettings()
viewModel.refreshCurrentSession()
}
@@ -441,13 +441,15 @@ fun ChatScreen(
val isLastMessage = index == state.messages.lastIndex
val isAssistant = message.role == MessageRole.ASSISTANT
val typingEnabled = AuthManager.isTypingEffectEnabled()
val typingDelayMs = AuthManager.getTypingEffectDelayMs()
if (typingEnabled && isLastMessage && isAssistant && lastAnimatedMessageId != message.id) {
if (state.typingEffectEnabled &&
isLastMessage &&
isAssistant &&
lastAnimatedMessageId != message.id
) {
StreamingBubbleWithTypingEffect(
streaming = message,
typingDelayMs = typingDelayMs,
typingDelayMs = state.typingEffectDelayMs,
isDark = isDark,
onAnimationComplete = {
lastAnimatedMessageId = message.id
@@ -466,13 +468,10 @@ fun ChatScreen(
// Streaming message — rendered separately for O(1) updates
state.streamingMessage?.let { streaming ->
item(key = "streaming-${streaming.id}") {
val typingEnabled = AuthManager.isTypingEffectEnabled()
val typingDelayMs = AuthManager.getTypingEffectDelayMs()
if (typingEnabled && streaming.isStreaming) {
if (state.typingEffectEnabled && streaming.isStreaming) {
StreamingBubbleWithTypingEffect(
streaming = streaming,
typingDelayMs = typingDelayMs,
typingDelayMs = state.typingEffectDelayMs,
isDark = isDark,
)
} else {
@@ -57,6 +57,9 @@ data class ChatUiState(
val searchQuery: String = "",
val searchMatchIndices: List<Int> = emptyList(),
val currentSearchMatchIndex: Int = -1,
// Cached settings
val typingEffectEnabled: Boolean = true,
val typingEffectDelayMs: Int = 30,
) {
/** Convenience — derived from [connectionStatus]. */
val isConnected: Boolean get() = connectionStatus == ConnectionStatus.CONNECTED
@@ -118,6 +121,7 @@ class ChatViewModel(
)
init {
refreshSettings()
connectWebSocket()
viewModelScope.launch {
wsClient.events.collect { event ->
@@ -860,6 +864,15 @@ class ChatViewModel(
loadSessionMessages(sessionId)
}
fun refreshSettings() {
_uiState.update { state ->
state.copy(
typingEffectEnabled = AuthManager.isTypingEffectEnabled(),
typingEffectDelayMs = AuthManager.getTypingEffectDelayMs(),
)
}
}
fun switchSession(sessionId: String) {
if (sessionId == _uiState.value.currentSessionId) return