package com.craigvg.lichun_android.ui.components.indicators import androidx.compose.animation.core.* import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.* 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.Color import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.craigvg.lichun_android.ui.theme.AppColors import com.craigvg.lichun_android.ui.theme.AppSpacing @Composable fun SkeletonView( modifier: Modifier = Modifier, cornerRadius: Dp = AppSpacing.cornerRadius ) { val shimmerTransition = rememberInfiniteTransition(label = "shimmer") val shimmerOffset by shimmerTransition.animateFloat( initialValue = -400f, targetValue = 400f, animationSpec = infiniteRepeatable( animation = tween(2000, easing = LinearEasing), repeatMode = RepeatMode.Restart ), label = "shimmerOffset" ) Box( modifier = modifier .clip(RoundedCornerShape(cornerRadius)) .background(AppColors.surfaceElevated) .background( Brush.linearGradient( colors = listOf( Color.Transparent, Color(0xFFFFE8DC).copy(alpha = 0.4f), Color(0xFFFFF5F0).copy(alpha = 0.5f), Color(0xFFFFE8DC).copy(alpha = 0.4f), Color.Transparent ), start = Offset(shimmerOffset, 0f), end = Offset(shimmerOffset + 400f, 0f) ) ) ) } @Composable fun SkeletonAvatar( modifier: Modifier = Modifier, size: Dp = AppSpacing.avatarMedium ) { SkeletonView( modifier = modifier.size(size).clip(CircleShape), cornerRadius = size / 2 ) } @Composable fun SkeletonListItem(modifier: Modifier = Modifier) { Row( modifier = modifier .fillMaxWidth() .padding(horizontal = AppSpacing.md, vertical = AppSpacing.sm), horizontalArrangement = Arrangement.spacedBy(AppSpacing.md) ) { SkeletonAvatar(size = AppSpacing.avatarSmall) Column(verticalArrangement = Arrangement.spacedBy(AppSpacing.xs)) { SkeletonView(modifier = Modifier.width(120.dp).height(16.dp)) SkeletonView(modifier = Modifier.width(80.dp).height(12.dp)) } } } @Composable fun SkeletonCard(modifier: Modifier = Modifier) { Column( modifier = modifier .fillMaxWidth() .clip(RoundedCornerShape(16.dp)) .background(AppColors.cardBackground) .padding(AppSpacing.md), verticalArrangement = Arrangement.spacedBy(AppSpacing.sm) ) { SkeletonView(modifier = Modifier.width(150.dp).height(20.dp)) SkeletonView(modifier = Modifier.fillMaxWidth().height(16.dp)) SkeletonView(modifier = Modifier.width(200.dp).height(16.dp)) Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween ) { SkeletonView(modifier = Modifier.width(60.dp).height(12.dp)) SkeletonView(modifier = Modifier.width(80.dp).height(12.dp)) } } } @Composable fun LoadingStateView( isLoading: Boolean, skeletonCount: Int = 3, content: @Composable () -> Unit ) { if (isLoading) { Column(verticalArrangement = Arrangement.spacedBy(AppSpacing.md)) { repeat(skeletonCount) { SkeletonListItem() } } } else { content() } }