diff --git a/app/src/main/java/io/legado/app/ui/main/homepage/HomepageScreen.kt b/app/src/main/java/io/legado/app/ui/main/homepage/HomepageScreen.kt index 675673854..2db624614 100644 --- a/app/src/main/java/io/legado/app/ui/main/homepage/HomepageScreen.kt +++ b/app/src/main/java/io/legado/app/ui/main/homepage/HomepageScreen.kt @@ -1,9 +1,15 @@ package io.legado.app.ui.main.homepage import androidx.activity.compose.BackHandler +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibilityScope import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.animation.SharedTransitionScope +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.togetherWith import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -63,6 +69,7 @@ import io.legado.app.ui.main.homepage.modules.ButtonGroupModule import io.legado.app.ui.main.homepage.modules.CardModule import io.legado.app.ui.main.homepage.modules.GridModule import io.legado.app.ui.main.homepage.modules.GridRankingModule +import io.legado.app.ui.main.homepage.modules.HomepageModuleSkeleton import io.legado.app.ui.main.homepage.modules.RankingModule import io.legado.app.ui.main.homepage.modules.WaterfallItem import io.legado.app.ui.theme.LegadoTheme @@ -75,8 +82,8 @@ import io.legado.app.ui.widget.components.book.SearchBookPreviewSheet import io.legado.app.ui.widget.components.button.series.SmallTonalButton import io.legado.app.ui.widget.components.card.GlassCard import io.legado.app.ui.widget.components.icon.AppIcon -import io.legado.app.ui.widget.components.progressIndicator.AppCircularProgressIndicator import io.legado.app.ui.widget.components.tabRow.AppTabRow +import io.legado.app.ui.widget.components.EmptyMessage import io.legado.app.ui.widget.components.text.AppText import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults @@ -198,9 +205,10 @@ fun HomepageScreen( .padding(paddingValues), ) { if (selectedSets.isEmpty()) { - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - AppText(stringResource(R.string.homepage_no_source_sets_selected)) - } + EmptyMessage( + messageResId = R.string.homepage_no_source_sets_selected, + modifier = Modifier.fillMaxSize(), + ) } else { HorizontalPager( state = pagerState, @@ -394,100 +402,103 @@ private fun ModuleList( // 2. 内容正文 when (val state = moduleUi.state) { - is ModuleLoadState.Loading -> { - item( - key = "loading_${moduleUi.globalId}", - span = StaggeredGridItemSpan.FullLine - ) { - Box( - modifier = Modifier - .fillMaxWidth() - .height(120.dp), - contentAlignment = Alignment.Center, - ) { - AppCircularProgressIndicator() - } - } - } - + is ModuleLoadState.Loading, is ModuleLoadState.Error -> { item( - key = "error_${moduleUi.globalId}", + key = "status_${moduleUi.globalId}", span = StaggeredGridItemSpan.FullLine ) { - Column( - modifier = Modifier - .fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - GlassCard( - onClick = { onErrorClick(state.message) }, - containerColor = LegadoTheme.colorScheme.errorContainer.copy( - alpha = 0.6f - ), - ) { - Column( - modifier = Modifier.fillMaxWidth() - ) { - - Row( - modifier = Modifier - .fillMaxWidth() - .padding( - horizontal = 16.dp, - vertical = 16.dp - ), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(12.dp) - ) { - - AppIcon( - imageVector = Icons.Outlined.Info, - contentDescription = null, - tint = LegadoTheme.colorScheme.error - ) - - AppText( - text = state.message, - color = LegadoTheme.colorScheme.error, - style = LegadoTheme.typography.bodySmall, - modifier = Modifier.weight(1f), - maxLines = 2, - overflow = TextOverflow.Ellipsis, - ) - } - - HorizontalDivider( - color = LegadoTheme.colorScheme.error.copy(alpha = 0.3f) + AnimatedContent( + targetState = state, + transitionSpec = { + fadeIn(tween(300)) togetherWith fadeOut(tween(300)) + }, + contentKey = { it::class }, + ) { targetState -> + when (targetState) { + is ModuleLoadState.Loading -> { + HomepageModuleSkeleton( + type = moduleUi.type, + modifier = Modifier.fillMaxWidth(), ) + } - Box( - modifier = Modifier - .fillMaxWidth() - .clickable { - viewModel.retryModule(moduleUi.globalId) - } - .padding(vertical = 10.dp), - contentAlignment = Alignment.Center + is ModuleLoadState.Error -> { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally ) { - Row( - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalAlignment = Alignment.CenterVertically + GlassCard( + onClick = { onErrorClick(targetState.message) }, + containerColor = LegadoTheme.colorScheme.errorContainer.copy( + alpha = 0.6f + ), ) { - AppIcon( - imageVector = Icons.Default.Refresh, - contentDescription = null, - tint = LegadoTheme.colorScheme.error - ) + Column( + modifier = Modifier.fillMaxWidth() + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding( + horizontal = 16.dp, + vertical = 16.dp + ), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + AppIcon( + imageVector = Icons.Outlined.Info, + contentDescription = null, + tint = LegadoTheme.colorScheme.error + ) - AppText( - text = "重试", - color = LegadoTheme.colorScheme.error, - style = LegadoTheme.typography.labelMedium - ) + AppText( + text = targetState.message, + color = LegadoTheme.colorScheme.error, + style = LegadoTheme.typography.bodySmall, + modifier = Modifier.weight(1f), + maxLines = 2, + overflow = TextOverflow.Ellipsis, + ) + } + + HorizontalDivider( + color = LegadoTheme.colorScheme.error.copy(alpha = 0.3f) + ) + + Box( + modifier = Modifier + .fillMaxWidth() + .clickable { + viewModel.retryModule(moduleUi.globalId) + } + .padding(vertical = 10.dp), + contentAlignment = Alignment.Center + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + AppIcon( + imageVector = Icons.Default.Refresh, + contentDescription = null, + tint = LegadoTheme.colorScheme.error + ) + + AppText( + text = "重试", + color = LegadoTheme.colorScheme.error, + style = LegadoTheme.typography.labelMedium + ) + } + } + } } } } + + else -> {} } } } @@ -520,19 +531,21 @@ private fun ModuleList( item.book.bookUrl, "home:${moduleUi.globalId}:waterfall:$index" ) - WaterfallItem( - item = item, - onClick = { - viewModel.onBookClick( - item.book, - sharedCoverKey - ) - }, - onLongClick = onBookLongClick, - sharedTransitionScope = sharedTransitionScope, - animatedVisibilityScope = animatedVisibilityScope, - sharedCoverKey = sharedCoverKey, - ) + AnimatedVisibility(visible = true, enter = fadeIn()) { + WaterfallItem( + item = item, + onClick = { + viewModel.onBookClick( + item.book, + sharedCoverKey + ) + }, + onLongClick = onBookLongClick, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope, + sharedCoverKey = sharedCoverKey, + ) + } } item( @@ -556,20 +569,22 @@ private fun ModuleList( item.book.bookUrl, "home:${moduleUi.globalId}:infinite:$index" ) - SearchBookGridItem( - book = item.book, - shelfState = item.shelfState, - onClick = { - viewModel.onBookClick( - item.book, - sharedCoverKey - ) - }, - onLongClick = onBookLongClick, - sharedTransitionScope = sharedTransitionScope, - animatedVisibilityScope = animatedVisibilityScope, - sharedCoverKey = sharedCoverKey - ) + AnimatedVisibility(visible = true, enter = fadeIn()) { + SearchBookGridItem( + book = item.book, + shelfState = item.shelfState, + onClick = { + viewModel.onBookClick( + item.book, + sharedCoverKey + ) + }, + onLongClick = onBookLongClick, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope, + sharedCoverKey = sharedCoverKey + ) + } } item( @@ -592,19 +607,21 @@ private fun ModuleList( key = "content_${moduleUi.globalId}", span = StaggeredGridItemSpan.FullLine ) { - GridModule( - books = state.books, - onClick = { book, sharedCoverKey -> - viewModel.onBookClick(book, sharedCoverKey) - }, - onLongClick = onBookLongClick, - modifier = Modifier.fillMaxWidth(), - columns = columns, - maxRows = rows, - sharedTransitionScope = sharedTransitionScope, - animatedVisibilityScope = animatedVisibilityScope, - sharedCoverKeySourceId = "home:${moduleUi.globalId}:grid", - ) + AnimatedVisibility(visible = true, enter = fadeIn()) { + GridModule( + books = state.books, + onClick = { book, sharedCoverKey -> + viewModel.onBookClick(book, sharedCoverKey) + }, + onLongClick = onBookLongClick, + modifier = Modifier.fillMaxWidth(), + columns = columns, + maxRows = rows, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope, + sharedCoverKeySourceId = "home:${moduleUi.globalId}:grid", + ) + } } } @@ -615,17 +632,19 @@ private fun ModuleList( key = "content_${moduleUi.globalId}", span = StaggeredGridItemSpan.FullLine ) { - BannerModule( - books = state.books, - onClick = { book, sharedCoverKey -> - viewModel.onBookClick(book, sharedCoverKey) - }, - onLongClick = onBookLongClick, - modifier = Modifier.fillMaxWidth(), - sharedTransitionScope = sharedTransitionScope, - animatedVisibilityScope = animatedVisibilityScope, - sharedCoverKeySourceId = "home:${moduleUi.globalId}:banner", - ) + AnimatedVisibility(visible = true, enter = fadeIn()) { + BannerModule( + books = state.books, + onClick = { book, sharedCoverKey -> + viewModel.onBookClick(book, sharedCoverKey) + }, + onLongClick = onBookLongClick, + modifier = Modifier.fillMaxWidth(), + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope, + sharedCoverKeySourceId = "home:${moduleUi.globalId}:banner", + ) + } } } @@ -634,17 +653,19 @@ private fun ModuleList( key = "content_${moduleUi.globalId}", span = StaggeredGridItemSpan.FullLine ) { - RankingModule( - books = state.books, - onClick = { book, sharedCoverKey -> - viewModel.onBookClick(book, sharedCoverKey) - }, - onLongClick = onBookLongClick, - modifier = Modifier.fillMaxWidth(), - sharedTransitionScope = sharedTransitionScope, - animatedVisibilityScope = animatedVisibilityScope, - sharedCoverKeySourceId = "home:${moduleUi.globalId}:ranking", - ) + AnimatedVisibility(visible = true, enter = fadeIn()) { + RankingModule( + books = state.books, + onClick = { book, sharedCoverKey -> + viewModel.onBookClick(book, sharedCoverKey) + }, + onLongClick = onBookLongClick, + modifier = Modifier.fillMaxWidth(), + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope, + sharedCoverKeySourceId = "home:${moduleUi.globalId}:ranking", + ) + } } } @@ -653,18 +674,20 @@ private fun ModuleList( key = "content_${moduleUi.globalId}", span = StaggeredGridItemSpan.FullLine ) { - GridRankingModule( - books = state.books, - onClick = { book, sharedCoverKey -> - viewModel.onBookClick(book, sharedCoverKey) - }, - onLongClick = onBookLongClick, - modifier = Modifier.fillMaxWidth(), - rows = config["layout_rows"]?.toIntOrNull() ?: 4, - sharedTransitionScope = sharedTransitionScope, - animatedVisibilityScope = animatedVisibilityScope, - sharedCoverKeySourceId = "home:${moduleUi.globalId}:grid-ranking", - ) + AnimatedVisibility(visible = true, enter = fadeIn()) { + GridRankingModule( + books = state.books, + onClick = { book, sharedCoverKey -> + viewModel.onBookClick(book, sharedCoverKey) + }, + onLongClick = onBookLongClick, + modifier = Modifier.fillMaxWidth(), + rows = config["layout_rows"]?.toIntOrNull() ?: 4, + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope, + sharedCoverKeySourceId = "home:${moduleUi.globalId}:grid-ranking", + ) + } } } @@ -673,17 +696,19 @@ private fun ModuleList( key = "content_${moduleUi.globalId}", span = StaggeredGridItemSpan.FullLine ) { - CardModule( - books = state.books, - onClick = { book, sharedCoverKey -> - viewModel.onBookClick(book, sharedCoverKey) - }, - onLongClick = onBookLongClick, - modifier = Modifier.fillMaxWidth(), - sharedTransitionScope = sharedTransitionScope, - animatedVisibilityScope = animatedVisibilityScope, - sharedCoverKeySourceId = "home:${moduleUi.globalId}:card", - ) + AnimatedVisibility(visible = true, enter = fadeIn()) { + CardModule( + books = state.books, + onClick = { book, sharedCoverKey -> + viewModel.onBookClick(book, sharedCoverKey) + }, + onLongClick = onBookLongClick, + modifier = Modifier.fillMaxWidth(), + sharedTransitionScope = sharedTransitionScope, + animatedVisibilityScope = animatedVisibilityScope, + sharedCoverKeySourceId = "home:${moduleUi.globalId}:card", + ) + } } } diff --git a/app/src/main/java/io/legado/app/ui/main/homepage/modules/SkeletonPlaceholders.kt b/app/src/main/java/io/legado/app/ui/main/homepage/modules/SkeletonPlaceholders.kt new file mode 100644 index 000000000..86d4caf96 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/main/homepage/modules/SkeletonPlaceholders.kt @@ -0,0 +1,405 @@ +package io.legado.app.ui.main.homepage.modules + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.core.LinearEasing +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.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +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.lazy.LazyRow +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +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.unit.Dp +import androidx.compose.ui.unit.dp +import io.legado.app.domain.model.HomepageModuleType +import io.legado.app.ui.theme.LegadoTheme +import io.legado.app.ui.widget.components.card.GlassCard + +@Composable +private fun rememberShimmerBrush(): Brush { + val infiniteTransition = rememberInfiniteTransition(label = "shimmer") + val offset by infiniteTransition.animateFloat( + initialValue = -1f, + targetValue = 2f, + animationSpec = infiniteRepeatable( + animation = tween(durationMillis = 1200, easing = LinearEasing), + repeatMode = RepeatMode.Restart, + ), + label = "shimmerOffset", + ) + + val colorScheme = LegadoTheme.colorScheme + val colors = remember(colorScheme) { + listOf( + colorScheme.surfaceContainerHighest, + colorScheme.surfaceContainerHigh, + colorScheme.surfaceContainerHighest, + ) + } + + return Brush.linearGradient( + colors = colors, + start = Offset(offset * 300f, 0f), + end = Offset(offset * 300f + 300f, 0f), + ) +} + +@Composable +private fun SkeletonBox( + modifier: Modifier = Modifier, + cornerRadius: Dp = 8.dp, +) { + Box( + modifier = modifier + .clip(RoundedCornerShape(cornerRadius)) + .background(rememberShimmerBrush()) + ) +} + +// ── Waterfall skeleton ── + +@Composable +fun WaterfallSkeletonItem(modifier: Modifier = Modifier) { + GlassCard( + modifier = modifier, + containerColor = LegadoTheme.colorScheme.surfaceContainerLow, + ) { + Column { + SkeletonBox( + modifier = Modifier + .fillMaxWidth() + .height(180.dp), + cornerRadius = 16.dp, + ) + Column( + modifier = Modifier.padding(horizontal = 8.dp, vertical = 8.dp) + ) { + SkeletonBox( + modifier = Modifier + .fillMaxWidth(0.8f) + .height(14.dp), + ) + Spacer(modifier = Modifier.height(6.dp)) + SkeletonBox( + modifier = Modifier + .fillMaxWidth(0.5f) + .height(10.dp), + ) + Spacer(modifier = Modifier.height(8.dp)) + SkeletonBox( + modifier = Modifier + .fillMaxWidth(0.6f) + .height(10.dp), + ) + Spacer(modifier = Modifier.height(4.dp)) + Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) { + SkeletonBox( + modifier = Modifier + .width(40.dp) + .height(18.dp), + cornerRadius = 4.dp, + ) + SkeletonBox( + modifier = Modifier + .width(52.dp) + .height(18.dp), + cornerRadius = 4.dp, + ) + } + } + } + } +} + +// ── Grid skeleton ── + +@Composable +fun GridSkeletonItem(modifier: Modifier = Modifier) { + Column(modifier = modifier) { + SkeletonBox( + modifier = Modifier + .fillMaxWidth() + .aspectRatio(5f / 7f), + cornerRadius = 4.dp, + ) + Spacer(modifier = Modifier.height(4.dp)) + SkeletonBox( + modifier = Modifier + .fillMaxWidth(0.8f) + .height(12.dp), + ) + } +} + +@Composable +fun GridSkeletonModule( + modifier: Modifier = Modifier, + columns: Int = 3, + rows: Int = 2, +) { + Column( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(4.dp), + ) { + repeat(rows) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + repeat(columns) { + GridSkeletonItem(modifier = Modifier.weight(1f)) + } + } + } + } +} + +// ── Banner skeleton ── + +@Composable +fun BannerSkeletonModule( + modifier: Modifier = Modifier, + itemCount: Int = 6, +) { + LazyRow( + modifier = modifier, + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + items(itemCount) { + SkeletonBox( + modifier = Modifier + .width(96.dp) + .aspectRatio(5f / 7f), + cornerRadius = 12.dp, + ) + } + } +} + +// ── Card skeleton ── + +@Composable +fun CardSkeletonItem(modifier: Modifier = Modifier) { + Column( + modifier = modifier + .width(120.dp) + .clip(RoundedCornerShape(16.dp)) + .background(LegadoTheme.colorScheme.surfaceContainerLow), + ) { + SkeletonBox( + modifier = Modifier + .fillMaxWidth() + .aspectRatio(5f / 7f), + cornerRadius = 0.dp, + ) + Column(modifier = Modifier.padding(8.dp)) { + SkeletonBox( + modifier = Modifier + .fillMaxWidth() + .height(14.dp), + ) + Spacer(modifier = Modifier.height(4.dp)) + SkeletonBox( + modifier = Modifier + .fillMaxWidth(0.7f) + .height(14.dp), + ) + } + } +} + +@Composable +fun CardSkeletonModule( + modifier: Modifier = Modifier, + itemCount: Int = 5, +) { + LazyRow( + modifier = modifier, + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + items(itemCount) { + CardSkeletonItem() + } + } +} + +// ── Ranking skeleton ── + +@Composable +fun RankingSkeletonModule( + modifier: Modifier = Modifier, + rows: Int = 5, +) { + GlassCard( + modifier = modifier, + containerColor = LegadoTheme.colorScheme.surfaceContainerLow, + cornerRadius = 16.dp, + ) { + Column(modifier = Modifier.padding(top = 12.dp)) { + repeat(rows) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 4.dp, vertical = 4.dp), + verticalAlignment = androidx.compose.ui.Alignment.CenterVertically, + ) { + SkeletonBox( + modifier = Modifier + .size(18.dp), + ) + Spacer(modifier = Modifier.width(14.dp)) + SkeletonBox( + modifier = Modifier + .width(52.dp) + .aspectRatio(5f / 7f), + ) + Spacer(modifier = Modifier.width(8.dp)) + Column(modifier = Modifier.weight(1f)) { + SkeletonBox( + modifier = Modifier + .fillMaxWidth(0.7f) + .height(14.dp), + ) + Spacer(modifier = Modifier.height(4.dp)) + SkeletonBox( + modifier = Modifier + .fillMaxWidth(0.4f) + .height(10.dp), + ) + } + } + } + } + } +} + +// ── GridRanking skeleton ── + +@Composable +fun GridRankingSkeletonModule( + modifier: Modifier = Modifier, + rows: Int = 4, +) { + GlassCard( + modifier = modifier, + containerColor = LegadoTheme.colorScheme.surfaceContainerLow, + cornerRadius = 20.dp, + ) { + Column(modifier = Modifier.padding(12.dp)) { + repeat(rows) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 4.dp), + verticalAlignment = androidx.compose.ui.Alignment.CenterVertically, + ) { + SkeletonBox( + modifier = Modifier + .width(48.dp) + .aspectRatio(5f / 7f), + ) + Spacer(modifier = Modifier.width(8.dp)) + SkeletonBox( + modifier = Modifier + .size(18.dp), + ) + Spacer(modifier = Modifier.width(4.dp)) + Column(modifier = Modifier.weight(1f)) { + SkeletonBox( + modifier = Modifier + .fillMaxWidth(0.8f) + .height(14.dp), + ) + Spacer(modifier = Modifier.height(4.dp)) + SkeletonBox( + modifier = Modifier + .fillMaxWidth(0.5f) + .height(10.dp), + ) + } + } + } + } + } +} + +// ── Dispatcher ── + +@Composable +fun HomepageModuleSkeleton( + type: HomepageModuleType, + modifier: Modifier = Modifier, + columns: Int = 3, + rows: Int = 2, +) { + when (type) { + HomepageModuleType.Waterfall -> { + Column( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + repeat(2) { + WaterfallSkeletonItem(modifier = Modifier.fillMaxWidth()) + } + } + } + + HomepageModuleType.InfiniteGrid -> { + GridSkeletonModule( + modifier = modifier, + columns = columns, + rows = rows, + ) + } + + HomepageModuleType.Grid -> { + GridSkeletonModule( + modifier = modifier, + columns = columns, + rows = rows, + ) + } + + HomepageModuleType.Banner -> { + BannerSkeletonModule(modifier = modifier) + } + + HomepageModuleType.Card -> { + CardSkeletonModule(modifier = modifier) + } + + HomepageModuleType.Ranking -> { + RankingSkeletonModule(modifier = modifier) + } + + HomepageModuleType.GridRanking -> { + GridRankingSkeletonModule(modifier = modifier) + } + + HomepageModuleType.ButtonGroup -> { + // ButtonGroup loads instantly (no network), no skeleton needed + } + + HomepageModuleType.Unknown -> {} + } +}