feat(#13): add pull-to-refresh to HermesScaffold (#14)

- HermesScaffold: added isRefreshing param, wraps content in PullToRefreshBox when onRefresh is set
- All 16 data screens: pass isRefreshing = state.isLoading
- Initial loading only shows full-screen LoadingState when data is empty (no spinner over existing content)
- AchievementsScreen already had the correct pattern, just needed isRefreshing param

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
This commit is contained in:
M57andM57 authored and GitHub committed 2026-06-18 18:58:08 +03:00
1 parent d37de1d223
commit 4892572bcc
18 files changed
+56 -23

No files matched your search

@@ -47,6 +47,7 @@ fun AchievementsScreen(
HermesScaffold(
title = { Text("Agent Achievements") },
onOpenDrawer = onOpenDrawer,
isRefreshing = state.isLoading,
onRefresh = { viewModel.loadAchievements() },
) { paddingValues ->
when {
@@ -61,10 +61,11 @@ fun ChannelsScreen(
HermesScaffold(
title = { Text("Messaging Channels") },
onOpenDrawer = onOpenDrawer,
isRefreshing = state.isLoading,
onRefresh = { viewModel.loadPlatforms() },
) { paddingValues ->
when {
state.isLoading -> {
state.isLoading && state.platforms.isEmpty() -> {
LoadingState(modifier = Modifier.padding(paddingValues))
}
state.errorMessage != null -> {
@@ -15,6 +15,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
@@ -38,6 +39,7 @@ fun HermesScaffold(
title: @Composable () -> Unit,
onOpenDrawer: (() -> Unit)? = null,
onRefresh: (() -> Unit)? = null,
isRefreshing: Boolean = false,
onBack: (() -> Unit)? = null,
showBack: Boolean = false,
snackbarHost: @Composable () -> Unit = {},
@@ -95,13 +97,27 @@ fun HermesScaffold(
},
containerColor = MaterialTheme.colorScheme.background,
) { paddingValues ->
Box(
modifier =
Modifier
.fillMaxSize()
.padding(paddingValues),
) {
content(paddingValues)
val refreshContent: @Composable () -> Unit = {
Box(
modifier =
Modifier
.fillMaxSize()
.padding(paddingValues),
) {
content(paddingValues)
}
}
if (onRefresh != null) {
PullToRefreshBox(
isRefreshing = isRefreshing,
onRefresh = onRefresh,
modifier = Modifier.fillMaxSize(),
) {
refreshContent()
}
} else {
refreshContent()
}
}
}
@@ -59,10 +59,11 @@ fun ConfigScreen(
HermesScaffold(
title = { Text("Configuration") },
onOpenDrawer = onOpenDrawer,
isRefreshing = state.isLoading,
onRefresh = { viewModel.loadRawConfig() },
) { paddingValues ->
when {
state.isLoading -> {
state.isLoading && state.yamlText == null -> {
LoadingState(modifier = Modifier.padding(paddingValues))
}
state.errorMessage != null -> {
@@ -61,10 +61,11 @@ fun CronJobsScreen(
HermesScaffold(
title = { Text("Cron Jobs") },
onOpenDrawer = onOpenDrawer,
isRefreshing = state.isLoading,
onRefresh = { viewModel.loadCronJobs() },
) {
when {
state.isLoading -> LoadingState()
state.isLoading && state.jobs.isEmpty() -> LoadingState()
state.errorMessage != null ->
ErrorState(
message = state.errorMessage ?: "Unknown error",
@@ -64,10 +64,11 @@ fun GatewayScreen(
HermesScaffold(
title = { Text("Gateway Control") },
onOpenDrawer = onOpenDrawer,
isRefreshing = state.isLoading,
onRefresh = { viewModel.loadStatus() },
) { paddingValues ->
when {
state.isLoading -> {
state.isLoading && state.status == null -> {
LoadingState(modifier = Modifier.padding(paddingValues))
}
state.errorMessage != null -> {
@@ -73,10 +73,11 @@ fun KanbanScreen(
HermesScaffold(
title = { Text("Kanban Board") },
onOpenDrawer = onOpenDrawer,
isRefreshing = state.isLoading,
onRefresh = { viewModel.loadBoards() },
) { paddingValues ->
when {
state.isLoading -> {
state.isLoading && state.boards.isEmpty() -> {
LoadingState(modifier = Modifier.padding(paddingValues))
}
state.errorMessage != null -> {
@@ -68,10 +68,11 @@ fun KeysScreen(
HermesScaffold(
title = { Text("Keys & Credentials") },
onOpenDrawer = onOpenDrawer,
isRefreshing = state.isLoading,
onRefresh = { viewModel.loadKeys() },
) { paddingValues ->
when {
state.isLoading -> {
state.isLoading && state.envVars.isEmpty() -> {
LoadingState(modifier = Modifier.padding(paddingValues))
}
state.errorMessage != null -> {
@@ -61,10 +61,11 @@ fun LogsScreen(
HermesScaffold(
title = { Text("Logs") },
onOpenDrawer = onOpenDrawer,
isRefreshing = state.isLoading,
onRefresh = { viewModel.loadLogs() },
) {
when {
state.isLoading -> LoadingState()
state.isLoading && state.logs.isEmpty() -> LoadingState()
state.errorMessage != null ->
ErrorState(
message = state.errorMessage ?: "Unknown error",
@@ -61,10 +61,11 @@ fun McpServersScreen(
HermesScaffold(
title = { Text("MCP Servers") },
onOpenDrawer = onOpenDrawer,
isRefreshing = state.isLoading,
onRefresh = { viewModel.loadServers() },
) { paddingValues ->
when {
state.isLoading -> {
state.isLoading && state.servers.isEmpty() -> {
LoadingState(modifier = Modifier.padding(paddingValues))
}
state.errorMessage != null -> {
@@ -68,10 +68,11 @@ fun ModelScreen(
HermesScaffold(
title = { Text("Models") },
onOpenDrawer = onOpenDrawer,
isRefreshing = state.isLoading,
onRefresh = { viewModel.loadModelOptions() },
) { paddingValues ->
when {
state.isLoading -> {
state.isLoading && state.providers.isEmpty() -> {
LoadingState(modifier = Modifier.padding(paddingValues))
}
state.errorMessage != null -> {
@@ -60,10 +60,11 @@ fun PairingScreen(
HermesScaffold(
title = { Text("Device Pairing") },
onOpenDrawer = onOpenDrawer,
isRefreshing = state.isLoading,
onRefresh = { viewModel.loadPairing() },
) { paddingValues ->
when {
state.isLoading -> {
state.isLoading && state.pairing == null -> {
LoadingState(modifier = Modifier.padding(paddingValues))
}
state.errorMessage != null -> {
@@ -59,10 +59,11 @@ fun PluginsScreen(
HermesScaffold(
title = { Text("Plugins") },
onOpenDrawer = onOpenDrawer,
isRefreshing = state.isLoading,
onRefresh = { viewModel.loadPlugins() },
) { paddingValues ->
when {
state.isLoading -> {
state.isLoading && state.plugins.isEmpty() -> {
LoadingState(modifier = Modifier.padding(paddingValues))
}
state.errorMessage != null -> {
@@ -72,10 +72,11 @@ fun ProfilesScreen(
HermesScaffold(
title = { Text("Profiles") },
onOpenDrawer = onOpenDrawer,
isRefreshing = state.isLoading,
onRefresh = { viewModel.loadProfiles() },
) { paddingValues ->
when {
state.isLoading -> {
state.isLoading && state.profiles.isEmpty() -> {
LoadingState(modifier = Modifier.padding(paddingValues))
}
state.errorMessage != null -> {
@@ -74,10 +74,11 @@ fun SkillsScreen(
HermesScaffold(
title = { Text("Skills") },
onOpenDrawer = onOpenDrawer,
isRefreshing = state.isLoading,
onRefresh = { viewModel.loadSkills() },
) {
when {
state.isLoading -> LoadingState()
state.isLoading && state.skills.isEmpty() -> LoadingState()
state.errorMessage != null ->
ErrorState(
message = state.errorMessage ?: "Unknown error",
@@ -67,10 +67,11 @@ fun SystemScreen(
HermesScaffold(
title = { Text("System") },
onOpenDrawer = onOpenDrawer,
isRefreshing = state.isLoading,
onRefresh = { viewModel.loadSystemData() },
) {
when {
state.isLoading -> LoadingState()
state.isLoading && state.stats == null && state.doctorReport == null -> LoadingState()
state.errorMessage != null ->
ErrorState(
message = state.errorMessage ?: "Unknown error",
@@ -64,10 +64,11 @@ fun ToolsetsScreen(
HermesScaffold(
title = { Text("Toolsets") },
onOpenDrawer = onOpenDrawer,
isRefreshing = state.isLoading,
onRefresh = { viewModel.loadToolsets() },
) { paddingValues ->
when {
state.isLoading -> {
state.isLoading && state.toolsets.isEmpty() -> {
LoadingState(modifier = Modifier.padding(paddingValues))
}
state.errorMessage != null -> {
@@ -62,10 +62,11 @@ fun WebhooksScreen(
HermesScaffold(
title = { Text("Webhooks") },
onOpenDrawer = onOpenDrawer,
isRefreshing = state.isLoading,
onRefresh = { viewModel.loadWebhooks() },
) { paddingValues ->
when {
state.isLoading -> {
state.isLoading && state.baseUrl == null -> {
LoadingState(modifier = Modifier.padding(paddingValues))
}
state.errorMessage != null -> {