mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-07-27 15:02:25 +00:00
feat: Add HomeSkeletonHero component for loading state representation in HomeScreen
This commit is contained in:
parent
3929ad32a7
commit
71d5ccbd8b
2 changed files with 148 additions and 11 deletions
|
|
@ -18,6 +18,7 @@ import com.nuvio.app.features.home.components.HomeCatalogRowSection
|
|||
import com.nuvio.app.features.home.components.HomeContinueWatchingSection
|
||||
import com.nuvio.app.features.home.components.HomeEmptyStateCard
|
||||
import com.nuvio.app.features.home.components.HomeHeroSection
|
||||
import com.nuvio.app.features.home.components.HomeSkeletonHero
|
||||
import com.nuvio.app.features.home.components.HomeSkeletonRow
|
||||
import com.nuvio.app.features.watched.WatchedRepository
|
||||
import com.nuvio.app.features.watchprogress.CurrentDateProvider
|
||||
|
|
@ -45,6 +46,7 @@ fun HomeScreen(
|
|||
|
||||
val addonsUiState by AddonRepository.uiState.collectAsStateWithLifecycle()
|
||||
val homeUiState by HomeRepository.uiState.collectAsStateWithLifecycle()
|
||||
val homeSettingsUiState by HomeCatalogSettingsRepository.uiState.collectAsStateWithLifecycle()
|
||||
val continueWatchingPreferences by ContinueWatchingPreferencesRepository.uiState.collectAsStateWithLifecycle()
|
||||
val watchedUiState by WatchedRepository.uiState.collectAsStateWithLifecycle()
|
||||
val watchProgressUiState by WatchProgressRepository.uiState.collectAsStateWithLifecycle()
|
||||
|
|
@ -125,11 +127,35 @@ fun HomeScreen(
|
|||
nextUpItemsBySeries = resolvedItems
|
||||
}
|
||||
|
||||
val hasActiveAddons = addonsUiState.addons.any { it.manifest != null }
|
||||
val homeDataResolved = !homeUiState.isLoading &&
|
||||
(homeUiState.sections.isNotEmpty() || homeUiState.errorMessage != null)
|
||||
val showHeroSkeleton = homeSettingsUiState.heroEnabled &&
|
||||
hasActiveAddons &&
|
||||
homeUiState.heroItems.isEmpty() &&
|
||||
!homeDataResolved
|
||||
|
||||
NuvioScreen(
|
||||
modifier = modifier,
|
||||
horizontalPadding = 0.dp,
|
||||
topPadding = if (homeUiState.heroItems.isNotEmpty()) 0.dp else null,
|
||||
topPadding = if (homeUiState.heroItems.isNotEmpty() || showHeroSkeleton) 0.dp else null,
|
||||
) {
|
||||
if (showHeroSkeleton) {
|
||||
item {
|
||||
HomeSkeletonHero(
|
||||
modifier = Modifier.padding(bottom = 12.dp),
|
||||
)
|
||||
}
|
||||
} else if (homeUiState.heroItems.isNotEmpty()) {
|
||||
item {
|
||||
HomeHeroSection(
|
||||
items = homeUiState.heroItems,
|
||||
modifier = Modifier.padding(bottom = 0.dp),
|
||||
onItemClick = onPosterClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
when {
|
||||
addonsUiState.addons.none { it.manifest != null } -> {
|
||||
if (continueWatchingPreferences.isVisible && continueWatchingItems.isNotEmpty()) {
|
||||
|
|
@ -182,15 +208,6 @@ fun HomeScreen(
|
|||
}
|
||||
|
||||
else -> {
|
||||
if (homeUiState.heroItems.isNotEmpty()) {
|
||||
item {
|
||||
HomeHeroSection(
|
||||
items = homeUiState.heroItems,
|
||||
modifier = Modifier.padding(bottom = 0.dp),
|
||||
onItemClick = onPosterClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
if (continueWatchingPreferences.isVisible && continueWatchingItems.isNotEmpty()) {
|
||||
item {
|
||||
HomeContinueWatchingSection(
|
||||
|
|
|
|||
|
|
@ -9,12 +9,17 @@ 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.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Column
|
||||
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.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -23,10 +28,11 @@ 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
|
||||
|
||||
@Composable
|
||||
fun HomeSkeletonRow(modifier: Modifier = Modifier) {
|
||||
private fun rememberHomeSkeletonBrush(): Brush {
|
||||
val shimmerColors = listOf(
|
||||
MaterialTheme.colorScheme.surface,
|
||||
MaterialTheme.colorScheme.surface.copy(alpha = 0.5f),
|
||||
|
|
@ -46,6 +52,94 @@ fun HomeSkeletonRow(modifier: Modifier = Modifier) {
|
|||
start = Offset(translateAnim - 200f, 0f),
|
||||
end = Offset(translateAnim, 0f),
|
||||
)
|
||||
return brush
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HomeSkeletonHero(modifier: Modifier = Modifier) {
|
||||
val brush = rememberHomeSkeletonBrush()
|
||||
|
||||
BoxWithConstraints(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(bottomStart = 28.dp, bottomEnd = 28.dp)),
|
||||
) {
|
||||
val heroHeight = (maxWidth.value * 1.22f).dp.coerceIn(440.dp, 800.dp)
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(heroHeight)
|
||||
.background(brush),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(
|
||||
Brush.verticalGradient(
|
||||
colors = listOf(
|
||||
MaterialTheme.colorScheme.background.copy(alpha = 0.04f),
|
||||
MaterialTheme.colorScheme.background.copy(alpha = 0.18f),
|
||||
MaterialTheme.colorScheme.background.copy(alpha = 0.42f),
|
||||
MaterialTheme.colorScheme.background.copy(alpha = 0.92f),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 24.dp, vertical = 24.dp),
|
||||
) {
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
SkeletonBlock(
|
||||
brush = brush,
|
||||
width = 220.dp,
|
||||
height = 64.dp,
|
||||
cornerRadius = 20.dp,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(14.dp))
|
||||
Row {
|
||||
SkeletonBlock(brush = brush, width = 72.dp, height = 14.dp, cornerRadius = 999.dp)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
SkeletonDot(brush = brush)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
SkeletonBlock(brush = brush, width = 88.dp, height = 14.dp, cornerRadius = 999.dp)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
SkeletonDot(brush = brush)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
SkeletonBlock(brush = brush, width = 54.dp, height = 14.dp, cornerRadius = 999.dp)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(18.dp))
|
||||
SkeletonBlock(
|
||||
brush = brush,
|
||||
width = 148.dp,
|
||||
height = 48.dp,
|
||||
cornerRadius = 40.dp,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Row {
|
||||
repeat(4) { index ->
|
||||
if (index > 0) {
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
}
|
||||
SkeletonBlock(
|
||||
brush = brush,
|
||||
width = if (index == 0) 32.dp else 8.dp,
|
||||
height = 8.dp,
|
||||
cornerRadius = 999.dp,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HomeSkeletonRow(modifier: Modifier = Modifier) {
|
||||
val brush = rememberHomeSkeletonBrush()
|
||||
|
||||
Column(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
|
|
@ -84,3 +178,29 @@ fun HomeSkeletonRow(modifier: Modifier = Modifier) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SkeletonBlock(
|
||||
brush: Brush,
|
||||
width: Dp,
|
||||
height: Dp,
|
||||
cornerRadius: Dp,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(width)
|
||||
.height(height)
|
||||
.clip(RoundedCornerShape(cornerRadius))
|
||||
.background(brush),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SkeletonDot(brush: Brush) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(4.dp)
|
||||
.clip(CircleShape)
|
||||
.background(brush),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue