perf: optimize screen load times with progressive un-gating and SWR caching (#1192)
This commit is contained in:
55 files changed
+2494
-401
No files matched your search
@@ -46,6 +46,7 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.m57.hermescontrol.R
|
||||
import com.m57.hermescontrol.data.local.AuthManager
|
||||
import com.m57.hermescontrol.data.model.OAuthProvider
|
||||
import com.m57.hermescontrol.data.model.OAuthStartResponse
|
||||
import com.m57.hermescontrol.theme.LocalHermesStatusColors
|
||||
@@ -65,9 +66,13 @@ fun ProvidersScreen(
|
||||
viewModel: ProvidersViewModel = viewModel { ProvidersViewModel() },
|
||||
) {
|
||||
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
val dataScope by AuthManager.dataScopeFlow.collectAsStateWithLifecycle()
|
||||
val context = LocalContext.current
|
||||
|
||||
LaunchedEffect(Unit) { viewModel.load() }
|
||||
LaunchedEffect(dataScope) {
|
||||
viewModel.clearScopeOwnedState()
|
||||
viewModel.load()
|
||||
}
|
||||
|
||||
ToastEffect(toastMessage = state.toastMessage, onClearToast = viewModel::clearToast)
|
||||
|
||||
@@ -98,7 +103,7 @@ fun ProvidersScreen(
|
||||
title = { Text(stringResource(R.string.screen_providers)) },
|
||||
navigationIcon = onOpenDrawer?.let { NavIcon.Menu(it) },
|
||||
isRefreshing = state.isLoading,
|
||||
onRefresh = { viewModel.load() },
|
||||
onRefresh = { viewModel.load(forceRefresh = true) },
|
||||
) { paddingValues ->
|
||||
when {
|
||||
state.isLoading && state.providers.isEmpty() -> {
|
||||
|
||||
@@ -2,8 +2,10 @@ package com.m57.hermescontrol.ui.providers
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.m57.hermescontrol.data.local.SwrCache
|
||||
import com.m57.hermescontrol.data.model.OAuthPollResponse
|
||||
import com.m57.hermescontrol.data.model.OAuthProvider
|
||||
import com.m57.hermescontrol.data.model.OAuthProvidersResponse
|
||||
import com.m57.hermescontrol.data.model.OAuthStartResponse
|
||||
import com.m57.hermescontrol.data.model.OAuthSubmitRequest
|
||||
import com.m57.hermescontrol.data.remote.ApiClient
|
||||
@@ -12,6 +14,7 @@ import com.m57.hermescontrol.data.remote.NetworkResult
|
||||
import com.m57.hermescontrol.data.remote.safeApiCall
|
||||
import com.m57.hermescontrol.ui.common.ToastHost
|
||||
import com.m57.hermescontrol.ui.common.safeLaunchLoad
|
||||
import com.m57.hermescontrol.ui.common.safeLaunchSwrLoad
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
@@ -66,9 +69,35 @@ class ProvidersViewModel :
|
||||
val uiState: StateFlow<ProvidersUiState> = _uiState.asStateFlow()
|
||||
|
||||
private var pollJob: Job? = null
|
||||
private val providersCache = SwrCache<String, OAuthProvidersResponse>()
|
||||
|
||||
fun load() {
|
||||
safeLaunchLoad(
|
||||
fun clearScopeOwnedState() {
|
||||
pollJob?.cancel()
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
isLoading = false,
|
||||
providers = emptyList(),
|
||||
errorMessage = null,
|
||||
flowPhase = OAuthFlowPhase.IDLE,
|
||||
flowProvider = null,
|
||||
flowErrorMessage = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun load(forceRefresh: Boolean = false) {
|
||||
safeLaunchSwrLoad(
|
||||
cache = providersCache,
|
||||
forceRefresh = forceRefresh,
|
||||
onCacheHit = { data ->
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
isLoading = false,
|
||||
providers = data.providers.orEmpty(),
|
||||
errorMessage = null,
|
||||
)
|
||||
}
|
||||
},
|
||||
apiCall = { safeApiCall { ApiClient.hermesApi.getOAuthProviders() } },
|
||||
onStart = {
|
||||
_uiState.update { it.copy(isLoading = true, errorMessage = null) }
|
||||
@@ -105,7 +134,7 @@ class ProvidersViewModel :
|
||||
toastMessage = "Disconnected",
|
||||
)
|
||||
}
|
||||
load()
|
||||
load(forceRefresh = true)
|
||||
},
|
||||
onError = { error ->
|
||||
_uiState.update {
|
||||
@@ -301,7 +330,7 @@ class ProvidersViewModel :
|
||||
|
||||
private fun finishFlow(status: String) {
|
||||
pollJob?.cancel()
|
||||
load()
|
||||
load(forceRefresh = true)
|
||||
// Stay on DONE so the dialog can show a success confirmation; the Screen's
|
||||
// Done button calls dismissFlow() to reset to IDLE. (Setting IDLE here would
|
||||
// conflate with DONE via StateFlow and unmount the dialog before the user sees it.)
|
||||
|
||||
Reference in new issue
Block a user