From 88d7e22ec2182996c49cb0bdbf51b25828d94aa4 Mon Sep 17 00:00:00 2001 From: KhooLy <73142442+KhooLy@users.noreply.github.com> Date: Fri, 3 Jul 2026 12:41:07 +0300 Subject: [PATCH] Add skeleton loading rows to home screens on both platforms While the home catalog is loading and no rows are available yet, both the mobile and TV home screens now show 4 animated skeleton shelf placeholders instead of a blank screen. The pulse animation matches the explore screen skeleton. --- .../app/ui/catalog/TvHomeShelfComponents.kt | 43 +++++++++++++++++ .../com/fluxa/app/ui/catalog/HomeScreen.kt | 6 +++ .../app/ui/catalog/MobileHomeSections.kt | 48 +++++++++++++++++++ .../com/fluxa/app/ui/catalog/HomeScreen.kt | 6 +++ 4 files changed, 103 insertions(+) diff --git a/app/src/main/java/com/fluxa/app/ui/catalog/TvHomeShelfComponents.kt b/app/src/main/java/com/fluxa/app/ui/catalog/TvHomeShelfComponents.kt index dbb22d5..1547d00 100644 --- a/app/src/main/java/com/fluxa/app/ui/catalog/TvHomeShelfComponents.kt +++ b/app/src/main/java/com/fluxa/app/ui/catalog/TvHomeShelfComponents.kt @@ -6,6 +6,11 @@ import com.fluxa.app.common.AppStrings import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState +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.snap import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn @@ -23,6 +28,7 @@ import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width @@ -386,6 +392,43 @@ private fun ShelfExpandedDescription(expandedMeta: Meta?, expandedOffsetX: Dp, r } } +@Composable +internal fun TvHomeShelfSkeleton(titleStartPadding: Dp) { + val transition = rememberInfiniteTransition(label = "shelf-skeleton") + val alpha by transition.animateFloat( + initialValue = 0.05f, + targetValue = 0.13f, + animationSpec = infiniteRepeatable( + animation = tween(FluxaDimensions.AnimDuration.ambientColor, easing = LinearEasing), + repeatMode = RepeatMode.Reverse + ), + label = "shelf-skeleton-alpha" + ) + Column(modifier = Modifier.padding(bottom = 28.dp)) { + Box( + modifier = Modifier + .padding(start = titleStartPadding, bottom = 12.dp) + .width(140.dp) + .height(18.dp) + .clip(androidx.compose.foundation.shape.RoundedCornerShape(5.dp)) + .background(Color.White.copy(alpha = alpha)) + ) + LazyRow( + horizontalArrangement = Arrangement.spacedBy(12.dp), + contentPadding = PaddingValues(horizontal = titleStartPadding) + ) { + items(5) { + Box( + modifier = Modifier + .size(200.dp, 120.dp) + .clip(androidx.compose.foundation.shape.RoundedCornerShape(10.dp)) + .background(Color.White.copy(alpha = alpha)) + ) + } + } + } +} + internal fun Meta.remainingWatchLabel(lang: String? = null): String? { val total = duration ?: return null val watched = timeOffset ?: return null diff --git a/app/src/mobile/java/com/fluxa/app/ui/catalog/HomeScreen.kt b/app/src/mobile/java/com/fluxa/app/ui/catalog/HomeScreen.kt index ce5dea5..4a0cce1 100644 --- a/app/src/mobile/java/com/fluxa/app/ui/catalog/HomeScreen.kt +++ b/app/src/mobile/java/com/fluxa/app/ui/catalog/HomeScreen.kt @@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.lazy.LazyListPrefetchStrategy import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.lazy.items import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.Immutable @@ -95,6 +96,11 @@ fun HomeScreen( modifier = Modifier.fillMaxSize(), contentPadding = PaddingValues(bottom = 120.dp) ) { + if (catalogState.isLoading && homeRowSpecs.isEmpty()) { + items(4, key = { "skeleton-$it" }, contentType = { "skeleton" }) { + MobileHomeShelfSkeleton(horizontalPadding) + } + } if (catalogState.showHeroSection) { item(key = "home-hero", contentType = "hero") { MobileHomeHeroItem( diff --git a/app/src/mobile/java/com/fluxa/app/ui/catalog/MobileHomeSections.kt b/app/src/mobile/java/com/fluxa/app/ui/catalog/MobileHomeSections.kt index 3b6a9f0..cb0704f 100644 --- a/app/src/mobile/java/com/fluxa/app/ui/catalog/MobileHomeSections.kt +++ b/app/src/mobile/java/com/fluxa/app/ui/catalog/MobileHomeSections.kt @@ -4,9 +4,17 @@ package com.fluxa.app.ui.catalog import com.fluxa.app.common.AppStrings +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.clickable import androidx.compose.foundation.interaction.MutableInteractionSource 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 @@ -14,9 +22,12 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.offset 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.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.runtime.getValue import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable @@ -469,3 +480,40 @@ private fun HomeRowHeader( } } } + +@Composable +internal fun MobileHomeShelfSkeleton(horizontalPadding: androidx.compose.ui.unit.Dp) { + val transition = rememberInfiniteTransition(label = "shelf-skeleton") + val alpha by transition.animateFloat( + initialValue = 0.05f, + targetValue = 0.13f, + animationSpec = infiniteRepeatable( + animation = tween(FluxaDimensions.AnimDuration.ambientColor, easing = LinearEasing), + repeatMode = RepeatMode.Reverse + ), + label = "shelf-skeleton-alpha" + ) + Column(modifier = Modifier.padding(bottom = 24.dp)) { + Box( + modifier = Modifier + .padding(start = horizontalPadding, bottom = 10.dp) + .width(120.dp) + .height(16.dp) + .clip(RoundedCornerShape(5.dp)) + .background(Color.White.copy(alpha = alpha)) + ) + LazyRow( + horizontalArrangement = Arrangement.spacedBy(10.dp), + contentPadding = PaddingValues(horizontal = horizontalPadding) + ) { + items(5) { + Box( + modifier = Modifier + .size(130.dp, 195.dp) + .clip(RoundedCornerShape(8.dp)) + .background(Color.White.copy(alpha = alpha)) + ) + } + } + } +} diff --git a/app/src/tv/java/com/fluxa/app/ui/catalog/HomeScreen.kt b/app/src/tv/java/com/fluxa/app/ui/catalog/HomeScreen.kt index 4245e35..8d0e407 100644 --- a/app/src/tv/java/com/fluxa/app/ui/catalog/HomeScreen.kt +++ b/app/src/tv/java/com/fluxa/app/ui/catalog/HomeScreen.kt @@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.LazyListPrefetchStrategy import androidx.compose.foundation.lazy.rememberLazyListState @@ -228,6 +229,11 @@ fun HomeScreen( } } + if (catalogState.isLoading && rowSpecs.isEmpty()) { + items(4, key = { "skeleton-$it" }, contentType = { "skeleton" }) { + TvHomeShelfSkeleton(titleStartPadding = railGutter) + } + } itemsIndexed( rowSpecs, key = { _, row -> row.categoryId },