fix: tab reloads actually reload — kanban switch spam, plugins/skills refresh, ws mint (#897)
- ws: ticket-mint response body was read on the calling (main) thread —
the socket read threw NetworkOnMainThreadException, the mint failed,
and the kanban events stream never connected (board stuck Offline).
execute() + body read now run entirely on Dispatchers.IO; same fix in
DashboardSessionTokenRefresher.fetch (loopback mint path).
- kanban: re-selecting the already-active board reloads data and
reconnects events WITHOUT the redundant POST /boards/{id}/switch that
refresh fired on every tap.
- plugins: top-bar Refresh + pull-to-refresh wired through HermesScaffold
(previously no reload control at all).
- skills: Refresh icon now does a plain GET reload; the heavy hub-update
POST moved to the cloud icon so refresh is no longer disguised as update.
- test: mock the 3-arg Log.w overload (catch now logs the full stack).
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
This commit is contained in:
1 parent
9c7bb8635f
commit
90c1e8dc5f
6 files changed
+131
-106
No files matched your search
@@ -32,7 +32,6 @@ import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -121,86 +120,82 @@ fun PluginsScreen(
|
||||
HermesScaffold(
|
||||
title = { Text(stringResource(R.string.screen_plugins)) },
|
||||
navigationIcon = onOpenDrawer?.let { NavIcon.Menu(it) },
|
||||
isRefreshing = state.isLoading,
|
||||
onRefresh = { viewModel.loadPlugins() },
|
||||
) { paddingValues ->
|
||||
PullToRefreshBox(
|
||||
isRefreshing = state.isLoading,
|
||||
onRefresh = { viewModel.loadPlugins() },
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
when {
|
||||
state.isLoading && state.plugins.isEmpty() -> {
|
||||
SkeletonListState(modifier = Modifier.padding(paddingValues))
|
||||
}
|
||||
when {
|
||||
state.isLoading && state.plugins.isEmpty() -> {
|
||||
SkeletonListState(modifier = Modifier.padding(paddingValues))
|
||||
}
|
||||
|
||||
state.errorMessage != null -> {
|
||||
ErrorState(
|
||||
message = state.errorMessage ?: "",
|
||||
onRetry = { viewModel.loadPlugins() },
|
||||
modifier = Modifier.padding(paddingValues),
|
||||
)
|
||||
}
|
||||
state.errorMessage != null -> {
|
||||
ErrorState(
|
||||
message = state.errorMessage ?: "",
|
||||
onRetry = { viewModel.loadPlugins() },
|
||||
modifier = Modifier.padding(paddingValues),
|
||||
)
|
||||
}
|
||||
|
||||
state.plugins.isEmpty() && state.orphanPlugins.isEmpty() -> {
|
||||
EmptyState(
|
||||
title = stringResource(R.string.plugins_empty_title),
|
||||
subtitle = stringResource(R.string.plugins_empty_desc),
|
||||
onAction = { viewModel.loadPlugins() },
|
||||
actionLabel = stringResource(R.string.content_desc_refresh),
|
||||
modifier = Modifier.padding(paddingValues),
|
||||
)
|
||||
}
|
||||
state.plugins.isEmpty() && state.orphanPlugins.isEmpty() -> {
|
||||
EmptyState(
|
||||
title = stringResource(R.string.plugins_empty_title),
|
||||
subtitle = stringResource(R.string.plugins_empty_desc),
|
||||
onAction = { viewModel.loadPlugins() },
|
||||
actionLabel = stringResource(R.string.content_desc_refresh),
|
||||
modifier = Modifier.padding(paddingValues),
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = listContentPadding,
|
||||
verticalArrangement = listItemSpacing,
|
||||
) {
|
||||
// Provider selection section
|
||||
if (state.memoryOptions.isNotEmpty() || state.contextOptions.isNotEmpty()) {
|
||||
item(key = "providers") {
|
||||
ProviderSelectionSection(state, viewModel)
|
||||
}
|
||||
else -> {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = listContentPadding,
|
||||
verticalArrangement = listItemSpacing,
|
||||
) {
|
||||
// Provider selection section
|
||||
if (state.memoryOptions.isNotEmpty() || state.contextOptions.isNotEmpty()) {
|
||||
item(key = "providers") {
|
||||
ProviderSelectionSection(state, viewModel)
|
||||
}
|
||||
}
|
||||
|
||||
// Install section
|
||||
item(key = "install") {
|
||||
InstallSection(state, viewModel)
|
||||
}
|
||||
// Install section
|
||||
item(key = "install") {
|
||||
InstallSection(state, viewModel)
|
||||
}
|
||||
|
||||
// Search bar
|
||||
item(key = "search") {
|
||||
SearchBar(
|
||||
query = query,
|
||||
onQueryChange = { query = it },
|
||||
placeholder = "Search plugins...",
|
||||
// Search bar
|
||||
item(key = "search") {
|
||||
SearchBar(
|
||||
query = query,
|
||||
onQueryChange = { query = it },
|
||||
placeholder = "Search plugins...",
|
||||
)
|
||||
}
|
||||
|
||||
// Plugin list
|
||||
items(filteredPlugins, key = { it.name }) { plugin ->
|
||||
PluginCard(
|
||||
plugin = plugin,
|
||||
state = state,
|
||||
viewModel = viewModel,
|
||||
onClick = { showDetail = plugin },
|
||||
)
|
||||
}
|
||||
|
||||
// Orphan dashboard plugins section
|
||||
if (state.orphanPlugins.isNotEmpty()) {
|
||||
item(key = "orphan-header") {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.plugins_orphan_heading),
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(vertical = 8.dp),
|
||||
)
|
||||
}
|
||||
|
||||
// Plugin list
|
||||
items(filteredPlugins, key = { it.name }) { plugin ->
|
||||
PluginCard(
|
||||
plugin = plugin,
|
||||
state = state,
|
||||
viewModel = viewModel,
|
||||
onClick = { showDetail = plugin },
|
||||
)
|
||||
}
|
||||
|
||||
// Orphan dashboard plugins section
|
||||
if (state.orphanPlugins.isNotEmpty()) {
|
||||
item(key = "orphan-header") {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.plugins_orphan_heading),
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(vertical = 8.dp),
|
||||
)
|
||||
}
|
||||
items(state.orphanPlugins, key = { "orphan-${it.name}" }) { plugin ->
|
||||
OrphanPluginCard(plugin = plugin, onClick = { showDetail = plugin })
|
||||
}
|
||||
items(state.orphanPlugins, key = { "orphan-${it.name}" }) { plugin ->
|
||||
OrphanPluginCard(plugin = plugin, onClick = { showDetail = plugin })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user