From 3798d0330fdfe37bda88d9194c9d66cbbca819f8 Mon Sep 17 00:00:00 2001 From: M57 <41645758+Hy4ri@users.noreply.github.com> Date: Tue, 23 Jun 2026 12:19:33 +0300 Subject: [PATCH] Fix main-thread decryption block by caching token in StateFlow (#322) Issue #278 reported that `AuthManager.getToken()` (which reads from `EncryptedSharedPreferences`) was being called synchronously inside the `MainNavigation` composable, causing UI thread blocks during navigation transitions (PERF-10). This commit resolves the issue by caching the token in a new `tokenFlow` (`MutableStateFlow`) within `AuthManager`. - The flow is initialised with the token during `AuthManager.init()`. - Updates to the token (`setToken`, `setSelectedProfileId`, `setProfileToken`) are synchronised with `tokenFlow`. - `MainNavigation` now correctly observes this state using `collectAsState()`, preventing disk-read blocking on recompositions. Impact: Navigation transitions are now fully non-blocking. No side effects are expected as state synchronisation maintains parity with persistent storage. Resolves #278 --- app/src/main/java/com/m57/hermescontrol/Navigation.kt | 3 ++- .../com/m57/hermescontrol/data/local/AuthManager.kt | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/m57/hermescontrol/Navigation.kt b/app/src/main/java/com/m57/hermescontrol/Navigation.kt index 0e1cde44..cc7a1022 100644 --- a/app/src/main/java/com/m57/hermescontrol/Navigation.kt +++ b/app/src/main/java/com/m57/hermescontrol/Navigation.kt @@ -326,7 +326,8 @@ private fun appEntryProvider( @Composable fun MainNavigation(sessionId: String? = null) { - val hasToken = !AuthManager.getToken().isNullOrBlank() + val token by AuthManager.tokenFlow.collectAsState() + val hasToken = !token.isNullOrBlank() val startScreen: NavKey = if (hasToken) ChatScreen else ConnectScreen val backStack = rememberNavBackStack(startScreen) diff --git a/app/src/main/java/com/m57/hermescontrol/data/local/AuthManager.kt b/app/src/main/java/com/m57/hermescontrol/data/local/AuthManager.kt index da5bc8fc..eccf6a65 100644 --- a/app/src/main/java/com/m57/hermescontrol/data/local/AuthManager.kt +++ b/app/src/main/java/com/m57/hermescontrol/data/local/AuthManager.kt @@ -57,6 +57,10 @@ object AuthManager { private val _bottomNavDisplayModeFlow = MutableStateFlow(BottomNavDisplayMode.ICON_AND_TEXT) val bottomNavDisplayModeFlow: StateFlow = _bottomNavDisplayModeFlow.asStateFlow() + + private val _tokenFlow = MutableStateFlow(null) + val tokenFlow: StateFlow = _tokenFlow.asStateFlow() + private val gson = com.google.gson.Gson() /** @@ -84,6 +88,7 @@ object AuthManager { _useDynamicColorsFlow.value = isUseDynamicColors() _themePresetFlow.value = getThemePreset() _bottomNavDisplayModeFlow.value = getBottomNavDisplayMode() + _tokenFlow.value = getToken() } } @@ -122,6 +127,9 @@ object AuthManager { token: String?, ) { requirePrefs().edit().putString("token_$profileId", token).apply() + if (getSelectedProfileId() == profileId) { + _tokenFlow.value = token + } } fun getSelectedProfileId(): String? { @@ -131,6 +139,7 @@ object AuthManager { fun setSelectedProfileId(id: String?) { requirePrefs().edit().putString(KEY_SELECTED_PROFILE_ID, id).apply() + _tokenFlow.value = getToken() } // ── Token ──────────────────────────────────────────────────────────── @@ -150,6 +159,7 @@ object AuthManager { setProfileToken(selectedId, token) } else { requirePrefs().edit().putString(KEY_TOKEN, token).apply() + _tokenFlow.value = token } }