feat(#617): skeleton loaders instead of bare spinner on list screens (#625)

Adds SkeletonListState to StateViews.kt — an animated shimmer
placeholder list whose geometry matches a typical list row (avatar circle
+ title line + subtitle line). The shimmer colors derive from
MaterialTheme.colorScheme.surfaceVariant (theme-aware, works on
light/dark/AMOLED/Material You). Sweeps horizontally via an
infiniteTransition tween — no new dependencies, pure Compose foundation
animations.

Wired SkeletonListState into 22 list-loading call sites across 22 screens:
skills, cron, plugins, mcp, toolsets, webhooks, system, sessions (x2),
config (x2 — list + YAMLEditor), model, profiles, keys, channels,
achievements, gateways, kanban, logs, analytics, providers,
processes, hub skills tab, installed skills tab (list branch).

Preserved LoadingState() (centered spinner) where the issue explicitly
calls for it:
- PairingScreen (one-shot auth flow)
- SkillEditorDialog / ModelPickerDialog / CronJobEditorDialog interiors
- SkillPreviewDialog and SkillEditorDialog inline-loading branches in
  SkillsScreen.kt (lines 790, 914)
- InstalledSkillsTab dialog-interior branch (line 393)

SkeletonListState testTag: "skeleton_list_state".

Verified: ktlintCheck, assembleDebug, testDebugUnitTest all green locally.

Co-authored-by: Hy4ri <hy4ri@users.noreply.github.com>
This commit is contained in:
M57andHy4ri authored and GitHub committed 2026-07-18 15:12:21 +03:00
1 parent 0a0b612d3a
commit 0018e5d172
23 files changed
+186 -53

No files matched your search

@@ -57,10 +57,10 @@ import com.m57.hermescontrol.data.model.RecentUnlock
import com.m57.hermescontrol.ui.common.EmptyState
import com.m57.hermescontrol.ui.common.ErrorState
import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.LoadingState
import com.m57.hermescontrol.ui.common.NavIcon
import com.m57.hermescontrol.ui.common.SearchBar
import com.m57.hermescontrol.ui.common.SectionHeader
import com.m57.hermescontrol.ui.common.SkeletonListState
import com.m57.hermescontrol.ui.common.StatusBadge
import com.m57.hermescontrol.ui.common.StatusBadgeType
import com.m57.hermescontrol.ui.common.ToastEffect
@@ -169,7 +169,7 @@ fun AchievementsScreen(
) { paddingValues ->
when {
state.isLoading && state.achievements.isEmpty() -> {
LoadingState(modifier = Modifier.padding(paddingValues))
SkeletonListState(modifier = Modifier.padding(paddingValues))
}
state.errorMessage != null && state.achievements.isEmpty() -> {
@@ -46,8 +46,8 @@ import com.m57.hermescontrol.theme.LocalHermesStatusColors
import com.m57.hermescontrol.ui.common.EmptyState
import com.m57.hermescontrol.ui.common.ErrorState
import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.LoadingState
import com.m57.hermescontrol.ui.common.NavIcon
import com.m57.hermescontrol.ui.common.SkeletonListState
import com.m57.hermescontrol.ui.common.listContentPadding
import com.m57.hermescontrol.ui.common.listItemSpacing
@@ -74,7 +74,7 @@ fun AnalyticsScreen(
) {
when {
state.isLoading && state.usage == null -> {
LoadingState()
SkeletonListState()
}
state.errorMessage != null && state.usage == null -> {
@@ -50,9 +50,9 @@ import com.m57.hermescontrol.ui.channels.components.TelegramOnboardingDialog
import com.m57.hermescontrol.ui.common.EmptyState
import com.m57.hermescontrol.ui.common.ErrorState
import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.LoadingState
import com.m57.hermescontrol.ui.common.NavIcon
import com.m57.hermescontrol.ui.common.SearchBar
import com.m57.hermescontrol.ui.common.SkeletonListState
import com.m57.hermescontrol.ui.common.ToastEffect
import com.m57.hermescontrol.ui.common.listContentPadding
import com.m57.hermescontrol.ui.common.listItemSpacing
@@ -132,10 +132,7 @@ fun ChannelsScreen(
) { paddingValues ->
when {
state.isLoading && state.platforms.isEmpty() -> {
LoadingState(
subtitle = stringResource(R.string.loading_state_subtitle_channels),
modifier = Modifier.padding(paddingValues),
)
SkeletonListState(modifier = Modifier.padding(paddingValues))
}
state.errorMessage != null && state.platforms.isEmpty() -> {
@@ -1,14 +1,26 @@
package com.m57.hermescontrol.ui.common
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Inbox
import androidx.compose.material.icons.filled.Refresh
@@ -19,12 +31,20 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.m57.hermescontrol.R
import com.m57.hermescontrol.theme.LocalSpacing
@@ -168,6 +188,125 @@ fun EmptyState(
}
}
// ── Skeleton (shimmer) — see issue #617 ────────────────────────────────
//
// Skeleton loaders for list screens. Replaces bare `CircularProgressIndicator`
// in list screens to (1) reduce perceived latency (~2× per NN/g research)
// and (2) eliminate layout shift when real content lands. Pair with
// `LoadingState()` for centered/one-shot contexts (Pairing flow, dialog
// interiors) where a spinner is still appropriate.
/**
* An animated shimmer [Brush] derived from the current theme's
* `MaterialTheme.colorScheme.surfaceVariant`. Sweeps horizontally across the
* canvas; offset driven by an infinite `1.2s` tween so the shimmer never
* freezes. Theme-derived — works on light, dark, AMOLED, Material You.
*/
@Composable
private fun shimmerBrush(): Brush {
val base = MaterialTheme.colorScheme.surfaceVariant
val highlight = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.4f)
val transition = rememberInfiniteTransition(label = "shimmer")
val widthPx = with(LocalDensity.current) { LocalConfiguration.current.screenWidthDp.dp.toPx() }
val offset by transition.animateFloat(
initialValue = -widthPx,
targetValue = widthPx,
animationSpec =
infiniteRepeatable(
animation = tween(durationMillis = 1200, easing = FastOutSlowInEasing),
repeatMode = RepeatMode.Restart,
),
label = "shimmer_offset",
)
return Brush.linearGradient(
colors = listOf(base, highlight, base),
start = Offset(offset, 0f),
end = Offset(offset + widthPx, 0f),
)
}
@Composable
private fun ShimmerBox(
widthFraction: Float,
height: Dp,
shape: Shape,
modifier: Modifier = Modifier,
) {
Box(
modifier =
modifier
.height(height)
.fillMaxWidth(widthFraction)
.clip(shape)
.background(shimmerBrush()),
)
}
@Composable
private fun SkeletonRow(height: Dp) {
val spacing = LocalSpacing.current
val shape = RoundedCornerShape(12.dp)
Row(
modifier =
Modifier
.fillMaxWidth()
.height(height)
.padding(vertical = spacing.sm),
verticalAlignment = Alignment.CenterVertically,
) {
// Avatar circle — 40dp to match default LoadingState spinner size.
ShimmerBox(
widthFraction = 0f,
height = 40.dp,
shape = CircleShape,
modifier = Modifier.size(40.dp),
)
Spacer(modifier = Modifier.width(spacing.md))
// Title + subtitle lines
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(spacing.sm),
) {
ShimmerBox(widthFraction = 0.6f, height = 14.dp, shape = shape)
ShimmerBox(widthFraction = 0.4f, height = 12.dp, shape = shape)
}
}
}
/**
* Skeleton placeholder list for loading states on list/management screens.
*
* Renders [itemCount] shimmering rows whose geometry matches the typical list
* row (avatar circle + title line + subtitle line). The shimmer colors derive
* from theme tokens — works on light, dark, AMOLED, and Material You. No
* layout shift on transition to real content (acceptance criterion, issue #617).
*
* @param itemCount number of placeholder rows to render (default 6 — long
* enough to fill a phone viewport under typical content density).
* @param rowHeight height of each placeholder row (default 72dp — same as a
* one-line `NavigationDrawerItem`). Override per-screen to match real row
* geometry when known.
* @param modifier outer modifier; pass `Modifier.padding(paddingValues)` from
* the scaffold so the skeleton offsets for the top bar like real content does.
*/
@Composable
fun SkeletonListState(
itemCount: Int = 6,
rowHeight: Dp = 72.dp,
modifier: Modifier = Modifier,
) {
Column(
modifier =
modifier
.fillMaxSize()
.padding(horizontal = 12.dp)
.testTag("skeleton_list_state"),
verticalArrangement = listItemSpacing,
) {
repeat(itemCount) { SkeletonRow(height = rowHeight) }
}
}
// ── Padding conventions ──────────────────────────────────────────────
val listContentPadding = PaddingValues(horizontal = 12.dp, vertical = 8.dp)
@@ -58,9 +58,9 @@ import com.m57.hermescontrol.R
import com.m57.hermescontrol.data.model.SchemaField
import com.m57.hermescontrol.ui.common.ErrorState
import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.LoadingState
import com.m57.hermescontrol.ui.common.NavIcon
import com.m57.hermescontrol.ui.common.SearchBar
import com.m57.hermescontrol.ui.common.SkeletonListState
import com.m57.hermescontrol.ui.common.ToastEffect
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElement
@@ -101,7 +101,7 @@ fun ConfigScreen(
) { paddingValues ->
when {
state.isLoading && state.config == null -> {
LoadingState(modifier = Modifier.padding(paddingValues))
SkeletonListState(modifier = Modifier.padding(paddingValues))
}
state.errorMessage != null && state.config == null -> {
@@ -281,7 +281,7 @@ private fun YAMLEditor(
onSave: () -> Unit,
) {
if (isLoading) {
LoadingState()
SkeletonListState()
return
}
@@ -57,6 +57,7 @@ import com.m57.hermescontrol.ui.common.ErrorState
import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.LoadingState
import com.m57.hermescontrol.ui.common.NavIcon
import com.m57.hermescontrol.ui.common.SkeletonListState
import com.m57.hermescontrol.ui.common.StatusBadge
import com.m57.hermescontrol.ui.common.StatusBadgeType
import com.m57.hermescontrol.ui.common.ToastEffect
@@ -96,7 +97,7 @@ fun CronJobsScreen(
) {
when {
state.isLoading && state.jobs.isEmpty() -> {
LoadingState()
SkeletonListState()
}
state.errorMessage != null -> {
@@ -38,8 +38,8 @@ import com.m57.hermescontrol.R
import com.m57.hermescontrol.theme.LocalHermesStatusColors
import com.m57.hermescontrol.ui.common.ErrorState
import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.LoadingState
import com.m57.hermescontrol.ui.common.NavIcon
import com.m57.hermescontrol.ui.common.SkeletonListState
import com.m57.hermescontrol.ui.common.ToastEffect
@OptIn(ExperimentalMaterial3Api::class)
@@ -66,7 +66,7 @@ fun GatewayScreen(
) { paddingValues ->
when {
state.isLoading && state.status == null -> {
LoadingState(modifier = Modifier.padding(paddingValues))
SkeletonListState(modifier = Modifier.padding(paddingValues))
}
state.errorMessage != null -> {
@@ -46,9 +46,9 @@ import com.m57.hermescontrol.R
import com.m57.hermescontrol.data.model.KanbanTask
import com.m57.hermescontrol.ui.common.ErrorState
import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.LoadingState
import com.m57.hermescontrol.ui.common.NavIcon
import com.m57.hermescontrol.ui.common.SearchBar
import com.m57.hermescontrol.ui.common.SkeletonListState
import com.m57.hermescontrol.ui.common.ToastEffect
private const val DEFAULT_COLUMN = "todo"
@@ -94,7 +94,7 @@ fun KanbanScreen(
) { paddingValues ->
when {
state.isLoading && state.boards.isEmpty() -> {
LoadingState(modifier = Modifier.padding(paddingValues))
SkeletonListState(modifier = Modifier.padding(paddingValues))
}
state.errorMessage != null -> {
@@ -51,10 +51,10 @@ import com.m57.hermescontrol.R
import com.m57.hermescontrol.ui.common.EmptyState
import com.m57.hermescontrol.ui.common.ErrorState
import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.LoadingState
import com.m57.hermescontrol.ui.common.NavIcon
import com.m57.hermescontrol.ui.common.SearchBar
import com.m57.hermescontrol.ui.common.SectionHeader
import com.m57.hermescontrol.ui.common.SkeletonListState
import com.m57.hermescontrol.ui.common.ToastEffect
import com.m57.hermescontrol.ui.common.listContentPadding
import com.m57.hermescontrol.ui.common.listItemSpacing
@@ -106,7 +106,7 @@ fun KeysScreen(
) { paddingValues ->
when {
state.isLoading && !hasAnyVars -> {
LoadingState(modifier = Modifier.padding(paddingValues))
SkeletonListState(modifier = Modifier.padding(paddingValues))
}
state.errorMessage != null && !hasAnyVars -> {
@@ -54,9 +54,9 @@ import com.m57.hermescontrol.theme.LocalSpacing
import com.m57.hermescontrol.ui.common.EmptyState
import com.m57.hermescontrol.ui.common.ErrorState
import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.LoadingState
import com.m57.hermescontrol.ui.common.NavIcon
import com.m57.hermescontrol.ui.common.SearchBar
import com.m57.hermescontrol.ui.common.SkeletonListState
import com.m57.hermescontrol.ui.common.ToastEffect
import kotlinx.coroutines.launch
@@ -191,7 +191,7 @@ fun LogsScreen(
) {
when {
state.isLoading && state.logs.isEmpty() -> {
LoadingState()
SkeletonListState()
}
state.errorMessage != null -> {
@@ -65,10 +65,10 @@ import com.m57.hermescontrol.ui.common.DetailDialog
import com.m57.hermescontrol.ui.common.EmptyState
import com.m57.hermescontrol.ui.common.ErrorState
import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.LoadingState
import com.m57.hermescontrol.ui.common.NavIcon
import com.m57.hermescontrol.ui.common.SearchBar
import com.m57.hermescontrol.ui.common.SectionHeader
import com.m57.hermescontrol.ui.common.SkeletonListState
import com.m57.hermescontrol.ui.common.StatusBadge
import com.m57.hermescontrol.ui.common.StatusBadgeType
import com.m57.hermescontrol.ui.common.ToastEffect
@@ -120,7 +120,7 @@ fun McpServersScreen(
) { paddingValues ->
when {
state.isLoading && state.servers.isEmpty() -> {
LoadingState(modifier = Modifier.padding(paddingValues))
SkeletonListState(modifier = Modifier.padding(paddingValues))
}
state.errorMessage != null -> {
@@ -58,9 +58,9 @@ import com.m57.hermescontrol.R
import com.m57.hermescontrol.ui.common.EmptyState
import com.m57.hermescontrol.ui.common.ErrorState
import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.LoadingState
import com.m57.hermescontrol.ui.common.NavIcon
import com.m57.hermescontrol.ui.common.SearchBar
import com.m57.hermescontrol.ui.common.SkeletonListState
import com.m57.hermescontrol.ui.common.ToastEffect
import com.m57.hermescontrol.ui.model.components.MoaConfigDialog
import com.m57.hermescontrol.ui.model.components.ModelPickerDialog
@@ -101,10 +101,7 @@ fun ModelScreen(
) { paddingValues ->
when {
state.isLoading && state.providers.isEmpty() -> {
LoadingState(
subtitle = stringResource(R.string.loading_state_subtitle_models),
modifier = Modifier.padding(paddingValues),
)
SkeletonListState(modifier = Modifier.padding(paddingValues))
}
state.errorMessage != null -> {
@@ -57,9 +57,9 @@ import com.m57.hermescontrol.ui.common.DetailDialog
import com.m57.hermescontrol.ui.common.EmptyState
import com.m57.hermescontrol.ui.common.ErrorState
import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.LoadingState
import com.m57.hermescontrol.ui.common.NavIcon
import com.m57.hermescontrol.ui.common.SearchBar
import com.m57.hermescontrol.ui.common.SkeletonListState
import com.m57.hermescontrol.ui.common.ToastEffect
import com.m57.hermescontrol.ui.common.listContentPadding
import com.m57.hermescontrol.ui.common.listItemSpacing
@@ -131,7 +131,7 @@ fun PluginsScreen(
) {
when {
state.isLoading && state.plugins.isEmpty() -> {
LoadingState(modifier = Modifier.padding(paddingValues))
SkeletonListState(modifier = Modifier.padding(paddingValues))
}
state.errorMessage != null -> {
@@ -46,8 +46,8 @@ import com.m57.hermescontrol.theme.LocalHermesStatusColors
import com.m57.hermescontrol.ui.common.EmptyState
import com.m57.hermescontrol.ui.common.ErrorState
import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.LoadingState
import com.m57.hermescontrol.ui.common.NavIcon
import com.m57.hermescontrol.ui.common.SkeletonListState
import com.m57.hermescontrol.ui.common.ToastEffect
import com.m57.hermescontrol.ui.common.listContentPadding
import com.m57.hermescontrol.ui.common.listItemSpacing
@@ -85,7 +85,7 @@ fun ProcessesScreen(
}
state.isLoading && state.processes.isEmpty() -> {
LoadingState()
SkeletonListState()
}
state.errorMessage != null -> {
@@ -54,8 +54,8 @@ import com.m57.hermescontrol.R
import com.m57.hermescontrol.ui.common.EmptyState
import com.m57.hermescontrol.ui.common.ErrorState
import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.LoadingState
import com.m57.hermescontrol.ui.common.NavIcon
import com.m57.hermescontrol.ui.common.SkeletonListState
import com.m57.hermescontrol.ui.common.ToastEffect
import com.m57.hermescontrol.ui.profiles.components.ProfileBuilderView
import com.m57.hermescontrol.ui.profiles.components.validateProfileName
@@ -134,7 +134,7 @@ fun ProfilesScreen(
} else {
when {
state.isLoading && state.profiles.isEmpty() -> {
LoadingState(modifier = Modifier.padding(paddingValues))
SkeletonListState(modifier = Modifier.padding(paddingValues))
}
state.errorMessage != null -> {
@@ -52,8 +52,8 @@ import com.m57.hermescontrol.theme.LocalHermesStatusColors
import com.m57.hermescontrol.ui.common.EmptyState
import com.m57.hermescontrol.ui.common.ErrorState
import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.LoadingState
import com.m57.hermescontrol.ui.common.NavIcon
import com.m57.hermescontrol.ui.common.SkeletonListState
import com.m57.hermescontrol.ui.common.ToastEffect
import com.m57.hermescontrol.ui.common.listContentPadding
import com.m57.hermescontrol.ui.common.listItemSpacing
@@ -102,10 +102,7 @@ fun ProvidersScreen(
) { paddingValues ->
when {
state.isLoading && state.providers.isEmpty() -> {
LoadingState(
subtitle = stringResource(R.string.loading_state_subtitle_providers),
modifier = Modifier.padding(paddingValues),
)
SkeletonListState(modifier = Modifier.padding(paddingValues))
}
state.errorMessage != null && state.providers.isEmpty() -> {
@@ -90,9 +90,9 @@ import com.m57.hermescontrol.theme.LocalSpacing
import com.m57.hermescontrol.ui.common.EmptyState
import com.m57.hermescontrol.ui.common.ErrorState
import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.LoadingState
import com.m57.hermescontrol.ui.common.NavIcon
import com.m57.hermescontrol.ui.common.SearchBar
import com.m57.hermescontrol.ui.common.SkeletonListState
import com.m57.hermescontrol.ui.common.StatCard
import com.m57.hermescontrol.ui.common.StatusBadge
import com.m57.hermescontrol.ui.common.StatusBadgeType
@@ -491,7 +491,7 @@ fun SessionsScreen(
Column(modifier = Modifier.fillMaxSize()) {
when {
state.isSearching && state.searchResults.isEmpty() -> {
LoadingState()
SkeletonListState()
}
state.searchError != null -> {
@@ -579,7 +579,7 @@ fun SessionsScreen(
}
state.isLoading && state.sessions.isEmpty() -> {
LoadingState()
SkeletonListState()
}
state.errorMessage != null -> {
@@ -74,6 +74,7 @@ import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.LoadingState
import com.m57.hermescontrol.ui.common.NavIcon
import com.m57.hermescontrol.ui.common.SearchBar
import com.m57.hermescontrol.ui.common.SkeletonListState
import com.m57.hermescontrol.ui.common.ToastEffect
import com.m57.hermescontrol.ui.common.listContentPadding
import com.m57.hermescontrol.ui.common.listItemSpacing
@@ -317,7 +318,7 @@ private fun InstalledSkillsView(
when {
state.isLoading -> {
LoadingState()
SkeletonListState()
}
state.errorMessage != null -> {
@@ -587,7 +588,7 @@ private fun HubBrowseView(
when {
state.isHubSearching -> {
LoadingState()
SkeletonListState()
}
state.hubSearchError != null -> {
@@ -48,7 +48,7 @@ import com.m57.hermescontrol.ui.common.DetailDialog
import com.m57.hermescontrol.ui.common.DetailRow
import com.m57.hermescontrol.ui.common.EmptyState
import com.m57.hermescontrol.ui.common.ErrorState
import com.m57.hermescontrol.ui.common.LoadingState
import com.m57.hermescontrol.ui.common.SkeletonListState
import com.m57.hermescontrol.ui.common.listContentPadding
import com.m57.hermescontrol.ui.common.listItemSpacing
import com.m57.hermescontrol.ui.skills.SkillsUiState
@@ -117,7 +117,7 @@ internal fun HubBrowseView(
when {
state.isHubSearching -> {
LoadingState()
SkeletonListState()
}
state.hubSearchError != null -> {
@@ -54,6 +54,7 @@ import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.LoadingState
import com.m57.hermescontrol.ui.common.NavIcon
import com.m57.hermescontrol.ui.common.SearchBar
import com.m57.hermescontrol.ui.common.SkeletonListState
import com.m57.hermescontrol.ui.common.listContentPadding
import com.m57.hermescontrol.ui.common.listItemSpacing
import com.m57.hermescontrol.ui.common.toDetailRows
@@ -177,7 +178,7 @@ internal fun InstalledSkillsView(
when {
state.isLoading -> {
LoadingState()
SkeletonListState()
}
state.errorMessage != null -> {
@@ -78,9 +78,9 @@ import com.m57.hermescontrol.ui.common.EmptyState
import com.m57.hermescontrol.ui.common.ErrorState
import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.InfoRow
import com.m57.hermescontrol.ui.common.LoadingState
import com.m57.hermescontrol.ui.common.NavIcon
import com.m57.hermescontrol.ui.common.SectionHeader
import com.m57.hermescontrol.ui.common.SkeletonListState
import com.m57.hermescontrol.ui.common.StatCard
import com.m57.hermescontrol.ui.common.StatusBadge
import com.m57.hermescontrol.ui.common.StatusBadgeType
@@ -252,7 +252,7 @@ fun SystemScreen(
when {
state.isLoading && state.stats == null && state.doctorReport == null &&
state.status == null -> {
LoadingState()
SkeletonListState()
}
state.errorMessage != null -> {
@@ -44,9 +44,9 @@ import com.m57.hermescontrol.ui.common.DetailDialog
import com.m57.hermescontrol.ui.common.EmptyState
import com.m57.hermescontrol.ui.common.ErrorState
import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.LoadingState
import com.m57.hermescontrol.ui.common.NavIcon
import com.m57.hermescontrol.ui.common.SearchBar
import com.m57.hermescontrol.ui.common.SkeletonListState
import com.m57.hermescontrol.ui.common.ToastEffect
import com.m57.hermescontrol.ui.common.listContentPadding
import com.m57.hermescontrol.ui.common.listItemSpacing
@@ -87,7 +87,7 @@ fun ToolsetsScreen(
) { paddingValues ->
when {
state.isLoading && state.toolsets.isEmpty() -> {
LoadingState(modifier = Modifier.padding(paddingValues))
SkeletonListState(modifier = Modifier.padding(paddingValues))
}
state.errorMessage != null -> {
@@ -53,9 +53,9 @@ import com.m57.hermescontrol.ui.common.DetailDialog
import com.m57.hermescontrol.ui.common.EmptyState
import com.m57.hermescontrol.ui.common.ErrorState
import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.LoadingState
import com.m57.hermescontrol.ui.common.NavIcon
import com.m57.hermescontrol.ui.common.SearchBar
import com.m57.hermescontrol.ui.common.SkeletonListState
import com.m57.hermescontrol.ui.common.StatusBadge
import com.m57.hermescontrol.ui.common.StatusBadgeType
import com.m57.hermescontrol.ui.common.ToastEffect
@@ -244,7 +244,7 @@ fun WebhooksScreen(
) { paddingValues ->
when {
state.isLoading && state.baseUrl == null -> {
LoadingState(modifier = Modifier.padding(paddingValues))
SkeletonListState(modifier = Modifier.padding(paddingValues))
}
state.errorMessage != null && state.baseUrl == null -> {