mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-08-17 21:05:43 +00:00
making headers sticky
This commit is contained in:
parent
dde6b2212f
commit
3bda11e6fc
8 changed files with 117 additions and 47 deletions
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ fun AddonsScreen(
|
|||
val overview = remember(sortedAddons) { sortedAddons.toOverview() }
|
||||
|
||||
NuvioScreen(modifier = modifier) {
|
||||
item {
|
||||
stickyHeader {
|
||||
NuvioScreenHeader(
|
||||
title = "Addons",
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue