diff --git a/app/src/main/java/com/m57/hermescontrol/ui/achievements/AchievementsScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/achievements/AchievementsScreen.kt index 7d4f3cff..a085a9eb 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/achievements/AchievementsScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/achievements/AchievementsScreen.kt @@ -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() -> { diff --git a/app/src/main/java/com/m57/hermescontrol/ui/analytics/AnalyticsScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/analytics/AnalyticsScreen.kt index d3435ade..cc66d31f 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/analytics/AnalyticsScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/analytics/AnalyticsScreen.kt @@ -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 -> { diff --git a/app/src/main/java/com/m57/hermescontrol/ui/channels/ChannelsScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/channels/ChannelsScreen.kt index 9882fe84..7717f9a3 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/channels/ChannelsScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/channels/ChannelsScreen.kt @@ -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() -> { diff --git a/app/src/main/java/com/m57/hermescontrol/ui/common/StateViews.kt b/app/src/main/java/com/m57/hermescontrol/ui/common/StateViews.kt index 5a9af51c..d61a4808 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/common/StateViews.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/common/StateViews.kt @@ -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) diff --git a/app/src/main/java/com/m57/hermescontrol/ui/config/ConfigScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/config/ConfigScreen.kt index 010913d7..902b75bc 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/config/ConfigScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/config/ConfigScreen.kt @@ -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 } diff --git a/app/src/main/java/com/m57/hermescontrol/ui/cron/CronJobsScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/cron/CronJobsScreen.kt index 57243664..aca6a9e8 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/cron/CronJobsScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/cron/CronJobsScreen.kt @@ -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 -> { diff --git a/app/src/main/java/com/m57/hermescontrol/ui/gateway/GatewayScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/gateway/GatewayScreen.kt index fb00c01c..16e1f3b6 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/gateway/GatewayScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/gateway/GatewayScreen.kt @@ -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 -> { diff --git a/app/src/main/java/com/m57/hermescontrol/ui/kanban/KanbanScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/kanban/KanbanScreen.kt index 33f43e4b..220f219d 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/kanban/KanbanScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/kanban/KanbanScreen.kt @@ -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 -> { diff --git a/app/src/main/java/com/m57/hermescontrol/ui/keys/KeysScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/keys/KeysScreen.kt index 79a9d3e8..78d74c18 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/keys/KeysScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/keys/KeysScreen.kt @@ -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 -> { diff --git a/app/src/main/java/com/m57/hermescontrol/ui/logs/LogsScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/logs/LogsScreen.kt index 33ad7495..01d1d680 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/logs/LogsScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/logs/LogsScreen.kt @@ -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 -> { diff --git a/app/src/main/java/com/m57/hermescontrol/ui/mcp/McpServersScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/mcp/McpServersScreen.kt index b398638f..b76cba13 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/mcp/McpServersScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/mcp/McpServersScreen.kt @@ -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 -> { diff --git a/app/src/main/java/com/m57/hermescontrol/ui/model/ModelScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/model/ModelScreen.kt index 99a63971..3d6c552a 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/model/ModelScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/model/ModelScreen.kt @@ -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 -> { diff --git a/app/src/main/java/com/m57/hermescontrol/ui/plugins/PluginsScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/plugins/PluginsScreen.kt index afe774df..e126bf81 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/plugins/PluginsScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/plugins/PluginsScreen.kt @@ -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 -> { diff --git a/app/src/main/java/com/m57/hermescontrol/ui/process/ProcessesScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/process/ProcessesScreen.kt index 6fa9ff66..b709e7f6 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/process/ProcessesScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/process/ProcessesScreen.kt @@ -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 -> { diff --git a/app/src/main/java/com/m57/hermescontrol/ui/profiles/ProfilesScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/profiles/ProfilesScreen.kt index 365384b6..8dd0c128 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/profiles/ProfilesScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/profiles/ProfilesScreen.kt @@ -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 -> { diff --git a/app/src/main/java/com/m57/hermescontrol/ui/providers/ProvidersScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/providers/ProvidersScreen.kt index c6cb9918..708906e3 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/providers/ProvidersScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/providers/ProvidersScreen.kt @@ -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() -> { diff --git a/app/src/main/java/com/m57/hermescontrol/ui/sessions/SessionsScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/sessions/SessionsScreen.kt index d672d668..662fd590 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/sessions/SessionsScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/sessions/SessionsScreen.kt @@ -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 -> { diff --git a/app/src/main/java/com/m57/hermescontrol/ui/skills/SkillsScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/skills/SkillsScreen.kt index 953ed63b..5bd1565b 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/skills/SkillsScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/skills/SkillsScreen.kt @@ -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 -> { diff --git a/app/src/main/java/com/m57/hermescontrol/ui/skills/components/HubSkillsTab.kt b/app/src/main/java/com/m57/hermescontrol/ui/skills/components/HubSkillsTab.kt index 5808eaa9..66ec3063 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/skills/components/HubSkillsTab.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/skills/components/HubSkillsTab.kt @@ -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 -> { diff --git a/app/src/main/java/com/m57/hermescontrol/ui/skills/components/InstalledSkillsTab.kt b/app/src/main/java/com/m57/hermescontrol/ui/skills/components/InstalledSkillsTab.kt index 55aee0cb..2a9bf033 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/skills/components/InstalledSkillsTab.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/skills/components/InstalledSkillsTab.kt @@ -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 -> { diff --git a/app/src/main/java/com/m57/hermescontrol/ui/system/SystemScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/system/SystemScreen.kt index 01ad7667..10bb5f08 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/system/SystemScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/system/SystemScreen.kt @@ -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 -> { diff --git a/app/src/main/java/com/m57/hermescontrol/ui/toolsets/ToolsetsScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/toolsets/ToolsetsScreen.kt index d38ee2dd..2f071b45 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/toolsets/ToolsetsScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/toolsets/ToolsetsScreen.kt @@ -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 -> { diff --git a/app/src/main/java/com/m57/hermescontrol/ui/webhooks/WebhooksScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/webhooks/WebhooksScreen.kt index 8ff7c4ec..07d30840 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/webhooks/WebhooksScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/webhooks/WebhooksScreen.kt @@ -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 -> {