making headers sticky

This commit is contained in:
tapframe 2026-03-11 21:05:12 +05:30
parent dde6b2212f
commit 3bda11e6fc
8 changed files with 117 additions and 47 deletions

View file

@ -1,5 +1,6 @@
package com.nuvio.app
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Extension
@ -74,6 +75,7 @@ fun App() {
Scaffold(
containerColor = MaterialTheme.colorScheme.background,
contentWindowInsets = WindowInsets(0),
bottomBar = {
if (currentRoute == TabsRoute::class.qualifiedName) {
NavigationBar(

View file

@ -10,10 +10,13 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
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.statusBars
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
@ -45,17 +48,18 @@ fun NuvioScreen(
modifier: Modifier = Modifier,
content: LazyListScope.() -> Unit,
) {
val statusBarTop = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
LazyColumn(
modifier = modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background),
contentPadding = PaddingValues(
start = 18.dp,
top = 12.dp + nuvioPlatformExtraTopPadding,
end = 18.dp,
bottom = 22.dp + nuvioPlatformExtraBottomPadding,
start = 16.dp,
top = 10.dp + statusBarTop + nuvioPlatformExtraTopPadding,
end = 16.dp,
bottom = 18.dp + nuvioPlatformExtraBottomPadding,
),
verticalArrangement = Arrangement.spacedBy(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
content = content,
)
}
@ -86,8 +90,12 @@ fun NuvioScreenHeader(
modifier: Modifier = Modifier,
actions: @Composable RowScope.() -> Unit = {},
) {
val statusBarTop = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
Row(
modifier = modifier.fillMaxWidth(),
modifier = modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.background)
.padding(top = statusBarTop, bottom = 4.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.Bottom,
) {

View file

@ -74,7 +74,7 @@ fun AddonsScreen(
val overview = remember(sortedAddons) { sortedAddons.toOverview() }
NuvioScreen(modifier = modifier) {
item {
stickyHeader {
NuvioScreenHeader(
title = "Addons",
) {

View file

@ -8,12 +8,11 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.nuvio.app.core.ui.NuvioIconActionButton
import com.nuvio.app.core.ui.NuvioScreen
import com.nuvio.app.core.ui.NuvioScreenHeader
import com.nuvio.app.features.addons.AddonRepository
import com.nuvio.app.features.home.components.HomeCatalogRowSection
import com.nuvio.app.features.home.components.HomeEmptyStateCard
import com.nuvio.app.features.home.components.HomeSkeletonRow
@Composable
fun HomeScreen(
@ -45,18 +44,6 @@ fun HomeScreen(
}
NuvioScreen(modifier = modifier) {
item {
NuvioScreenHeader(
title = "Home",
) {
NuvioIconActionButton(
icon = Icons.Rounded.Refresh,
contentDescription = "Refresh catalog rows",
onClick = { HomeRepository.refresh(addonsUiState.addons, force = true) },
)
}
}
when {
addonsUiState.addons.none { it.manifest != null } -> {
item {
@ -68,11 +55,8 @@ fun HomeScreen(
}
homeUiState.isLoading && homeUiState.sections.isEmpty() -> {
item {
HomeEmptyStateCard(
title = "Loading catalogs",
message = "Pulling feed-compatible catalogs from your installed addons.",
)
items(3) {
HomeSkeletonRow()
}
}

View file

@ -21,15 +21,14 @@ fun HomeCatalogRowSection(
) {
Column(
modifier = modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(16.dp),
verticalArrangement = Arrangement.spacedBy(10.dp),
) {
HomeCatalogSectionHeader(
title = section.title,
subtitle = section.subtitle,
onViewAllClick = onViewAllClick,
)
LazyRow(
horizontalArrangement = Arrangement.spacedBy(14.dp),
horizontalArrangement = Arrangement.spacedBy(10.dp),
) {
items(
items = section.items,

View file

@ -25,7 +25,6 @@ import androidx.compose.ui.unit.dp
@Composable
fun HomeCatalogSectionHeader(
title: String,
subtitle: String,
modifier: Modifier = Modifier,
onViewAllClick: (() -> Unit)? = null,
) {
@ -39,29 +38,21 @@ fun HomeCatalogSectionHeader(
) {
Text(
text = title,
style = MaterialTheme.typography.headlineLarge,
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.onBackground,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Box(
modifier = Modifier
.padding(top = 8.dp)
.width(82.dp)
.height(6.dp)
.padding(top = 6.dp)
.width(60.dp)
.height(4.dp)
.background(
color = MaterialTheme.colorScheme.primary,
shape = RoundedCornerShape(999.dp),
),
)
Text(
text = subtitle,
modifier = Modifier.padding(top = 10.dp),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
ViewAllPill(
onClick = onViewAllClick,

View file

@ -33,15 +33,15 @@ fun HomePosterCard(
) {
Column(
modifier = modifier
.width(142.dp)
.width(110.dp)
.then(if (onClick != null) Modifier.clickable(onClick = onClick) else Modifier),
verticalArrangement = Arrangement.spacedBy(10.dp),
verticalArrangement = Arrangement.spacedBy(6.dp),
) {
Box(
modifier = Modifier
.fillMaxWidth()
.aspectRatio(item.posterShape.aspectRatio)
.clip(RoundedCornerShape(22.dp))
.clip(RoundedCornerShape(16.dp))
.background(MaterialTheme.colorScheme.surface),
contentAlignment = Alignment.Center,
) {
@ -66,7 +66,7 @@ fun HomePosterCard(
}
Text(
text = item.name,
style = MaterialTheme.typography.titleMedium,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
@ -75,7 +75,7 @@ fun HomePosterCard(
if (detailLine.isNotBlank()) {
Text(
text = detailLine,
style = MaterialTheme.typography.bodyMedium,
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 1,
overflow = TextOverflow.Ellipsis,

View file

@ -0,0 +1,86 @@
package com.nuvio.app.features.home.components
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.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
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
@Composable
fun HomeSkeletonRow(modifier: Modifier = Modifier) {
val shimmerColors = listOf(
MaterialTheme.colorScheme.surface,
MaterialTheme.colorScheme.surface.copy(alpha = 0.5f),
MaterialTheme.colorScheme.surface,
)
val transition = rememberInfiniteTransition()
val translateAnim by transition.animateFloat(
initialValue = 0f,
targetValue = 1000f,
animationSpec = infiniteRepeatable(
animation = tween(durationMillis = 1200, easing = LinearEasing),
repeatMode = RepeatMode.Restart,
),
)
val brush = Brush.linearGradient(
colors = shimmerColors,
start = Offset(translateAnim - 200f, 0f),
end = Offset(translateAnim, 0f),
)
Column(
modifier = modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(10.dp),
) {
// Title placeholder
Box(
modifier = Modifier
.width(140.dp)
.height(18.dp)
.clip(RoundedCornerShape(6.dp))
.background(brush),
)
// Accent bar
Box(
modifier = Modifier
.width(60.dp)
.height(4.dp)
.clip(RoundedCornerShape(999.dp))
.background(brush),
)
Spacer(modifier = Modifier.height(2.dp))
// Poster row
Row(
horizontalArrangement = Arrangement.spacedBy(10.dp),
) {
repeat(4) {
Box(
modifier = Modifier
.width(110.dp)
.height(163.dp)
.clip(RoundedCornerShape(16.dp))
.background(brush),
)
}
}
}
}