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
@@ -71,6 +71,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.McpCatalogEntry
|
||||
import com.m57.hermescontrol.data.model.McpServer
|
||||
import com.m57.hermescontrol.theme.LocalSpacing
|
||||
@@ -99,6 +100,7 @@ fun McpServersScreen(
|
||||
viewModel: McpServersViewModel = viewModel { McpServersViewModel() },
|
||||
) {
|
||||
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
val dataScope by AuthManager.dataScopeFlow.collectAsStateWithLifecycle()
|
||||
val spacing = LocalSpacing.current
|
||||
val context = LocalContext.current
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
@@ -127,14 +129,17 @@ fun McpServersScreen(
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) { viewModel.loadServers() }
|
||||
LaunchedEffect(dataScope) {
|
||||
viewModel.clearScopeOwnedState()
|
||||
viewModel.loadServers()
|
||||
}
|
||||
ToastEffect(toastMessage = state.toastMessage, onClearToast = viewModel::clearToast)
|
||||
|
||||
HermesScaffold(
|
||||
title = { Text(stringResource(R.string.screen_mcp_servers)) },
|
||||
navigationIcon = onOpenDrawer?.let { NavIcon.Menu(it) },
|
||||
isRefreshing = state.isLoading,
|
||||
onRefresh = { viewModel.loadServers() },
|
||||
onRefresh = { viewModel.loadServers(forceRefresh = true) },
|
||||
actions = {
|
||||
IconButton(
|
||||
onClick = { viewModel.testAllServers() },
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.m57.hermescontrol.ui.mcp
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.m57.hermescontrol.data.local.SwrCache
|
||||
import com.m57.hermescontrol.data.model.AddMcpServerRequest
|
||||
import com.m57.hermescontrol.data.model.McpCatalogEntry
|
||||
import com.m57.hermescontrol.data.model.McpCatalogInstallRequest
|
||||
@@ -9,11 +10,13 @@ import com.m57.hermescontrol.data.model.McpOAuthFlowResponse
|
||||
import com.m57.hermescontrol.data.model.McpServer
|
||||
import com.m57.hermescontrol.data.model.McpServerTestResponse
|
||||
import com.m57.hermescontrol.data.model.McpServerToggleRequest
|
||||
import com.m57.hermescontrol.data.model.McpServersResponse
|
||||
import com.m57.hermescontrol.data.remote.ApiClient
|
||||
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.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
@@ -69,10 +72,39 @@ class McpServersViewModel :
|
||||
private val _uiState = MutableStateFlow(McpServersUiState())
|
||||
val uiState: StateFlow<McpServersUiState> = _uiState.asStateFlow()
|
||||
|
||||
private val mcpCache = SwrCache<String, McpServersResponse>()
|
||||
|
||||
fun clearScopeOwnedState() {
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
isLoading = false,
|
||||
servers = emptyList(),
|
||||
serverTestResults = emptyMap(),
|
||||
testingServers = emptySet(),
|
||||
isTestingAll = false,
|
||||
errorMessage = null,
|
||||
activeOAuthFlow = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ── Data loading ──────────────────────────────────────────
|
||||
|
||||
fun loadServers() {
|
||||
safeLaunchLoad(
|
||||
fun loadServers(forceRefresh: Boolean = false) {
|
||||
safeLaunchSwrLoad(
|
||||
cache = mcpCache,
|
||||
forceRefresh = forceRefresh,
|
||||
onCacheHit = { data ->
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
isLoading = false,
|
||||
servers = data.servers.orEmpty(),
|
||||
serverTestResults = emptyMap(),
|
||||
testingServers = emptySet(),
|
||||
errorMessage = null,
|
||||
)
|
||||
}
|
||||
},
|
||||
apiCall = { safeApiCall { ApiClient.hermesApi.getMcpServers() } },
|
||||
onStart = { _uiState.update { it.copy(isLoading = true, errorMessage = null) } },
|
||||
onSuccess = { data ->
|
||||
@@ -247,7 +279,7 @@ class McpServersViewModel :
|
||||
when (result) {
|
||||
is NetworkResult.Success -> {
|
||||
_uiState.update { it.copy(toastMessage = "Server '$name' deleted") }
|
||||
loadServers()
|
||||
loadServers(forceRefresh = true)
|
||||
}
|
||||
|
||||
is NetworkResult.Failure -> {
|
||||
@@ -305,7 +337,9 @@ class McpServersViewModel :
|
||||
toastMessage = msg,
|
||||
)
|
||||
}
|
||||
loadServers()
|
||||
if (successCount > 0) {
|
||||
loadServers(forceRefresh = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -371,7 +405,7 @@ class McpServersViewModel :
|
||||
toastMessage = "Server '${request.name}' added",
|
||||
)
|
||||
}
|
||||
loadServers()
|
||||
loadServers(forceRefresh = true)
|
||||
}
|
||||
|
||||
is NetworkResult.Failure -> {
|
||||
@@ -428,7 +462,7 @@ class McpServersViewModel :
|
||||
toastMessage = "Env var '$key' added",
|
||||
)
|
||||
}
|
||||
loadServers()
|
||||
loadServers(forceRefresh = true)
|
||||
}
|
||||
|
||||
is NetworkResult.Failure -> {
|
||||
@@ -453,7 +487,7 @@ class McpServersViewModel :
|
||||
when (result) {
|
||||
is NetworkResult.Success -> {
|
||||
_uiState.update { it.copy(toastMessage = "Env var '$key' removed") }
|
||||
loadServers()
|
||||
loadServers(forceRefresh = true)
|
||||
}
|
||||
|
||||
is NetworkResult.Failure -> {
|
||||
@@ -520,7 +554,7 @@ class McpServersViewModel :
|
||||
toastMessage = "Catalog entry '${entry.name}' installed",
|
||||
)
|
||||
}
|
||||
loadServers()
|
||||
loadServers(forceRefresh = true)
|
||||
}
|
||||
|
||||
is NetworkResult.Failure -> {
|
||||
@@ -605,7 +639,7 @@ class McpServersViewModel :
|
||||
toastMessage = "OAuth authorization successful!",
|
||||
)
|
||||
}
|
||||
loadServers()
|
||||
loadServers(forceRefresh = true)
|
||||
}
|
||||
|
||||
"error" -> {
|
||||
|
||||
Reference in new issue
Block a user