diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 8d0e142..2a4975a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -185,4 +185,5 @@ dependencies { implementation(libs.room.runtime) implementation(libs.room.ktx) implementation(libs.timber) + implementation(libs.compose.shimmer) } \ No newline at end of file diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/component/ToolCard.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/component/ToolCard.kt index 466cf9d..5fab36f 100644 --- a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/component/ToolCard.kt +++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/component/ToolCard.kt @@ -1,5 +1,8 @@ package top.fatweb.oxygen.toolbox.ui.component +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.infiniteRepeatable import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -15,6 +18,7 @@ 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.RoundedCornerShape import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults @@ -30,6 +34,11 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import com.valentinilk.shimmer.LocalShimmerTheme +import com.valentinilk.shimmer.ShimmerBounds +import com.valentinilk.shimmer.rememberShimmer +import com.valentinilk.shimmer.shimmer +import com.valentinilk.shimmer.shimmerSpec import top.fatweb.oxygen.toolbox.R import top.fatweb.oxygen.toolbox.icon.OxygenIcons import top.fatweb.oxygen.toolbox.model.tool.ToolEntity @@ -244,3 +253,131 @@ private fun AuthorInfo( ) } } + +@Composable +fun ToolCardSkeleton( + modifier: Modifier = Modifier +) { + val shimmer = rememberShimmer( + shimmerBounds = ShimmerBounds.Window, + theme = LocalShimmerTheme.current.copy( + animationSpec = infiniteRepeatable( + animation = shimmerSpec( + durationMillis = 1_500, + easing = LinearEasing, + delayMillis = 200 + ), + repeatMode = RepeatMode.Restart + ) + ) + ) + + Card( + modifier = modifier + .clip(RoundedCornerShape(8.dp)), + shape = RoundedCornerShape(8.dp), + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface) + ) { + Column( + modifier = Modifier.padding(16.dp) + ) { + Row( + modifier = Modifier + .height(28.dp) + ) { + Surface( + modifier = modifier + .fillMaxHeight() + .width(64.dp) + .shimmer(shimmer), + shape = RoundedCornerShape(8.dp), + color = MaterialTheme.colorScheme.surfaceContainer + ) {} + } + Spacer(Modifier.height(16.dp)) + Box( + modifier = Modifier + .fillMaxWidth(), + contentAlignment = Alignment.Center + ) { + Surface( + modifier = Modifier + .size(80.dp) + .shimmer(shimmer), + shape = RoundedCornerShape(8.dp), + color = MaterialTheme.colorScheme.surfaceContainer + ) {} + } + Spacer(Modifier.height(16.dp)) + Column( + modifier = modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(6.dp) + ) { + Surface( + modifier = Modifier + .size(width = 40.dp, height = 22.dp) + .shimmer(shimmer), + shape = RoundedCornerShape(4.dp), + color = MaterialTheme.colorScheme.surfaceContainer + ) {} + Surface( + modifier = Modifier + .size(width = 60.dp, height = 20.dp) + .shimmer(shimmer), + shape = RoundedCornerShape(4.dp), + color = MaterialTheme.colorScheme.surfaceContainer + ) {} + Column( + verticalArrangement = Arrangement.spacedBy(2.dp) + ) { + Surface( + modifier = Modifier + .size(width = 120.dp, height = 12.dp) + .shimmer(shimmer), + shape = RoundedCornerShape(4.dp), + color = MaterialTheme.colorScheme.surfaceContainer + ) {} + Surface( + modifier = Modifier + .size(width = 120.dp, height = 12.dp) + .shimmer(shimmer), + shape = RoundedCornerShape(4.dp), + color = MaterialTheme.colorScheme.surfaceContainer + ) {} + Surface( + modifier = Modifier + .size(width = 80.dp, height = 12.dp) + .shimmer(shimmer), + shape = RoundedCornerShape(4.dp), + color = MaterialTheme.colorScheme.surfaceContainer + ) {} + } + } + Spacer(Modifier.height(16.dp)) + Row( + modifier = Modifier + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally) + ) { + Surface( + modifier = Modifier + .size(24.dp) + .shimmer(shimmer), + shape = RoundedCornerShape(12.dp), + color = MaterialTheme.colorScheme.surfaceContainer + ) {} + Surface( + modifier = Modifier + .size(width = 120.dp, height = 12.dp) + .shimmer(shimmer), + shape = RoundedCornerShape(4.dp), + color = MaterialTheme.colorScheme.surfaceContainer + ) {} + } + } + } +} + +const val DEFAULT_TOOL_CARD_SKELETON_COUNT = 8 diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/star/ToolStarScreen.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/star/ToolStarScreen.kt index 0f360e2..edf0519 100644 --- a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/star/ToolStarScreen.kt +++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/star/ToolStarScreen.kt @@ -43,11 +43,13 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import top.fatweb.oxygen.toolbox.R import top.fatweb.oxygen.toolbox.icon.OxygenIcons import top.fatweb.oxygen.toolbox.model.tool.ToolEntity +import top.fatweb.oxygen.toolbox.ui.component.DEFAULT_TOOL_CARD_SKELETON_COUNT import top.fatweb.oxygen.toolbox.ui.component.DialogClickerRow import top.fatweb.oxygen.toolbox.ui.component.DialogSectionGroup import top.fatweb.oxygen.toolbox.ui.component.DialogTitle import top.fatweb.oxygen.toolbox.ui.component.Indicator import top.fatweb.oxygen.toolbox.ui.component.ToolCard +import top.fatweb.oxygen.toolbox.ui.component.ToolCardSkeleton import top.fatweb.oxygen.toolbox.ui.component.scrollbar.DraggableScrollbar import top.fatweb.oxygen.toolbox.ui.component.scrollbar.rememberDraggableScroller import top.fatweb.oxygen.toolbox.ui.component.scrollbar.scrollbarState @@ -96,6 +98,24 @@ internal fun StarScreen( when (starScreenUiState) { StarScreenUiState.Loading -> { Indicator() + LazyVerticalStaggeredGrid( + modifier = Modifier + .fillMaxSize(), + columns = StaggeredGridCells.Adaptive(160.dp), + contentPadding = PaddingValues(16.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalItemSpacing = 24.dp, + state = state + ) { + items(count = DEFAULT_TOOL_CARD_SKELETON_COUNT) { + ToolCardSkeleton() + } + + item(span = StaggeredGridItemSpan.FullLine) { + Spacer(Modifier.height(8.dp)) + Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.safeDrawing)) + } + } } StarScreenUiState.Nothing -> { @@ -210,6 +230,7 @@ private fun ToolMenu( @Composable private fun howManyTools(starScreenUiState: StarScreenUiState) = when (starScreenUiState) { - StarScreenUiState.Loading, StarScreenUiState.Nothing -> 0 + StarScreenUiState.Loading -> DEFAULT_TOOL_CARD_SKELETON_COUNT + StarScreenUiState.Nothing -> 0 is StarScreenUiState.Success -> starScreenUiState.tools.size } diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/tools/ToolsScreen.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/tools/ToolsScreen.kt index a13a7c2..240c786 100644 --- a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/tools/ToolsScreen.kt +++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/tools/ToolsScreen.kt @@ -47,11 +47,13 @@ import kotlinx.coroutines.launch import top.fatweb.oxygen.toolbox.R import top.fatweb.oxygen.toolbox.icon.OxygenIcons import top.fatweb.oxygen.toolbox.model.tool.ToolEntity +import top.fatweb.oxygen.toolbox.ui.component.DEFAULT_TOOL_CARD_SKELETON_COUNT import top.fatweb.oxygen.toolbox.ui.component.DialogClickerRow import top.fatweb.oxygen.toolbox.ui.component.DialogSectionGroup import top.fatweb.oxygen.toolbox.ui.component.DialogTitle import top.fatweb.oxygen.toolbox.ui.component.Indicator import top.fatweb.oxygen.toolbox.ui.component.ToolCard +import top.fatweb.oxygen.toolbox.ui.component.ToolCardSkeleton import top.fatweb.oxygen.toolbox.ui.component.scrollbar.DraggableScrollbar import top.fatweb.oxygen.toolbox.ui.component.scrollbar.rememberDraggableScroller import top.fatweb.oxygen.toolbox.ui.component.scrollbar.scrollbarState @@ -116,6 +118,24 @@ internal fun ToolsScreen( when (toolsScreenUiState) { ToolsScreenUiState.Loading -> { Indicator() + LazyVerticalStaggeredGrid( + modifier = Modifier + .fillMaxSize(), + columns = StaggeredGridCells.Adaptive(160.dp), + contentPadding = PaddingValues(16.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalItemSpacing = 24.dp, + state = state + ) { + items(count = DEFAULT_TOOL_CARD_SKELETON_COUNT) { + ToolCardSkeleton() + } + + item(span = StaggeredGridItemSpan.FullLine) { + Spacer(Modifier.height(8.dp)) + Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.safeDrawing)) + } + } } ToolsScreenUiState.Nothing -> { @@ -254,6 +274,7 @@ private fun ToolMenu( @Composable private fun howManyTools(toolsScreenUiState: ToolsScreenUiState) = when (toolsScreenUiState) { - ToolsScreenUiState.Loading, ToolsScreenUiState.Nothing -> 0 + ToolsScreenUiState.Loading -> DEFAULT_TOOL_CARD_SKELETON_COUNT + ToolsScreenUiState.Nothing -> 0 is ToolsScreenUiState.Success -> toolsScreenUiState.tools.size } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8ec220e..16a4f82 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -33,6 +33,7 @@ androidsvg = "1.4" webviewCompose = "0.33.6" room = "2.6.1" timber = "5.0.1" +composeShimmer = "1.3.1" [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } @@ -102,3 +103,4 @@ room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" } room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" } timber = { group = "com.jakewharton.timber", name = "timber", version.ref = "timber" } +compose-shimmer = { group = "com.valentinilk.shimmer", name = "compose-shimmer", version.ref = "composeShimmer" }