mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-08-09 00:17:59 +00:00
ref: design tokens
This commit is contained in:
parent
09cb8f8277
commit
8ecb392c74
28 changed files with 1395 additions and 754 deletions
|
|
@ -188,6 +188,17 @@ val iosDistributionSourceDir = if (iosDistribution == "full") {
|
|||
val iosFrameworkBundleId = "com.nuvio.media"
|
||||
val fullCommonSourceDir = project.file("src/fullCommonMain/kotlin")
|
||||
val generatedRuntimeConfigDir = layout.buildDirectory.dir("generated/runtime-config/kotlin")
|
||||
val requestedGradleTasks = gradle.startParameter.taskNames.map { taskName ->
|
||||
taskName.substringAfterLast(':').lowercase()
|
||||
}
|
||||
val isAndroidAppBundleBuild = requestedGradleTasks.any { taskName ->
|
||||
taskName == "bundle" ||
|
||||
taskName == "bundlerelease" ||
|
||||
taskName == "bundledebug" ||
|
||||
taskName.startsWith("bundleplaystore") ||
|
||||
taskName.startsWith("bundlefull") ||
|
||||
taskName.endsWith("bundle")
|
||||
}
|
||||
|
||||
val generateRuntimeConfigs = tasks.register<GenerateRuntimeConfigsTask>("generateRuntimeConfigs") {
|
||||
outputDir.set(generatedRuntimeConfigDir)
|
||||
|
|
@ -352,7 +363,7 @@ android {
|
|||
}
|
||||
splits {
|
||||
abi {
|
||||
isEnable = true
|
||||
isEnable = !isAndroidAppBundleBuild
|
||||
reset()
|
||||
include("arm64-v8a", "armeabi-v7a", "x86", "x86_64")
|
||||
isUniversalApk = false
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="nuvio_background">#020404</color>
|
||||
<color name="nuvio_background">#0D0D0D</color>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -96,11 +96,13 @@ import com.nuvio.app.core.ui.NuvioToastController
|
|||
import com.nuvio.app.core.ui.NuvioFloatingPrompt
|
||||
import com.nuvio.app.core.ui.TraktListPickerDialog
|
||||
import com.nuvio.app.core.ui.NuvioTheme
|
||||
import com.nuvio.app.core.ui.NuvioTokens
|
||||
import com.nuvio.app.core.ui.LocalNuvioBottomNavigationOverlayPadding
|
||||
import com.nuvio.app.core.ui.NativeNavigationTab
|
||||
import com.nuvio.app.core.ui.NativeTabBridge
|
||||
import com.nuvio.app.core.ui.isLiquidGlassNativeTabBarSupported
|
||||
import com.nuvio.app.core.ui.localizedContinueWatchingSubtitle
|
||||
import com.nuvio.app.core.ui.nuvio
|
||||
import com.nuvio.app.features.auth.AuthScreen
|
||||
import com.nuvio.app.features.addons.AddonRepository
|
||||
import com.nuvio.app.features.catalog.CatalogRepository
|
||||
|
|
@ -547,10 +549,10 @@ fun App() {
|
|||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.background),
|
||||
.background(MaterialTheme.nuvio.colors.background),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
CircularProgressIndicator(color = MaterialTheme.colorScheme.primary)
|
||||
CircularProgressIndicator(color = MaterialTheme.nuvio.colors.accent)
|
||||
}
|
||||
}
|
||||
AppGateScreen.Auth.name -> {
|
||||
|
|
@ -1298,7 +1300,7 @@ private fun MainAppContent(
|
|||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.background),
|
||||
.background(MaterialTheme.nuvio.colors.background),
|
||||
) {
|
||||
SharedTransitionLayout {
|
||||
NavHost(
|
||||
|
|
@ -2042,7 +2044,7 @@ private fun MainAppContent(
|
|||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
CircularProgressIndicator(color = MaterialTheme.colorScheme.primary)
|
||||
CircularProgressIndicator(color = MaterialTheme.nuvio.colors.accent)
|
||||
}
|
||||
return@composable
|
||||
}
|
||||
|
|
@ -2235,17 +2237,17 @@ private fun MainAppContent(
|
|||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color.Black.copy(alpha = 0.82f)),
|
||||
.background(MaterialTheme.nuvio.colors.overlayScrim.copy(alpha = MaterialTheme.nuvio.opacity.overlayHeavy)),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(MaterialTheme.nuvio.spacing.cardPadding),
|
||||
) {
|
||||
CircularProgressIndicator(color = Color.White)
|
||||
CircularProgressIndicator(color = MaterialTheme.nuvio.colors.playerControlsForeground)
|
||||
Text(
|
||||
text = stringResource(Res.string.streams_finding_source),
|
||||
color = Color.White.copy(alpha = 0.82f),
|
||||
color = MaterialTheme.nuvio.colors.playerControlsForeground.copy(alpha = MaterialTheme.nuvio.opacity.overlayHeavy),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
|
|
@ -2907,23 +2909,24 @@ private fun TabletFloatingTopBar(
|
|||
onAddProfileRequested: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
val statusBarPadding = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = statusBarPadding + 10.dp, bottom = 8.dp),
|
||||
.padding(top = statusBarPadding + NuvioTokens.Space.s10, bottom = tokens.spacing.controlGap),
|
||||
contentAlignment = Alignment.TopCenter,
|
||||
) {
|
||||
Surface(
|
||||
color = MaterialTheme.colorScheme.surface.copy(alpha = 0.96f),
|
||||
shape = RoundedCornerShape(999.dp),
|
||||
tonalElevation = 4.dp,
|
||||
shadowElevation = 10.dp,
|
||||
color = tokens.colors.surface.copy(alpha = tokens.opacity.visible - tokens.opacity.subtle),
|
||||
shape = tokens.shapes.chip,
|
||||
tonalElevation = tokens.elevation.playerControls,
|
||||
shadowElevation = tokens.elevation.overlay,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(horizontal = 10.dp, vertical = 8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.padding(horizontal = NuvioTokens.Space.s10, vertical = tokens.spacing.controlGap),
|
||||
horizontalArrangement = Arrangement.spacedBy(tokens.spacing.controlGap),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
TabletTopPillItem(
|
||||
|
|
@ -2934,11 +2937,11 @@ private fun TabletFloatingTopBar(
|
|||
Icon(
|
||||
imageVector = Icons.Filled.Home,
|
||||
contentDescription = stringResource(Res.string.compose_nav_home),
|
||||
modifier = Modifier.size(18.dp),
|
||||
modifier = Modifier.size(NuvioTokens.Space.s18),
|
||||
tint = if (selectedTab == AppScreenTab.Home) {
|
||||
MaterialTheme.colorScheme.onPrimaryContainer
|
||||
tokens.colors.textPrimary
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onSurfaceVariant
|
||||
tokens.colors.textMuted
|
||||
},
|
||||
)
|
||||
},
|
||||
|
|
@ -2951,11 +2954,11 @@ private fun TabletFloatingTopBar(
|
|||
Icon(
|
||||
painter = painterResource(Res.drawable.sidebar_search),
|
||||
contentDescription = stringResource(Res.string.compose_nav_search),
|
||||
modifier = Modifier.size(18.dp),
|
||||
modifier = Modifier.size(NuvioTokens.Space.s18),
|
||||
tint = if (selectedTab == AppScreenTab.Search) {
|
||||
MaterialTheme.colorScheme.onPrimaryContainer
|
||||
tokens.colors.textPrimary
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onSurfaceVariant
|
||||
tokens.colors.textMuted
|
||||
},
|
||||
)
|
||||
},
|
||||
|
|
@ -2968,26 +2971,26 @@ private fun TabletFloatingTopBar(
|
|||
Icon(
|
||||
painter = painterResource(Res.drawable.sidebar_library),
|
||||
contentDescription = stringResource(Res.string.compose_nav_library),
|
||||
modifier = Modifier.size(18.dp),
|
||||
modifier = Modifier.size(NuvioTokens.Space.s18),
|
||||
tint = if (selectedTab == AppScreenTab.Library) {
|
||||
MaterialTheme.colorScheme.onPrimaryContainer
|
||||
tokens.colors.textPrimary
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onSurfaceVariant
|
||||
tokens.colors.textMuted
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
Surface(
|
||||
color = if (selectedTab == AppScreenTab.Settings) {
|
||||
MaterialTheme.colorScheme.primaryContainer
|
||||
tokens.colors.overlaySelected
|
||||
} else {
|
||||
MaterialTheme.colorScheme.surface
|
||||
tokens.colors.surface
|
||||
},
|
||||
shape = RoundedCornerShape(999.dp),
|
||||
shape = tokens.shapes.chip,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.padding(horizontal = tokens.spacing.listGap, vertical = tokens.spacing.controlGap),
|
||||
horizontalArrangement = Arrangement.spacedBy(tokens.spacing.controlGap),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
ProfileSwitcherTab(
|
||||
|
|
@ -3001,9 +3004,9 @@ private fun TabletFloatingTopBar(
|
|||
modifier = Modifier.clickable { onTabSelected(AppScreenTab.Settings) },
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = if (selectedTab == AppScreenTab.Settings) {
|
||||
MaterialTheme.colorScheme.onPrimaryContainer
|
||||
tokens.colors.textPrimary
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onSurfaceVariant
|
||||
tokens.colors.textMuted
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -3023,15 +3026,16 @@ private fun TabletTopPillItem(
|
|||
onClick: () -> Unit,
|
||||
icon: @Composable () -> Unit,
|
||||
) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
Surface(
|
||||
color = if (selected) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.surface,
|
||||
shape = RoundedCornerShape(999.dp),
|
||||
tonalElevation = if (selected) 2.dp else 0.dp,
|
||||
color = if (selected) tokens.colors.overlaySelected else tokens.colors.surface,
|
||||
shape = tokens.shapes.chip,
|
||||
tonalElevation = if (selected) tokens.elevation.raised else tokens.elevation.flat,
|
||||
modifier = Modifier.clickable(onClick = onClick),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(horizontal = 14.dp, vertical = 10.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.padding(horizontal = tokens.components.chipHorizontalPadding, vertical = NuvioTokens.Space.s10),
|
||||
horizontalArrangement = Arrangement.spacedBy(tokens.spacing.controlGap),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
icon()
|
||||
|
|
@ -3039,9 +3043,9 @@ private fun TabletTopPillItem(
|
|||
text = label,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = if (selected) {
|
||||
MaterialTheme.colorScheme.onPrimaryContainer
|
||||
tokens.colors.textPrimary
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onSurfaceVariant
|
||||
tokens.colors.textMuted
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -3052,10 +3056,11 @@ private fun TabletTopPillItem(
|
|||
private fun AppLaunchOverlay(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
Box(
|
||||
modifier = modifier
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
.zIndex(10f),
|
||||
.background(tokens.colors.background)
|
||||
.zIndex(NuvioTokens.Z.dialog),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Column(
|
||||
|
|
@ -3069,8 +3074,8 @@ private fun AppLaunchOverlay(
|
|||
.height(44.dp),
|
||||
contentScale = ContentScale.Fit,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
CircularProgressIndicator(color = MaterialTheme.colorScheme.primary)
|
||||
Spacer(modifier = Modifier.height(tokens.spacing.sectionGap))
|
||||
CircularProgressIndicator(color = tokens.colors.accent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ import androidx.compose.ui.graphics.Color
|
|||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
|
|
@ -34,9 +33,9 @@ fun NuvioModalBottomSheet(
|
|||
onDismissRequest: () -> Unit,
|
||||
sheetState: SheetState,
|
||||
modifier: Modifier = Modifier,
|
||||
containerColor: Color = MaterialTheme.colorScheme.surface,
|
||||
contentColor: Color = MaterialTheme.colorScheme.onSurface,
|
||||
shape: Shape = RoundedCornerShape(topStart = 28.dp, topEnd = 28.dp),
|
||||
containerColor: Color = MaterialTheme.nuvio.colors.surfaceSheet,
|
||||
contentColor: Color = MaterialTheme.nuvio.colors.textPrimary,
|
||||
shape: Shape = RoundedCornerShape(topStart = NuvioTokens.Space.s28, topEnd = NuvioTokens.Space.s28),
|
||||
showDragHandle: Boolean = true,
|
||||
content: @Composable ColumnScope.() -> Unit,
|
||||
) {
|
||||
|
|
@ -62,7 +61,7 @@ fun NuvioBottomSheetDivider(
|
|||
) {
|
||||
HorizontalDivider(
|
||||
modifier = modifier,
|
||||
color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.6f),
|
||||
color = MaterialTheme.nuvio.colors.borderSubtle,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -74,27 +73,28 @@ fun NuvioBottomSheetActionRow(
|
|||
icon: ImageVector? = null,
|
||||
trailingContent: (@Composable RowScope.() -> Unit)? = null,
|
||||
) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = onClick)
|
||||
.padding(horizontal = 16.dp, vertical = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(14.dp),
|
||||
.padding(horizontal = tokens.spacing.screenHorizontal, vertical = tokens.spacing.screenHorizontal),
|
||||
horizontalArrangement = Arrangement.spacedBy(NuvioTokens.Space.s14),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
if (icon != null) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.size(22.dp),
|
||||
tint = tokens.colors.accent,
|
||||
modifier = Modifier.size(NuvioTokens.Icon.md),
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = title,
|
||||
modifier = Modifier.weight(1f),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
color = tokens.colors.textPrimary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
|
|
@ -115,11 +115,12 @@ suspend fun dismissNuvioBottomSheet(
|
|||
|
||||
@Composable
|
||||
private fun NuvioBottomSheetDragHandle() {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(top = 10.dp, bottom = 6.dp)
|
||||
.size(width = 54.dp, height = 5.dp)
|
||||
.clip(RoundedCornerShape(999.dp))
|
||||
.background(MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.65f)),
|
||||
.padding(top = NuvioTokens.Space.s10, bottom = NuvioTokens.Space.s6)
|
||||
.size(width = NuvioTokens.Space.s56 - NuvioTokens.Space.s2, height = NuvioTokens.Space.s5)
|
||||
.clip(tokens.shapes.chip)
|
||||
.background(tokens.colors.borderDefault),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ import androidx.compose.foundation.lazy.LazyColumn
|
|||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.BasicAlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
|
|
@ -78,24 +77,25 @@ import kotlinx.coroutines.flow.asStateFlow
|
|||
@Composable
|
||||
fun NuvioScreen(
|
||||
modifier: Modifier = Modifier,
|
||||
horizontalPadding: Dp = 16.dp,
|
||||
horizontalPadding: Dp = MaterialTheme.nuvio.spacing.screenHorizontal,
|
||||
topPadding: Dp? = null,
|
||||
listState: LazyListState = rememberLazyListState(),
|
||||
content: LazyListScope.() -> Unit,
|
||||
) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
val statusBarTop = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.background),
|
||||
.background(tokens.colors.background),
|
||||
contentPadding = PaddingValues(
|
||||
start = horizontalPadding,
|
||||
top = topPadding ?: 10.dp + statusBarTop + nuvioPlatformExtraTopPadding,
|
||||
top = topPadding ?: tokens.spacing.screenTop + statusBarTop + nuvioPlatformExtraTopPadding,
|
||||
end = horizontalPadding,
|
||||
bottom = nuvioSafeBottomPadding(18.dp),
|
||||
bottom = nuvioSafeBottomPadding(tokens.spacing.screenBottom),
|
||||
),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(tokens.spacing.listGap),
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
|
@ -117,15 +117,16 @@ fun NuvioSurfaceCard(
|
|||
tonalElevation: Int = 0,
|
||||
content: @Composable ColumnScope.() -> Unit,
|
||||
) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
Surface(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
color = MaterialTheme.colorScheme.surface,
|
||||
shape = RoundedCornerShape(24.dp),
|
||||
color = tokens.colors.surface,
|
||||
shape = tokens.shapes.card,
|
||||
tonalElevation = tonalElevation.dp,
|
||||
shadowElevation = 0.dp,
|
||||
shadowElevation = tokens.elevation.flat,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(horizontal = 18.dp, vertical = 18.dp),
|
||||
modifier = Modifier.padding(tokens.spacing.cardPadding),
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
|
@ -140,27 +141,28 @@ fun NuvioScreenHeader(
|
|||
onBack: (() -> Unit)? = null,
|
||||
actions: @Composable RowScope.() -> Unit = {},
|
||||
) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
val statusBarTop = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
|
||||
val resolvedTopPadding = topPadding ?: if (includeStatusBarPadding) statusBarTop else 0.dp
|
||||
val resolvedTopPadding = topPadding ?: if (includeStatusBarPadding) statusBarTop else NuvioTokens.Space.none
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.nuvioBlockPointerPassthrough()
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
.padding(top = resolvedTopPadding, bottom = 4.dp),
|
||||
.background(tokens.colors.background)
|
||||
.padding(top = resolvedTopPadding, bottom = NuvioTokens.Space.s4),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.Bottom,
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(tokens.spacing.controlGap),
|
||||
) {
|
||||
if (onBack != null) {
|
||||
IconButton(onClick = onBack) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Rounded.ArrowBack,
|
||||
contentDescription = stringResource(Res.string.action_back),
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
tint = tokens.colors.textPrimary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -172,12 +174,12 @@ fun NuvioScreenHeader(
|
|||
Text(
|
||||
text = currentTitle,
|
||||
style = MaterialTheme.typography.displayLarge,
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
color = tokens.colors.textPrimary,
|
||||
)
|
||||
}
|
||||
}
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(2.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(NuvioTokens.Space.s2),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
content = actions,
|
||||
)
|
||||
|
|
@ -193,7 +195,7 @@ fun NuvioSectionLabel(
|
|||
text = text,
|
||||
modifier = modifier,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
color = MaterialTheme.nuvio.colors.textMuted,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
}
|
||||
|
|
@ -214,7 +216,7 @@ fun NuvioActionLabel(
|
|||
}
|
||||
),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
color = MaterialTheme.nuvio.colors.accent,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -223,14 +225,15 @@ fun NuvioIconActionButton(
|
|||
icon: ImageVector,
|
||||
contentDescription: String,
|
||||
modifier: Modifier = Modifier,
|
||||
tint: Color = MaterialTheme.colorScheme.onSurface,
|
||||
tint: Color = MaterialTheme.nuvio.colors.textPrimary,
|
||||
onClick: () -> Unit = {},
|
||||
) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
IconButton(
|
||||
modifier = modifier
|
||||
.background(
|
||||
color = MaterialTheme.colorScheme.background.copy(alpha = 0.001f),
|
||||
shape = CircleShape,
|
||||
color = tokens.colors.background.copy(alpha = 0.001f),
|
||||
shape = tokens.shapes.avatar,
|
||||
),
|
||||
onClick = onClick,
|
||||
) {
|
||||
|
|
@ -246,11 +249,11 @@ fun NuvioIconActionButton(
|
|||
fun NuvioBackButton(
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
shape: Shape = CircleShape,
|
||||
containerColor: Color = MaterialTheme.colorScheme.surface,
|
||||
contentColor: Color = MaterialTheme.colorScheme.onSurface,
|
||||
buttonSize: Dp = 40.dp,
|
||||
iconSize: Dp = 22.dp,
|
||||
shape: Shape = MaterialTheme.nuvio.shapes.avatar,
|
||||
containerColor: Color = MaterialTheme.nuvio.colors.surface,
|
||||
contentColor: Color = MaterialTheme.nuvio.colors.textPrimary,
|
||||
buttonSize: Dp = NuvioTokens.Space.s40,
|
||||
iconSize: Dp = NuvioTokens.Icon.md,
|
||||
contentDescription: String = stringResource(Res.string.action_back),
|
||||
) {
|
||||
Box(
|
||||
|
|
@ -277,18 +280,19 @@ fun NuvioPrimaryButton(
|
|||
enabled: Boolean = true,
|
||||
onClick: () -> Unit = {},
|
||||
) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
Button(
|
||||
onClick = onClick,
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.height(52.dp),
|
||||
.height(NuvioTokens.Space.s48 + NuvioTokens.Space.s4),
|
||||
enabled = enabled,
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
shape = tokens.shapes.button,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = MaterialTheme.colorScheme.primary,
|
||||
contentColor = MaterialTheme.colorScheme.onPrimary,
|
||||
disabledContainerColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.65f),
|
||||
disabledContentColor = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.65f),
|
||||
containerColor = tokens.colors.accent,
|
||||
contentColor = tokens.colors.onAccent,
|
||||
disabledContainerColor = tokens.colors.accent.copy(alpha = tokens.opacity.disabled),
|
||||
disabledContentColor = tokens.colors.onAccent.copy(alpha = tokens.opacity.disabled),
|
||||
),
|
||||
) {
|
||||
AnimatedContent(
|
||||
|
|
@ -313,27 +317,28 @@ fun NuvioInputField(
|
|||
modifier: Modifier = Modifier,
|
||||
trailingContent: (@Composable (() -> Unit))? = null,
|
||||
) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
OutlinedTextField(
|
||||
value = value,
|
||||
onValueChange = onValueChange,
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
singleLine = true,
|
||||
shape = RoundedCornerShape(14.dp),
|
||||
shape = RoundedCornerShape(NuvioTokens.Radius.lg),
|
||||
placeholder = {
|
||||
Text(
|
||||
text = placeholder,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
color = tokens.colors.textMuted,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
)
|
||||
},
|
||||
textStyle = MaterialTheme.typography.bodyLarge.copy(color = MaterialTheme.colorScheme.onSurface),
|
||||
textStyle = MaterialTheme.typography.bodyLarge.copy(color = tokens.colors.textPrimary),
|
||||
trailingIcon = trailingContent,
|
||||
colors = OutlinedTextFieldDefaults.colors(
|
||||
focusedBorderColor = MaterialTheme.colorScheme.outline,
|
||||
unfocusedBorderColor = MaterialTheme.colorScheme.outline,
|
||||
focusedContainerColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||
unfocusedContainerColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||
cursorColor = MaterialTheme.colorScheme.primary,
|
||||
focusedBorderColor = tokens.colors.borderFocus,
|
||||
unfocusedBorderColor = tokens.colors.borderDefault,
|
||||
focusedContainerColor = tokens.colors.surfaceCard,
|
||||
unfocusedContainerColor = tokens.colors.surfaceCard,
|
||||
cursorColor = tokens.colors.accent,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -343,18 +348,19 @@ fun NuvioInfoBadge(
|
|||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
Box(
|
||||
modifier = modifier
|
||||
.background(
|
||||
color = MaterialTheme.colorScheme.surfaceVariant,
|
||||
shape = RoundedCornerShape(999.dp),
|
||||
color = tokens.colors.surfaceCard,
|
||||
shape = tokens.shapes.chip,
|
||||
)
|
||||
.padding(horizontal = 10.dp, vertical = 6.dp),
|
||||
.padding(horizontal = NuvioTokens.Space.s10, vertical = NuvioTokens.Space.s6),
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
color = tokens.colors.textMuted,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
|
|
@ -374,13 +380,13 @@ fun NuvioInlineMetadata(
|
|||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
color = MaterialTheme.nuvio.colors.textMuted,
|
||||
)
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
Spacer(modifier = Modifier.width(NuvioTokens.Space.s6))
|
||||
Text(
|
||||
text = value,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
color = MaterialTheme.nuvio.colors.textPrimary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -399,6 +405,7 @@ fun NuvioStatusModal(
|
|||
onDismiss: (() -> Unit)? = null,
|
||||
) {
|
||||
if (!isVisible) return
|
||||
val tokens = MaterialTheme.nuvio
|
||||
|
||||
BasicAlertDialog(
|
||||
onDismissRequest = {
|
||||
|
|
@ -409,31 +416,31 @@ fun NuvioStatusModal(
|
|||
) {
|
||||
Surface(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
color = MaterialTheme.colorScheme.surface,
|
||||
shape = RoundedCornerShape(24.dp),
|
||||
color = tokens.colors.surfaceDialog,
|
||||
shape = tokens.shapes.dialog,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(20.dp),
|
||||
modifier = Modifier.padding(tokens.spacing.dialogPadding),
|
||||
) {
|
||||
if (isBusy) {
|
||||
CircularProgressIndicator(
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
strokeWidth = 2.5.dp,
|
||||
color = tokens.colors.accent,
|
||||
strokeWidth = NuvioTokens.Border.medium + NuvioTokens.Space.hairline,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Spacer(modifier = Modifier.height(NuvioTokens.Space.s16))
|
||||
}
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
color = tokens.colors.textPrimary,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Spacer(modifier = Modifier.height(tokens.spacing.controlGap))
|
||||
Text(
|
||||
text = message,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
color = tokens.colors.textMuted,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(18.dp))
|
||||
Spacer(modifier = Modifier.height(NuvioTokens.Space.s18))
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.End,
|
||||
|
|
@ -441,20 +448,20 @@ fun NuvioStatusModal(
|
|||
if (!isBusy && dismissText != null && onDismiss != null) {
|
||||
Button(
|
||||
onClick = onDismiss,
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
shape = tokens.shapes.button,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||
contentColor = MaterialTheme.colorScheme.onSurface,
|
||||
containerColor = tokens.colors.surfaceCard,
|
||||
contentColor = tokens.colors.textPrimary,
|
||||
),
|
||||
) {
|
||||
Text(dismissText)
|
||||
}
|
||||
Spacer(modifier = Modifier.width(10.dp))
|
||||
Spacer(modifier = Modifier.width(NuvioTokens.Space.s10))
|
||||
}
|
||||
Button(
|
||||
onClick = onConfirm,
|
||||
enabled = !isBusy,
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
shape = tokens.shapes.button,
|
||||
) {
|
||||
Text(confirmText)
|
||||
}
|
||||
|
|
@ -468,6 +475,7 @@ fun NuvioStatusModal(
|
|||
fun NuvioToastHost(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
val toast by NuvioToastController.currentToast.collectAsState()
|
||||
val statusBarTop = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
|
||||
val visibilityState = remember { MutableTransitionState(false) }
|
||||
|
|
@ -505,21 +513,21 @@ fun NuvioToastHost(
|
|||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = statusBarTop + 12.dp)
|
||||
.padding(horizontal = 16.dp),
|
||||
.padding(top = statusBarTop + tokens.spacing.listGap)
|
||||
.padding(horizontal = tokens.spacing.screenHorizontal),
|
||||
contentAlignment = Alignment.TopCenter,
|
||||
) {
|
||||
Surface(
|
||||
shape = RoundedCornerShape(18.dp),
|
||||
color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||
tonalElevation = 6.dp,
|
||||
shadowElevation = 10.dp,
|
||||
shape = RoundedCornerShape(NuvioTokens.Radius.xl),
|
||||
color = tokens.colors.surfacePopover,
|
||||
tonalElevation = tokens.elevation.raised,
|
||||
shadowElevation = tokens.elevation.overlay,
|
||||
) {
|
||||
Text(
|
||||
text = currentToast.message,
|
||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
modifier = Modifier.padding(horizontal = NuvioTokens.Space.s16, vertical = NuvioTokens.Space.s12),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
color = tokens.colors.textPrimary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.Row
|
|||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.DeleteOutline
|
||||
import androidx.compose.material.icons.filled.Info
|
||||
|
|
@ -52,6 +51,7 @@ fun NuvioContinueWatchingActionSheet(
|
|||
onRemove: () -> Unit,
|
||||
) {
|
||||
if (item == null) return
|
||||
val tokens = MaterialTheme.nuvio
|
||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
|
|
@ -73,7 +73,7 @@ fun NuvioContinueWatchingActionSheet(
|
|||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = nuvioSafeBottomPadding(16.dp)),
|
||||
.padding(bottom = nuvioSafeBottomPadding(tokens.spacing.screenHorizontal)),
|
||||
) {
|
||||
ContinueWatchingSheetHeader(item = item)
|
||||
if (showDetailsOption) {
|
||||
|
|
@ -115,19 +115,20 @@ private fun ContinueWatchingSheetHeader(
|
|||
item: ContinueWatchingItem,
|
||||
) {
|
||||
val posterCardStyle = rememberPosterCardStyleUiState()
|
||||
val tokens = MaterialTheme.nuvio
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 14.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(14.dp),
|
||||
.padding(horizontal = tokens.spacing.screenHorizontal, vertical = NuvioTokens.Space.s14),
|
||||
horizontalArrangement = Arrangement.spacedBy(NuvioTokens.Space.s14),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(width = 64.dp, height = 92.dp)
|
||||
.clip(RoundedCornerShape(posterCardStyle.cornerRadiusDp.dp))
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant),
|
||||
.size(width = NuvioTokens.Space.s64, height = NuvioTokens.Space.s80 + NuvioTokens.Space.s12)
|
||||
.clip(androidx.compose.foundation.shape.RoundedCornerShape(posterCardStyle.cornerRadiusDp.dp))
|
||||
.background(tokens.colors.surfaceCard),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
val artwork = item.poster ?: item.imageUrl
|
||||
|
|
@ -141,9 +142,9 @@ private fun ContinueWatchingSheetHeader(
|
|||
} else {
|
||||
Text(
|
||||
text = item.title,
|
||||
modifier = Modifier.padding(12.dp),
|
||||
modifier = Modifier.padding(tokens.spacing.listGap),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
color = tokens.colors.textMuted,
|
||||
maxLines = 3,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
|
|
@ -152,12 +153,12 @@ private fun ContinueWatchingSheetHeader(
|
|||
|
||||
Column(
|
||||
modifier = Modifier.weight(1f),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(NuvioTokens.Space.s4),
|
||||
) {
|
||||
Text(
|
||||
text = item.title,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
color = tokens.colors.textPrimary,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
|
|
@ -165,7 +166,7 @@ private fun ContinueWatchingSheetHeader(
|
|||
Text(
|
||||
text = localizedContinueWatchingSubtitle(item),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
color = tokens.colors.textMuted,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.padding
|
|||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Check
|
||||
import androidx.compose.material.icons.rounded.KeyboardArrowDown
|
||||
|
|
@ -31,7 +30,6 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
data class NuvioDropdownOption(
|
||||
|
|
@ -50,14 +48,15 @@ fun NuvioDropdownChip(
|
|||
onSelected: (NuvioDropdownOption) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
var isSheetVisible by remember { mutableStateOf(false) }
|
||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
Row(
|
||||
modifier = modifier
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.background(MaterialTheme.colorScheme.surface)
|
||||
.clip(tokens.shapes.compactCard)
|
||||
.background(tokens.colors.surface)
|
||||
.then(
|
||||
if (enabled) {
|
||||
Modifier.clickable { isSheetVisible = true }
|
||||
|
|
@ -65,22 +64,22 @@ fun NuvioDropdownChip(
|
|||
Modifier
|
||||
},
|
||||
)
|
||||
.padding(horizontal = 12.dp, vertical = 8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
||||
.padding(horizontal = NuvioTokens.Space.s12, vertical = tokens.components.chipVerticalPadding),
|
||||
horizontalArrangement = Arrangement.spacedBy(NuvioTokens.Space.s6),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = label,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = if (enabled) MaterialTheme.colorScheme.onSurface else MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
color = if (enabled) tokens.colors.textPrimary else tokens.colors.textDisabled,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.KeyboardArrowDown,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(18.dp),
|
||||
tint = if (enabled) MaterialTheme.colorScheme.onSurfaceVariant else MaterialTheme.colorScheme.outline,
|
||||
modifier = Modifier.size(NuvioTokens.Icon.sm + NuvioTokens.Space.s2),
|
||||
tint = if (enabled) tokens.colors.textMuted else tokens.colors.borderDefault,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -121,6 +120,7 @@ private fun NuvioDropdownOptionsSheet(
|
|||
onDismiss: () -> Unit,
|
||||
onSelected: (NuvioDropdownOption) -> Unit,
|
||||
) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
NuvioModalBottomSheet(
|
||||
onDismissRequest = onDismiss,
|
||||
sheetState = sheetState,
|
||||
|
|
@ -128,19 +128,19 @@ private fun NuvioDropdownOptionsSheet(
|
|||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = nuvioSafeBottomPadding(16.dp)),
|
||||
.padding(bottom = nuvioSafeBottomPadding(tokens.spacing.screenHorizontal)),
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 14.dp),
|
||||
modifier = Modifier.padding(horizontal = tokens.spacing.screenHorizontal, vertical = NuvioTokens.Space.s14),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
color = tokens.colors.textPrimary,
|
||||
)
|
||||
NuvioBottomSheetDivider()
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(max = 420.dp),
|
||||
.heightIn(max = tokens.breakpoints.largePhone),
|
||||
) {
|
||||
itemsIndexed(options) { index, option ->
|
||||
NuvioBottomSheetActionRow(
|
||||
|
|
@ -151,8 +151,8 @@ private fun NuvioDropdownOptionsSheet(
|
|||
Icon(
|
||||
imageVector = Icons.Rounded.Check,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.size(20.dp),
|
||||
tint = tokens.colors.accent,
|
||||
modifier = Modifier.size(tokens.icons.md),
|
||||
)
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ 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.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.PlayArrow
|
||||
|
|
@ -73,6 +72,7 @@ fun NuvioFloatingPrompt(
|
|||
modifier: Modifier = Modifier,
|
||||
autoDismissMs: Long = AutoDismissDelayMs,
|
||||
) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
val visibilityState = remember { MutableTransitionState(false) }
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val density = LocalDensity.current
|
||||
|
|
@ -98,12 +98,12 @@ fun NuvioFloatingPrompt(
|
|||
LaunchedEffect(Unit) {
|
||||
delay(autoDismissMs)
|
||||
val dismissDistance = maxOf(
|
||||
promptHeightPx.toFloat() + with(density) { 24.dp.toPx() },
|
||||
with(density) { 160.dp.toPx() },
|
||||
promptHeightPx.toFloat() + with(density) { tokens.spacing.sectionGap.toPx() },
|
||||
with(density) { (NuvioTokens.Space.s80 + NuvioTokens.Space.s80).toPx() },
|
||||
)
|
||||
dragOffsetY.animateTo(
|
||||
targetValue = dismissDistance,
|
||||
animationSpec = tween(durationMillis = 240),
|
||||
animationSpec = tween(durationMillis = tokens.motion.normalMillis),
|
||||
)
|
||||
onDismiss()
|
||||
}
|
||||
|
|
@ -116,14 +116,14 @@ fun NuvioFloatingPrompt(
|
|||
AnimatedVisibility(
|
||||
visibleState = visibilityState,
|
||||
modifier = modifier,
|
||||
enter = fadeIn(tween(400)) + slideInVertically(tween(400)) { it },
|
||||
exit = fadeOut(tween(300)) + slideOutVertically(tween(300)) { it },
|
||||
enter = fadeIn(tween(tokens.motion.slowMillis)) + slideInVertically(tween(tokens.motion.slowMillis)) { it },
|
||||
exit = fadeOut(tween(tokens.motion.sheetEnterMillis)) + slideOutVertically(tween(tokens.motion.sheetEnterMillis)) { it },
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = navBarBottom + 72.dp)
|
||||
.padding(horizontal = 16.dp)
|
||||
.padding(bottom = navBarBottom + NuvioTokens.Space.s72)
|
||||
.padding(horizontal = tokens.spacing.screenHorizontal)
|
||||
.offset { IntOffset(0, dragOffsetY.value.roundToInt().coerceAtLeast(0)) }
|
||||
.pointerInput(Unit) {
|
||||
detectVerticalDragGestures(
|
||||
|
|
@ -133,18 +133,18 @@ fun NuvioFloatingPrompt(
|
|||
if (shouldDismiss) {
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.TextHandleMove)
|
||||
val dismissDistance = maxOf(
|
||||
promptHeightPx.toFloat() + with(density) { 24.dp.toPx() },
|
||||
with(density) { 160.dp.toPx() },
|
||||
promptHeightPx.toFloat() + with(density) { tokens.spacing.sectionGap.toPx() },
|
||||
with(density) { (NuvioTokens.Space.s80 + NuvioTokens.Space.s80).toPx() },
|
||||
)
|
||||
dragOffsetY.animateTo(
|
||||
targetValue = dismissDistance,
|
||||
animationSpec = tween(durationMillis = 220),
|
||||
animationSpec = tween(durationMillis = tokens.motion.normalMillis),
|
||||
)
|
||||
onDismiss()
|
||||
} else {
|
||||
dragOffsetY.animateTo(
|
||||
targetValue = 0f,
|
||||
animationSpec = tween(durationMillis = 180),
|
||||
animationSpec = tween(durationMillis = tokens.motion.fastMillis),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -153,7 +153,7 @@ fun NuvioFloatingPrompt(
|
|||
coroutineScope.launch {
|
||||
dragOffsetY.animateTo(
|
||||
targetValue = 0f,
|
||||
animationSpec = tween(durationMillis = 180),
|
||||
animationSpec = tween(durationMillis = tokens.motion.fastMillis),
|
||||
)
|
||||
}
|
||||
},
|
||||
|
|
@ -167,27 +167,27 @@ fun NuvioFloatingPrompt(
|
|||
) {
|
||||
Surface(
|
||||
modifier = Modifier.onSizeChanged { promptHeightPx = it.height },
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||
tonalElevation = 4.dp,
|
||||
shadowElevation = 8.dp,
|
||||
shape = tokens.shapes.card,
|
||||
color = tokens.colors.surfacePopover,
|
||||
tonalElevation = tokens.elevation.playerControls,
|
||||
shadowElevation = tokens.elevation.modal,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(horizontal = 14.dp, vertical = 14.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
modifier = Modifier.padding(tokens.spacing.cardPaddingCompact),
|
||||
verticalArrangement = Arrangement.spacedBy(tokens.spacing.listGap),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 2.dp),
|
||||
.padding(start = NuvioTokens.Space.s2),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(tokens.spacing.listGap),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(width = 54.dp, height = 78.dp)
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant),
|
||||
.size(width = NuvioTokens.Space.s56 - NuvioTokens.Space.s2, height = NuvioTokens.Space.s80 - NuvioTokens.Space.s2)
|
||||
.clip(tokens.shapes.compactCard)
|
||||
.background(tokens.colors.surfaceCard),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
if (imageUrl != null) {
|
||||
|
|
@ -207,37 +207,37 @@ fun NuvioFloatingPrompt(
|
|||
Text(
|
||||
text = stringResource(Res.string.floating_prompt_continue_where_left_off),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
color = tokens.colors.textMuted,
|
||||
)
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.bodyLarge.copy(fontWeight = FontWeight.SemiBold),
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
color = tokens.colors.textPrimary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Text(
|
||||
text = subtitle,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
color = tokens.colors.textMuted,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier.size(42.dp),
|
||||
modifier = Modifier.size(NuvioTokens.Space.s40 + NuvioTokens.Space.s2),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
FilledIconButton(
|
||||
onClick = actionWithHaptic,
|
||||
modifier = Modifier.size(42.dp),
|
||||
shape = CircleShape,
|
||||
modifier = Modifier.size(NuvioTokens.Space.s40 + NuvioTokens.Space.s2),
|
||||
shape = tokens.shapes.avatar,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.PlayArrow,
|
||||
contentDescription = actionLabel,
|
||||
modifier = Modifier.size(22.dp),
|
||||
modifier = Modifier.size(tokens.icons.md),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -246,21 +246,21 @@ fun NuvioFloatingPrompt(
|
|||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(999.dp))
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.55f))
|
||||
.height(8.dp)
|
||||
.padding(1.dp),
|
||||
.clip(tokens.shapes.chip)
|
||||
.background(tokens.colors.playerTimelineTrack)
|
||||
.height(NuvioTokens.Space.s8)
|
||||
.padding(tokens.borders.thin),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(progressFraction.coerceIn(0f, 1f))
|
||||
.height(6.dp)
|
||||
.clip(RoundedCornerShape(999.dp)),
|
||||
.height(NuvioTokens.Space.s6)
|
||||
.clip(tokens.shapes.chip),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.matchParentSize()
|
||||
.background(MaterialTheme.colorScheme.primary),
|
||||
.background(tokens.colors.playerTimelineFill),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.padding
|
|||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.selection.selectable
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
|
|
@ -22,7 +21,6 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.jetbrains.compose.resources.DrawableResource
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
|
||||
|
|
@ -31,17 +29,18 @@ fun NuvioNavigationBar(
|
|||
modifier: Modifier = Modifier,
|
||||
content: @Composable NuvioNavigationBarScope.() -> Unit,
|
||||
) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
Column(modifier.fillMaxWidth()) {
|
||||
HorizontalDivider(
|
||||
thickness = 0.5.dp,
|
||||
color = MaterialTheme.colorScheme.outlineVariant,
|
||||
thickness = tokens.borders.hairline,
|
||||
color = tokens.colors.borderDefault,
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(nuvioBottomNavigationBarInsets().asPaddingValues())
|
||||
.padding(horizontal = 4.dp, vertical = nuvioBottomNavigationExtraVerticalPadding),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally),
|
||||
.padding(horizontal = NuvioTokens.Space.s4, vertical = nuvioBottomNavigationExtraVerticalPadding),
|
||||
horizontalArrangement = Arrangement.spacedBy(tokens.spacing.controlGap, Alignment.CenterHorizontally),
|
||||
) {
|
||||
NuvioNavigationBarScopeImpl(this).content()
|
||||
}
|
||||
|
|
@ -88,25 +87,25 @@ private class NuvioNavigationBarScopeImpl(
|
|||
contentDescription: String?,
|
||||
modifier: Modifier,
|
||||
) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
val iconColor by animateColorAsState(
|
||||
targetValue = if (selected) MaterialTheme.colorScheme.primary
|
||||
else MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
targetValue = if (selected) tokens.colors.accent else tokens.colors.textMuted,
|
||||
)
|
||||
with(rowScope) {
|
||||
Icon(
|
||||
modifier = modifier
|
||||
.widthIn(max = 150.dp)
|
||||
.widthIn(max = tokens.components.navItemMaxWidth)
|
||||
.fillMaxWidth()
|
||||
.weight(1f, fill = false)
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.clip(tokens.components.navItemShape)
|
||||
.selectable(
|
||||
selected = selected,
|
||||
enabled = true,
|
||||
role = Role.Tab,
|
||||
onClick = onClick,
|
||||
)
|
||||
.padding(10.dp)
|
||||
.size(28.dp),
|
||||
.padding(NuvioTokens.Space.s10)
|
||||
.size(tokens.components.navIconSize),
|
||||
imageVector = icon,
|
||||
contentDescription = contentDescription,
|
||||
tint = iconColor,
|
||||
|
|
@ -122,25 +121,25 @@ private class NuvioNavigationBarScopeImpl(
|
|||
contentDescription: String?,
|
||||
modifier: Modifier,
|
||||
) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
val iconColor by animateColorAsState(
|
||||
targetValue = if (selected) MaterialTheme.colorScheme.primary
|
||||
else MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
targetValue = if (selected) tokens.colors.accent else tokens.colors.textMuted,
|
||||
)
|
||||
with(rowScope) {
|
||||
Icon(
|
||||
modifier = modifier
|
||||
.widthIn(max = 150.dp)
|
||||
.widthIn(max = tokens.components.navItemMaxWidth)
|
||||
.fillMaxWidth()
|
||||
.weight(1f, fill = false)
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.clip(tokens.components.navItemShape)
|
||||
.selectable(
|
||||
selected = selected,
|
||||
enabled = true,
|
||||
role = Role.Tab,
|
||||
onClick = onClick,
|
||||
)
|
||||
.padding(10.dp)
|
||||
.size(28.dp),
|
||||
.padding(NuvioTokens.Space.s10)
|
||||
.size(tokens.components.navIconSize),
|
||||
painter = painterResource(icon),
|
||||
contentDescription = contentDescription,
|
||||
tint = iconColor,
|
||||
|
|
@ -155,20 +154,21 @@ private class NuvioNavigationBarScopeImpl(
|
|||
modifier: Modifier,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
with(rowScope) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.widthIn(max = 150.dp)
|
||||
.widthIn(max = tokens.components.navItemMaxWidth)
|
||||
.fillMaxWidth()
|
||||
.weight(1f, fill = false)
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.clip(tokens.components.navItemShape)
|
||||
.selectable(
|
||||
selected = selected,
|
||||
enabled = true,
|
||||
role = Role.Tab,
|
||||
onClick = onClick,
|
||||
)
|
||||
.padding(10.dp),
|
||||
.padding(NuvioTokens.Space.s10),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
content()
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import androidx.compose.material3.MaterialTheme
|
|||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.nuvio.app.core.network.NetworkCondition
|
||||
import com.nuvio.app.core.network.messageForEmptyState
|
||||
import com.nuvio.app.core.network.titleForEmptyState
|
||||
|
|
@ -20,20 +19,21 @@ fun NuvioNetworkOfflineCard(
|
|||
modifier: Modifier = Modifier,
|
||||
onRetry: (() -> Unit)? = null,
|
||||
) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
NuvioSurfaceCard(modifier = modifier) {
|
||||
Text(
|
||||
text = condition.titleForEmptyState(),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
color = tokens.colors.textPrimary,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Spacer(modifier = Modifier.height(tokens.spacing.controlGap))
|
||||
Text(
|
||||
text = condition.messageForEmptyState(),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
color = tokens.colors.textMuted,
|
||||
)
|
||||
if (onRetry != null) {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Spacer(modifier = Modifier.height(tokens.spacing.screenHorizontal))
|
||||
NuvioPrimaryButton(
|
||||
text = stringResource(Res.string.action_retry),
|
||||
onClick = onRetry,
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import androidx.compose.foundation.layout.Row
|
|||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
|
|
@ -57,6 +56,7 @@ fun NuvioPosterActionSheet(
|
|||
onToggleWatched: () -> Unit,
|
||||
) {
|
||||
if (item == null) return
|
||||
val tokens = MaterialTheme.nuvio
|
||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
|
|
@ -74,7 +74,7 @@ fun NuvioPosterActionSheet(
|
|||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = nuvioSafeBottomPadding(16.dp)),
|
||||
.padding(bottom = nuvioSafeBottomPadding(tokens.spacing.screenHorizontal)),
|
||||
) {
|
||||
PosterSheetHeader(item = item)
|
||||
NuvioBottomSheetDivider()
|
||||
|
|
@ -121,18 +121,19 @@ fun NuvioPosterActionSheet(
|
|||
fun NuvioWatchedBadge(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
Box(
|
||||
modifier = modifier
|
||||
.size(22.dp)
|
||||
.clip(CircleShape)
|
||||
.background(MaterialTheme.colorScheme.primary),
|
||||
.size(NuvioTokens.Icon.md)
|
||||
.clip(tokens.shapes.avatar)
|
||||
.background(tokens.colors.accent),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Check,
|
||||
contentDescription = stringResource(Res.string.episodes_cd_watched),
|
||||
tint = MaterialTheme.colorScheme.onPrimary,
|
||||
modifier = Modifier.size(12.dp),
|
||||
tint = tokens.colors.onAccent,
|
||||
modifier = Modifier.size(NuvioTokens.Icon.xs),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -156,7 +157,7 @@ fun NuvioAnimatedWatchedBadge(
|
|||
fun BoxScope.NuvioPosterWatchedOverlay(
|
||||
isWatched: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
padding: Dp = 6.dp,
|
||||
padding: Dp = NuvioTokens.Space.s6,
|
||||
) {
|
||||
NuvioAnimatedWatchedBadge(
|
||||
isVisible = isWatched,
|
||||
|
|
@ -171,19 +172,20 @@ private fun PosterSheetHeader(
|
|||
item: MetaPreview,
|
||||
) {
|
||||
val posterCardStyle = rememberPosterCardStyleUiState()
|
||||
val tokens = MaterialTheme.nuvio
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 14.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(14.dp),
|
||||
.padding(horizontal = tokens.spacing.screenHorizontal, vertical = NuvioTokens.Space.s14),
|
||||
horizontalArrangement = Arrangement.spacedBy(NuvioTokens.Space.s14),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(width = 64.dp, height = 92.dp)
|
||||
.size(width = NuvioTokens.Space.s64, height = NuvioTokens.Space.s80 + NuvioTokens.Space.s12)
|
||||
.clip(RoundedCornerShape(posterCardStyle.cornerRadiusDp.dp))
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant),
|
||||
.background(tokens.colors.surfaceCard),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
if (item.poster != null) {
|
||||
|
|
@ -196,9 +198,9 @@ private fun PosterSheetHeader(
|
|||
} else {
|
||||
Text(
|
||||
text = item.name,
|
||||
modifier = Modifier.padding(12.dp),
|
||||
modifier = Modifier.padding(tokens.spacing.listGap),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
color = tokens.colors.textMuted,
|
||||
maxLines = 3,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
|
|
@ -207,12 +209,12 @@ private fun PosterSheetHeader(
|
|||
|
||||
Column(
|
||||
modifier = Modifier.weight(1f),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(NuvioTokens.Space.s4),
|
||||
) {
|
||||
Text(
|
||||
text = item.name,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
color = tokens.colors.textPrimary,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
|
|
@ -221,9 +223,9 @@ private fun PosterSheetHeader(
|
|||
text = item.releaseInfo?.takeIf { it.isNotBlank() }?.let { formatReleaseDateForDisplay(it) }
|
||||
?: item.type.replaceFirstChar { char ->
|
||||
if (char.isLowerCase()) char.titlecase() else char.toString()
|
||||
},
|
||||
},
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
color = tokens.colors.textMuted,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -5,37 +5,36 @@ import androidx.compose.foundation.layout.Box
|
|||
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.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun NuvioProgressBar(
|
||||
progress: Float,
|
||||
modifier: Modifier = Modifier,
|
||||
height: Dp = 4.dp,
|
||||
trackColor: Color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f),
|
||||
fillColor: Color = MaterialTheme.colorScheme.primary,
|
||||
height: Dp = NuvioTokens.Space.s4,
|
||||
trackColor: Color = MaterialTheme.nuvio.colors.playerTimelineTrack,
|
||||
fillColor: Color = MaterialTheme.nuvio.colors.playerTimelineFill,
|
||||
) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
val clampedProgress = progress.coerceIn(0f, 1f)
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.height(height)
|
||||
.clip(RoundedCornerShape(percent = 50))
|
||||
.clip(tokens.shapes.chip)
|
||||
.background(trackColor),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(clampedProgress)
|
||||
.width(0.dp)
|
||||
.width(NuvioTokens.Space.none)
|
||||
.height(height)
|
||||
.clip(RoundedCornerShape(percent = 50))
|
||||
.clip(tokens.shapes.chip)
|
||||
.background(fillColor),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,9 +63,10 @@ fun <T> NuvioShelfSection(
|
|||
key: ((T) -> Any)? = null,
|
||||
itemContent: @Composable (T) -> Unit,
|
||||
) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
Column(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(tokens.spacing.controlGap + NuvioTokens.Space.s2),
|
||||
) {
|
||||
if (title.isNotBlank()) {
|
||||
NuvioShelfSectionHeader(
|
||||
|
|
@ -111,6 +112,7 @@ fun NuvioPosterCard(
|
|||
onLongClick: (() -> Unit)? = null,
|
||||
) {
|
||||
val posterCardStyle = rememberPosterCardStyleUiState()
|
||||
val tokens = MaterialTheme.nuvio
|
||||
val cardWidth = shape.cardWidth(basePosterWidthDp = posterCardStyle.widthDp)
|
||||
val cardShape = RoundedCornerShape(posterCardStyle.cornerRadiusDp.dp)
|
||||
val catalogLogoOverlaySize = catalogLogoOverlaySize(
|
||||
|
|
@ -121,14 +123,14 @@ fun NuvioPosterCard(
|
|||
|
||||
Column(
|
||||
modifier = modifier.width(cardWidth),
|
||||
verticalArrangement = Arrangement.spacedBy(6.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(NuvioTokens.Space.s6),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.aspectRatio(shape.aspectRatio)
|
||||
.clip(cardShape)
|
||||
.background(MaterialTheme.colorScheme.surface)
|
||||
.background(tokens.colors.surface)
|
||||
.posterCardClickable(onClick = onClick, onLongClick = onLongClick),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
|
|
@ -142,9 +144,9 @@ fun NuvioPosterCard(
|
|||
} else {
|
||||
Text(
|
||||
text = title,
|
||||
modifier = Modifier.padding(horizontal = 14.dp),
|
||||
modifier = Modifier.padding(horizontal = NuvioTokens.Space.s14),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
color = tokens.colors.textMuted,
|
||||
textAlign = TextAlign.Center,
|
||||
maxLines = 3,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
|
|
@ -155,7 +157,7 @@ fun NuvioPosterCard(
|
|||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomStart)
|
||||
.padding(horizontal = 10.dp, vertical = 10.dp),
|
||||
.padding(horizontal = NuvioTokens.Space.s10, vertical = NuvioTokens.Space.s10),
|
||||
) {
|
||||
if (!bottomLeftLogoUrl.isNullOrBlank()) {
|
||||
AsyncImage(
|
||||
|
|
@ -170,7 +172,7 @@ fun NuvioPosterCard(
|
|||
Text(
|
||||
text = bottomLeftText.orEmpty(),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onPrimary,
|
||||
color = tokens.colors.textPrimary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.widthIn(max = catalogLogoOverlaySize.textMaxWidth),
|
||||
|
|
@ -185,7 +187,7 @@ fun NuvioPosterCard(
|
|||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
color = tokens.colors.textPrimary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
|
|
@ -193,15 +195,15 @@ fun NuvioPosterCard(
|
|||
Text(
|
||||
text = detailLine,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
color = tokens.colors.textMuted,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
} else {
|
||||
Box(modifier = Modifier.height(0.dp))
|
||||
Box(modifier = Modifier.height(NuvioTokens.Space.none))
|
||||
}
|
||||
} else {
|
||||
Box(modifier = Modifier.height(0.dp))
|
||||
Box(modifier = Modifier.height(NuvioTokens.Space.none))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -214,6 +216,7 @@ private fun NuvioShelfSectionHeader(
|
|||
onViewAllClick: (() -> Unit)? = null,
|
||||
viewAllPillSize: NuvioViewAllPillSize = NuvioViewAllPillSize.Default,
|
||||
) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
Row(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
|
|
@ -225,19 +228,19 @@ private fun NuvioShelfSectionHeader(
|
|||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
color = tokens.colors.textPrimary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
if (showAccent) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(top = 6.dp)
|
||||
.width(60.dp)
|
||||
.height(4.dp)
|
||||
.padding(top = NuvioTokens.Space.s6)
|
||||
.width(NuvioTokens.Space.s64 - NuvioTokens.Space.s4)
|
||||
.height(NuvioTokens.Space.s4)
|
||||
.background(
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
shape = RoundedCornerShape(999.dp),
|
||||
color = tokens.colors.accent,
|
||||
shape = tokens.shapes.chip,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -256,22 +259,21 @@ private fun NuvioViewAllPill(
|
|||
onClick: (() -> Unit)?,
|
||||
size: NuvioViewAllPillSize,
|
||||
) {
|
||||
val colorScheme = MaterialTheme.colorScheme
|
||||
val isAmoled = colorScheme.background == androidx.compose.ui.graphics.Color.Black && colorScheme.surface == androidx.compose.ui.graphics.Color(0xFF050505)
|
||||
val horizontalPadding = if (size == NuvioViewAllPillSize.Compact) 12.dp else 18.dp
|
||||
val verticalPadding = if (size == NuvioViewAllPillSize.Compact) 9.dp else 14.dp
|
||||
val tokens = MaterialTheme.nuvio
|
||||
val horizontalPadding = if (size == NuvioViewAllPillSize.Compact) NuvioTokens.Space.s12 else NuvioTokens.Space.s18
|
||||
val verticalPadding = if (size == NuvioViewAllPillSize.Compact) NuvioTokens.Space.s8 + NuvioTokens.Space.s1 else NuvioTokens.Space.s14
|
||||
val textStyle = if (size == NuvioViewAllPillSize.Compact) {
|
||||
MaterialTheme.typography.labelLarge
|
||||
} else {
|
||||
MaterialTheme.typography.titleMedium
|
||||
}
|
||||
val iconSpacing = if (size == NuvioViewAllPillSize.Compact) 2.dp else 4.dp
|
||||
val iconSpacing = if (size == NuvioViewAllPillSize.Compact) NuvioTokens.Space.s2 else NuvioTokens.Space.s4
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = if (isAmoled) androidx.compose.ui.graphics.Color(0xFF0D0D0D) else colorScheme.surface,
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
color = tokens.colors.surface,
|
||||
shape = RoundedCornerShape(NuvioTokens.Radius.xl),
|
||||
)
|
||||
.then(if (onClick != null) Modifier.clickable(onClick = onClick) else Modifier)
|
||||
.padding(horizontal = horizontalPadding, vertical = verticalPadding),
|
||||
|
|
@ -281,13 +283,13 @@ private fun NuvioViewAllPill(
|
|||
Text(
|
||||
text = stringResource(Res.string.home_view_all),
|
||||
style = textStyle,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
color = tokens.colors.textPrimary,
|
||||
)
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Rounded.KeyboardArrowRight,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.height(if (size == NuvioViewAllPillSize.Compact) 16.dp else 20.dp),
|
||||
tint = tokens.colors.textMuted,
|
||||
modifier = Modifier.height(if (size == NuvioViewAllPillSize.Compact) NuvioTokens.Icon.sm else tokens.icons.md),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import androidx.compose.ui.text.font.FontFamily
|
|||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.Density
|
||||
import androidx.compose.ui.unit.sp
|
||||
import nuvio.composeapp.generated.resources.Res
|
||||
import nuvio.composeapp.generated.resources.jetbrains_sans_bold
|
||||
import nuvio.composeapp.generated.resources.jetbrains_sans_regular
|
||||
|
|
@ -53,24 +52,6 @@ private fun buildColorScheme(palette: ThemeColorPalette, amoled: Boolean = false
|
|||
onError = Color(0xFFFCE5EC),
|
||||
)
|
||||
|
||||
private val NuvioDarkColors = darkColorScheme(
|
||||
primary = Color(0xFF2E86B8),
|
||||
onPrimary = Color(0xFFD2E8F7),
|
||||
primaryContainer = Color(0xFF102531),
|
||||
onPrimaryContainer = Color(0xFFE2F1FA),
|
||||
secondary = Color(0xFF8A929C),
|
||||
onSecondary = Color(0xFFEEF1F3),
|
||||
background = Color(0xFF020404),
|
||||
onBackground = Color(0xFFF5F7F8),
|
||||
surface = Color(0xFF0A0D0D),
|
||||
onSurface = Color(0xFFF5F7F8),
|
||||
surfaceVariant = Color(0xFF121616),
|
||||
onSurfaceVariant = Color(0xFF969CA3),
|
||||
outline = Color(0xFF252A2A),
|
||||
error = Color(0xFFE36A8A),
|
||||
onError = Color(0xFFFCE5EC),
|
||||
)
|
||||
|
||||
private val JetBrainsSans: FontFamily
|
||||
@Composable
|
||||
get() = FontFamily(
|
||||
|
|
@ -84,54 +65,54 @@ private val NuvioTypography: Typography
|
|||
get() = Typography(
|
||||
displayLarge = TextStyle(
|
||||
fontFamily = JetBrainsSans,
|
||||
fontSize = 38.sp,
|
||||
lineHeight = 42.sp,
|
||||
fontSize = NuvioTokens.Type.pageDisplay,
|
||||
lineHeight = NuvioTokens.LineHeight.pageDisplay,
|
||||
fontWeight = FontWeight.Bold,
|
||||
letterSpacing = (-1.2).sp,
|
||||
letterSpacing = NuvioTokens.LetterSpacing.pageDisplay,
|
||||
),
|
||||
headlineLarge = TextStyle(
|
||||
fontFamily = JetBrainsSans,
|
||||
fontSize = 26.sp,
|
||||
lineHeight = 30.sp,
|
||||
fontSize = NuvioTokens.Type.headline,
|
||||
lineHeight = NuvioTokens.LineHeight.headline,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
letterSpacing = (-0.8).sp,
|
||||
letterSpacing = NuvioTokens.LetterSpacing.headline,
|
||||
),
|
||||
titleLarge = TextStyle(
|
||||
fontFamily = JetBrainsSans,
|
||||
fontSize = 18.sp,
|
||||
lineHeight = 24.sp,
|
||||
fontSize = NuvioTokens.Type.titleSm,
|
||||
lineHeight = NuvioTokens.LineHeight.materialTitleLarge,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
),
|
||||
titleMedium = TextStyle(
|
||||
fontFamily = JetBrainsSans,
|
||||
fontSize = 16.sp,
|
||||
lineHeight = 20.sp,
|
||||
fontSize = NuvioTokens.Type.bodyLg,
|
||||
lineHeight = NuvioTokens.LineHeight.bodyMd,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
),
|
||||
bodyLarge = TextStyle(
|
||||
fontFamily = JetBrainsSans,
|
||||
fontSize = 15.sp,
|
||||
lineHeight = 22.sp,
|
||||
fontSize = NuvioTokens.Type.bodyApp,
|
||||
lineHeight = NuvioTokens.LineHeight.bodyApp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
),
|
||||
bodyMedium = TextStyle(
|
||||
fontFamily = JetBrainsSans,
|
||||
fontSize = 14.sp,
|
||||
lineHeight = 20.sp,
|
||||
fontSize = NuvioTokens.Type.bodyMd,
|
||||
lineHeight = NuvioTokens.LineHeight.bodyMd,
|
||||
fontWeight = FontWeight.Normal,
|
||||
),
|
||||
labelLarge = TextStyle(
|
||||
fontFamily = JetBrainsSans,
|
||||
fontSize = 14.sp,
|
||||
lineHeight = 18.sp,
|
||||
fontSize = NuvioTokens.Type.bodyMd,
|
||||
lineHeight = NuvioTokens.LineHeight.bodySm,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
),
|
||||
labelMedium = TextStyle(
|
||||
fontFamily = JetBrainsSans,
|
||||
fontSize = 12.sp,
|
||||
lineHeight = 14.sp,
|
||||
fontSize = NuvioTokens.Type.labelSm,
|
||||
lineHeight = NuvioTokens.LineHeight.labelXs,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
letterSpacing = 0.8.sp,
|
||||
letterSpacing = NuvioTokens.LetterSpacing.label,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -140,62 +121,62 @@ private val NuvioTypeTokens: NuvioTypeScale
|
|||
get() = NuvioTypeScale(
|
||||
labelXs = TextStyle(
|
||||
fontFamily = JetBrainsSans,
|
||||
fontSize = 11.sp,
|
||||
lineHeight = 14.sp,
|
||||
fontSize = NuvioTokens.Type.labelXs,
|
||||
lineHeight = NuvioTokens.LineHeight.labelXs,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
),
|
||||
labelSm = TextStyle(
|
||||
fontFamily = JetBrainsSans,
|
||||
fontSize = 12.sp,
|
||||
lineHeight = 15.sp,
|
||||
fontSize = NuvioTokens.Type.labelSm,
|
||||
lineHeight = NuvioTokens.LineHeight.labelSm,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
),
|
||||
bodySm = TextStyle(
|
||||
fontFamily = JetBrainsSans,
|
||||
fontSize = 13.sp,
|
||||
lineHeight = 18.sp,
|
||||
fontSize = NuvioTokens.Type.bodySm,
|
||||
lineHeight = NuvioTokens.LineHeight.bodySm,
|
||||
fontWeight = FontWeight.Normal,
|
||||
),
|
||||
bodyMd = TextStyle(
|
||||
fontFamily = JetBrainsSans,
|
||||
fontSize = 14.sp,
|
||||
lineHeight = 20.sp,
|
||||
fontSize = NuvioTokens.Type.bodyMd,
|
||||
lineHeight = NuvioTokens.LineHeight.bodyMd,
|
||||
fontWeight = FontWeight.Normal,
|
||||
),
|
||||
bodyLg = TextStyle(
|
||||
fontFamily = JetBrainsSans,
|
||||
fontSize = 16.sp,
|
||||
lineHeight = 22.sp,
|
||||
fontSize = NuvioTokens.Type.bodyLg,
|
||||
lineHeight = NuvioTokens.LineHeight.bodyLg,
|
||||
fontWeight = FontWeight.Normal,
|
||||
),
|
||||
titleSm = TextStyle(
|
||||
fontFamily = JetBrainsSans,
|
||||
fontSize = 18.sp,
|
||||
lineHeight = 22.sp,
|
||||
fontSize = NuvioTokens.Type.titleSm,
|
||||
lineHeight = NuvioTokens.LineHeight.titleSm,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
),
|
||||
titleMd = TextStyle(
|
||||
fontFamily = JetBrainsSans,
|
||||
fontSize = 22.sp,
|
||||
lineHeight = 26.sp,
|
||||
fontSize = NuvioTokens.Type.titleMd,
|
||||
lineHeight = NuvioTokens.LineHeight.titleMd,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
),
|
||||
titleLg = TextStyle(
|
||||
fontFamily = JetBrainsSans,
|
||||
fontSize = 28.sp,
|
||||
lineHeight = 32.sp,
|
||||
fontSize = NuvioTokens.Type.titleLg,
|
||||
lineHeight = NuvioTokens.LineHeight.titleLg,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
),
|
||||
displaySm = TextStyle(
|
||||
fontFamily = JetBrainsSans,
|
||||
fontSize = 32.sp,
|
||||
lineHeight = 36.sp,
|
||||
fontSize = NuvioTokens.Type.displaySm,
|
||||
lineHeight = NuvioTokens.LineHeight.displaySm,
|
||||
fontWeight = FontWeight.Bold,
|
||||
),
|
||||
displayMd = TextStyle(
|
||||
fontFamily = JetBrainsSans,
|
||||
fontSize = 48.sp,
|
||||
lineHeight = 52.sp,
|
||||
fontSize = NuvioTokens.Type.displayMd,
|
||||
lineHeight = NuvioTokens.LineHeight.displayMd,
|
||||
fontWeight = FontWeight.Bold,
|
||||
),
|
||||
)
|
||||
|
|
@ -211,7 +192,9 @@ fun NuvioTheme(
|
|||
amoled: Boolean = false,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val colorScheme = buildColorScheme(ThemeColors.getColorPalette(appTheme), amoled = amoled)
|
||||
val palette = ThemeColors.getColorPalette(appTheme)
|
||||
val colorScheme = buildColorScheme(palette, amoled = amoled)
|
||||
val tokens = defaultNuvioThemeTokens(palette, amoled = amoled, colorScheme = colorScheme)
|
||||
|
||||
val density = LocalDensity.current
|
||||
CompositionLocalProvider(
|
||||
|
|
@ -219,6 +202,7 @@ fun NuvioTheme(
|
|||
density = density.density,
|
||||
fontScale = 1f,
|
||||
),
|
||||
LocalNuvioThemeTokens provides tokens,
|
||||
LocalNuvioTypeScale provides NuvioTypeTokens,
|
||||
LocalRippleConfiguration provides NuvioRippleConfiguration,
|
||||
LocalAppTheme provides appTheme,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
package com.nuvio.app.core.ui
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
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
|
||||
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.lazy.LazyColumn
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
|
||||
@Composable
|
||||
fun NuvioTokenPreviewSurface(
|
||||
modifier: Modifier = Modifier,
|
||||
themes: List<AppTheme> = AppTheme.entries,
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = modifier.background(MaterialTheme.nuvio.colors.background),
|
||||
contentPadding = PaddingValues(MaterialTheme.nuvio.spacing.screenHorizontal),
|
||||
verticalArrangement = Arrangement.spacedBy(MaterialTheme.nuvio.spacing.sectionGap),
|
||||
) {
|
||||
items(themes.size) { index ->
|
||||
val theme = themes[index]
|
||||
NuvioTheme(appTheme = theme) {
|
||||
TokenPreviewThemeSection(theme = theme)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TokenPreviewThemeSection(theme: AppTheme) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(tokens.shapes.card)
|
||||
.background(tokens.colors.surface)
|
||||
.border(tokens.borders.thin, tokens.colors.borderSubtle, tokens.shapes.card)
|
||||
.padding(tokens.spacing.cardPadding),
|
||||
verticalArrangement = Arrangement.spacedBy(tokens.spacing.listGap),
|
||||
) {
|
||||
Text(
|
||||
text = theme.name,
|
||||
style = MaterialTheme.nuvioTypeScale.titleMd,
|
||||
color = tokens.colors.textPrimary,
|
||||
)
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(tokens.spacing.controlGap)) {
|
||||
listOf(
|
||||
tokens.colors.background,
|
||||
tokens.colors.surface,
|
||||
tokens.colors.surfaceCard,
|
||||
tokens.colors.accent,
|
||||
tokens.colors.success,
|
||||
tokens.colors.warning,
|
||||
tokens.colors.danger,
|
||||
).forEach { color ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(NuvioTokens.Icon.xl)
|
||||
.clip(RoundedCornerShape(NuvioTokens.Radius.sm))
|
||||
.background(color)
|
||||
.border(tokens.borders.hairline, tokens.colors.borderDefault, RoundedCornerShape(NuvioTokens.Radius.sm)),
|
||||
)
|
||||
}
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(NuvioTokens.Space.s8)
|
||||
.clip(tokens.shapes.chip)
|
||||
.background(tokens.colors.accent),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,526 @@
|
|||
package com.nuvio.app.core.ui
|
||||
|
||||
import androidx.compose.animation.core.CubicBezierEasing
|
||||
import androidx.compose.animation.core.Easing
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.ColorScheme
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
object NuvioTokens {
|
||||
object Space {
|
||||
val none = 0.dp
|
||||
val hairline = 0.5.dp
|
||||
val s1 = 1.dp
|
||||
val s2 = 2.dp
|
||||
val s3 = 3.dp
|
||||
val s4 = 4.dp
|
||||
val s5 = 5.dp
|
||||
val s6 = 6.dp
|
||||
val s7 = 7.dp
|
||||
val s8 = 8.dp
|
||||
val s10 = 10.dp
|
||||
val s12 = 12.dp
|
||||
val s14 = 14.dp
|
||||
val s16 = 16.dp
|
||||
val s18 = 18.dp
|
||||
val s20 = 20.dp
|
||||
val s22 = 22.dp
|
||||
val s24 = 24.dp
|
||||
val s28 = 28.dp
|
||||
val s32 = 32.dp
|
||||
val s36 = 36.dp
|
||||
val s40 = 40.dp
|
||||
val s48 = 48.dp
|
||||
val s56 = 56.dp
|
||||
val s64 = 64.dp
|
||||
val s72 = 72.dp
|
||||
val s80 = 80.dp
|
||||
val s96 = 96.dp
|
||||
}
|
||||
|
||||
object Radius {
|
||||
val none = Space.none
|
||||
val xs = Space.s4
|
||||
val sm = Space.s6
|
||||
val md = Space.s8
|
||||
val lg = Space.s12
|
||||
val xl = Space.s16
|
||||
val xxl = Space.s24
|
||||
val full = 999.dp
|
||||
|
||||
val card = xxl
|
||||
val compactCard = lg
|
||||
val sheet = xxl
|
||||
val dialog = xxl
|
||||
val button = xl
|
||||
val chip = full
|
||||
val poster = lg
|
||||
val avatar = full
|
||||
val playerPanel = xxl
|
||||
}
|
||||
|
||||
object Border {
|
||||
val hairline = Space.hairline
|
||||
val thin = Space.s1
|
||||
val medium = Space.s2
|
||||
}
|
||||
|
||||
object Elevation {
|
||||
val none = Space.none
|
||||
val raised = Space.s2
|
||||
val modal = Space.s8
|
||||
val overlay = Space.s12
|
||||
val playerControls = Space.s4
|
||||
}
|
||||
|
||||
object Opacity {
|
||||
const val invisible = 0f
|
||||
const val disabled = 0.38f
|
||||
const val secondary = 0.70f
|
||||
const val muted = 0.60f
|
||||
const val selected = 0.15f
|
||||
const val hover = 0.08f
|
||||
const val pressed = 0.12f
|
||||
const val subtle = 0.06f
|
||||
const val medium = 0.52f
|
||||
const val strong = 0.75f
|
||||
const val overlayLight = 0.35f
|
||||
const val overlayMedium = 0.56f
|
||||
const val overlayHeavy = 0.82f
|
||||
const val visible = 1f
|
||||
}
|
||||
|
||||
object Motion {
|
||||
const val instantMillis = 0
|
||||
const val fastMillis = 150
|
||||
const val normalMillis = 220
|
||||
const val sheetEnterMillis = 300
|
||||
const val sheetExitMillis = 250
|
||||
const val slowMillis = 400
|
||||
const val cinematicMillis = 700
|
||||
|
||||
val standard: Easing = CubicBezierEasing(0.2f, 0f, 0f, 1f)
|
||||
val emphasized: Easing = CubicBezierEasing(0.2f, 0f, 0f, 1f)
|
||||
val decelerate: Easing = CubicBezierEasing(0f, 0f, 0f, 1f)
|
||||
val accelerate: Easing = CubicBezierEasing(0.3f, 0f, 1f, 1f)
|
||||
}
|
||||
|
||||
object Icon {
|
||||
val xs = Space.s12
|
||||
val sm = Space.s16
|
||||
val md = Space.s20
|
||||
val lg = Space.s24
|
||||
val xl = Space.s32
|
||||
val xxl = Space.s40
|
||||
}
|
||||
|
||||
object Type {
|
||||
val labelXs = 11.sp
|
||||
val labelSm = 12.sp
|
||||
val bodySm = 13.sp
|
||||
val bodyMd = 14.sp
|
||||
val bodyApp = 15.sp
|
||||
val bodyLg = 16.sp
|
||||
val titleSm = 18.sp
|
||||
val headline = 26.sp
|
||||
val titleMd = 22.sp
|
||||
val titleLg = 28.sp
|
||||
val displaySm = 32.sp
|
||||
val pageDisplay = 38.sp
|
||||
val displayMd = 48.sp
|
||||
}
|
||||
|
||||
object LineHeight {
|
||||
val labelXs = 14.sp
|
||||
val labelSm = 15.sp
|
||||
val bodySm = 18.sp
|
||||
val bodyMd = 20.sp
|
||||
val bodyApp = 22.sp
|
||||
val bodyLg = 22.sp
|
||||
val titleSm = 22.sp
|
||||
val materialTitleLarge = 24.sp
|
||||
val headline = 30.sp
|
||||
val titleMd = 26.sp
|
||||
val titleLg = 32.sp
|
||||
val displaySm = 36.sp
|
||||
val pageDisplay = 42.sp
|
||||
val displayMd = 52.sp
|
||||
}
|
||||
|
||||
object LetterSpacing {
|
||||
val none = 0.sp
|
||||
val pageDisplay = (-1.2).sp
|
||||
val headline = (-0.8).sp
|
||||
val label = 0.8.sp
|
||||
}
|
||||
|
||||
object Breakpoint {
|
||||
val phone = 0.dp
|
||||
val largePhone = 420.dp
|
||||
val tablet = 600.dp
|
||||
val largeTablet = 840.dp
|
||||
val desktop = 1024.dp
|
||||
val playerWide = 1280.dp
|
||||
}
|
||||
|
||||
object Z {
|
||||
const val base = 0f
|
||||
const val stickyHeader = 2f
|
||||
const val navigation = 4f
|
||||
const val sheet = 8f
|
||||
const val dialog = 10f
|
||||
const val playerOverlay = 12f
|
||||
const val toast = 16f
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class NuvioColorTokens(
|
||||
val background: Color,
|
||||
val backgroundAmoled: Color,
|
||||
val surface: Color,
|
||||
val surfaceElevated: Color,
|
||||
val surfaceCard: Color,
|
||||
val surfaceSheet: Color,
|
||||
val surfaceDialog: Color,
|
||||
val surfacePopover: Color,
|
||||
val nativeChrome: Color,
|
||||
val textPrimary: Color,
|
||||
val textSecondary: Color,
|
||||
val textMuted: Color,
|
||||
val textDisabled: Color,
|
||||
val textInverse: Color,
|
||||
val accent: Color,
|
||||
val accentStrong: Color,
|
||||
val onAccent: Color,
|
||||
val focusRing: Color,
|
||||
val focusBackground: Color,
|
||||
val borderSubtle: Color,
|
||||
val borderDefault: Color,
|
||||
val borderStrong: Color,
|
||||
val borderFocus: Color,
|
||||
val borderSelected: Color,
|
||||
val success: Color,
|
||||
val warning: Color,
|
||||
val danger: Color,
|
||||
val info: Color,
|
||||
val neutral: Color,
|
||||
val overlayScrim: Color,
|
||||
val overlayHover: Color,
|
||||
val overlayPressed: Color,
|
||||
val overlaySelected: Color,
|
||||
val overlayDisabled: Color,
|
||||
val shimmer: Color,
|
||||
val skeleton: Color,
|
||||
val playerControlsBackground: Color,
|
||||
val playerControlsForeground: Color,
|
||||
val playerTimelineTrack: Color,
|
||||
val playerTimelineFill: Color,
|
||||
val playerSubtitlePreview: Color,
|
||||
val playerBuffering: Color,
|
||||
val parentalGuide: Color,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
data class NuvioShapeTokens(
|
||||
val card: Shape,
|
||||
val compactCard: Shape,
|
||||
val sheet: Shape,
|
||||
val dialog: Shape,
|
||||
val button: Shape,
|
||||
val chip: Shape,
|
||||
val poster: Shape,
|
||||
val avatar: Shape,
|
||||
val playerPanel: Shape,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
data class NuvioSpacingTokens(
|
||||
val screenHorizontal: Dp,
|
||||
val screenTop: Dp,
|
||||
val screenBottom: Dp,
|
||||
val sectionGap: Dp,
|
||||
val listGap: Dp,
|
||||
val railGap: Dp,
|
||||
val cardPadding: Dp,
|
||||
val cardPaddingCompact: Dp,
|
||||
val controlGap: Dp,
|
||||
val dialogPadding: Dp,
|
||||
val sheetPadding: Dp,
|
||||
val playerOverlayPadding: Dp,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
data class NuvioBorderTokens(
|
||||
val hairline: Dp,
|
||||
val thin: Dp,
|
||||
val medium: Dp,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
data class NuvioElevationTokens(
|
||||
val flat: Dp,
|
||||
val raised: Dp,
|
||||
val modal: Dp,
|
||||
val overlay: Dp,
|
||||
val playerControls: Dp,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
data class NuvioMotionTokens(
|
||||
val instantMillis: Int,
|
||||
val fastMillis: Int,
|
||||
val normalMillis: Int,
|
||||
val sheetEnterMillis: Int,
|
||||
val sheetExitMillis: Int,
|
||||
val slowMillis: Int,
|
||||
val cinematicMillis: Int,
|
||||
val standard: Easing,
|
||||
val emphasized: Easing,
|
||||
val decelerate: Easing,
|
||||
val accelerate: Easing,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
data class NuvioOpacityTokens(
|
||||
val invisible: Float,
|
||||
val disabled: Float,
|
||||
val secondary: Float,
|
||||
val muted: Float,
|
||||
val selected: Float,
|
||||
val hover: Float,
|
||||
val pressed: Float,
|
||||
val subtle: Float,
|
||||
val medium: Float,
|
||||
val strong: Float,
|
||||
val overlayLight: Float,
|
||||
val overlayMedium: Float,
|
||||
val overlayHeavy: Float,
|
||||
val visible: Float,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
data class NuvioIconTokens(
|
||||
val xs: Dp,
|
||||
val sm: Dp,
|
||||
val md: Dp,
|
||||
val lg: Dp,
|
||||
val xl: Dp,
|
||||
val xxl: Dp,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
data class NuvioBreakpointTokens(
|
||||
val phone: Dp,
|
||||
val largePhone: Dp,
|
||||
val tablet: Dp,
|
||||
val largeTablet: Dp,
|
||||
val desktop: Dp,
|
||||
val playerWide: Dp,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
data class NuvioComponentTokens(
|
||||
val navItemShape: Shape,
|
||||
val navIconSize: Dp,
|
||||
val navItemMaxWidth: Dp,
|
||||
val sheetMaxWidth: Dp,
|
||||
val dialogMaxWidth: Dp,
|
||||
val chipHorizontalPadding: Dp,
|
||||
val chipVerticalPadding: Dp,
|
||||
val posterRadius: Dp,
|
||||
val avatarSize: Dp,
|
||||
val playerPanelMaxWidth: Dp,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
data class NuvioThemeTokens(
|
||||
val colors: NuvioColorTokens,
|
||||
val spacing: NuvioSpacingTokens,
|
||||
val shapes: NuvioShapeTokens,
|
||||
val borders: NuvioBorderTokens,
|
||||
val elevation: NuvioElevationTokens,
|
||||
val motion: NuvioMotionTokens,
|
||||
val opacity: NuvioOpacityTokens,
|
||||
val icons: NuvioIconTokens,
|
||||
val breakpoints: NuvioBreakpointTokens,
|
||||
val components: NuvioComponentTokens,
|
||||
)
|
||||
|
||||
internal val LocalNuvioThemeTokens = staticCompositionLocalOf {
|
||||
defaultNuvioThemeTokens(ThemeColors.White, amoled = false, colorScheme = null)
|
||||
}
|
||||
|
||||
val MaterialTheme.nuvio: NuvioThemeTokens
|
||||
@Composable
|
||||
@Stable
|
||||
get() = LocalNuvioThemeTokens.current
|
||||
|
||||
internal fun defaultNuvioThemeTokens(
|
||||
palette: ThemeColorPalette,
|
||||
amoled: Boolean,
|
||||
colorScheme: ColorScheme?,
|
||||
): NuvioThemeTokens {
|
||||
val background = if (amoled) Color.Black else palette.background
|
||||
val textPrimary = Color(0xFFF5F7F8)
|
||||
val textSecondary = Color(0xFFB8BEC5)
|
||||
val textMuted = Color(0xFF969CA3)
|
||||
val surface = palette.backgroundElevated
|
||||
val surfaceCard = palette.backgroundCard
|
||||
val accent = palette.secondary
|
||||
val accentStrong = palette.secondaryVariant
|
||||
val borderSubtle = Color(0xFF252A2A).copy(alpha = 0.55f)
|
||||
val borderDefault = Color(0xFF252A2A)
|
||||
val overlayScrim = Color.Black.copy(alpha = NuvioTokens.Opacity.overlayMedium)
|
||||
|
||||
return NuvioThemeTokens(
|
||||
colors = NuvioColorTokens(
|
||||
background = background,
|
||||
backgroundAmoled = Color.Black,
|
||||
surface = surface,
|
||||
surfaceElevated = surface,
|
||||
surfaceCard = surfaceCard,
|
||||
surfaceSheet = surface,
|
||||
surfaceDialog = surface,
|
||||
surfacePopover = surfaceCard,
|
||||
nativeChrome = background,
|
||||
textPrimary = textPrimary,
|
||||
textSecondary = textSecondary,
|
||||
textMuted = textMuted,
|
||||
textDisabled = textMuted.copy(alpha = NuvioTokens.Opacity.disabled),
|
||||
textInverse = Color(0xFF111111),
|
||||
accent = accent,
|
||||
accentStrong = accentStrong,
|
||||
onAccent = palette.onSecondary,
|
||||
focusRing = palette.focusRing,
|
||||
focusBackground = palette.focusBackground,
|
||||
borderSubtle = borderSubtle,
|
||||
borderDefault = borderDefault,
|
||||
borderStrong = Color(0xFF3A4040),
|
||||
borderFocus = palette.focusRing,
|
||||
borderSelected = accent.copy(alpha = NuvioTokens.Opacity.strong),
|
||||
success = Color(0xFF66BB6A),
|
||||
warning = Color(0xFFFFC857),
|
||||
danger = colorScheme?.error ?: Color(0xFFE36A8A),
|
||||
info = Color(0xFF42A5F5),
|
||||
neutral = textMuted,
|
||||
overlayScrim = overlayScrim,
|
||||
overlayHover = Color.White.copy(alpha = NuvioTokens.Opacity.hover),
|
||||
overlayPressed = Color.White.copy(alpha = NuvioTokens.Opacity.pressed),
|
||||
overlaySelected = Color.White.copy(alpha = NuvioTokens.Opacity.selected),
|
||||
overlayDisabled = Color.Black.copy(alpha = NuvioTokens.Opacity.disabled),
|
||||
shimmer = Color.White.copy(alpha = 0.10f),
|
||||
skeleton = Color.White.copy(alpha = 0.06f),
|
||||
playerControlsBackground = Color.Black.copy(alpha = 0.72f),
|
||||
playerControlsForeground = Color.White,
|
||||
playerTimelineTrack = Color.White.copy(alpha = 0.30f),
|
||||
playerTimelineFill = accent,
|
||||
playerSubtitlePreview = Color.Black.copy(alpha = 0.55f),
|
||||
playerBuffering = accent,
|
||||
parentalGuide = Color(0xFF5D1F1F),
|
||||
),
|
||||
spacing = NuvioSpacingTokens(
|
||||
screenHorizontal = NuvioTokens.Space.s16,
|
||||
screenTop = NuvioTokens.Space.s10,
|
||||
screenBottom = NuvioTokens.Space.s18,
|
||||
sectionGap = NuvioTokens.Space.s24,
|
||||
listGap = NuvioTokens.Space.s12,
|
||||
railGap = NuvioTokens.Space.s14,
|
||||
cardPadding = NuvioTokens.Space.s18,
|
||||
cardPaddingCompact = NuvioTokens.Space.s14,
|
||||
controlGap = NuvioTokens.Space.s8,
|
||||
dialogPadding = NuvioTokens.Space.s20,
|
||||
sheetPadding = NuvioTokens.Space.s20,
|
||||
playerOverlayPadding = NuvioTokens.Space.s24,
|
||||
),
|
||||
shapes = NuvioShapeTokens(
|
||||
card = RoundedCornerShape(NuvioTokens.Radius.card),
|
||||
compactCard = RoundedCornerShape(NuvioTokens.Radius.compactCard),
|
||||
sheet = RoundedCornerShape(NuvioTokens.Radius.sheet),
|
||||
dialog = RoundedCornerShape(NuvioTokens.Radius.dialog),
|
||||
button = RoundedCornerShape(NuvioTokens.Radius.button),
|
||||
chip = RoundedCornerShape(NuvioTokens.Radius.chip),
|
||||
poster = RoundedCornerShape(NuvioTokens.Radius.poster),
|
||||
avatar = RoundedCornerShape(NuvioTokens.Radius.avatar),
|
||||
playerPanel = RoundedCornerShape(NuvioTokens.Radius.playerPanel),
|
||||
),
|
||||
borders = NuvioBorderTokens(
|
||||
hairline = NuvioTokens.Border.hairline,
|
||||
thin = NuvioTokens.Border.thin,
|
||||
medium = NuvioTokens.Border.medium,
|
||||
),
|
||||
elevation = NuvioElevationTokens(
|
||||
flat = NuvioTokens.Elevation.none,
|
||||
raised = NuvioTokens.Elevation.raised,
|
||||
modal = NuvioTokens.Elevation.modal,
|
||||
overlay = NuvioTokens.Elevation.overlay,
|
||||
playerControls = NuvioTokens.Elevation.playerControls,
|
||||
),
|
||||
motion = NuvioMotionTokens(
|
||||
instantMillis = NuvioTokens.Motion.instantMillis,
|
||||
fastMillis = NuvioTokens.Motion.fastMillis,
|
||||
normalMillis = NuvioTokens.Motion.normalMillis,
|
||||
sheetEnterMillis = NuvioTokens.Motion.sheetEnterMillis,
|
||||
sheetExitMillis = NuvioTokens.Motion.sheetExitMillis,
|
||||
slowMillis = NuvioTokens.Motion.slowMillis,
|
||||
cinematicMillis = NuvioTokens.Motion.cinematicMillis,
|
||||
standard = NuvioTokens.Motion.standard,
|
||||
emphasized = NuvioTokens.Motion.emphasized,
|
||||
decelerate = NuvioTokens.Motion.decelerate,
|
||||
accelerate = NuvioTokens.Motion.accelerate,
|
||||
),
|
||||
opacity = NuvioOpacityTokens(
|
||||
invisible = NuvioTokens.Opacity.invisible,
|
||||
disabled = NuvioTokens.Opacity.disabled,
|
||||
secondary = NuvioTokens.Opacity.secondary,
|
||||
muted = NuvioTokens.Opacity.muted,
|
||||
selected = NuvioTokens.Opacity.selected,
|
||||
hover = NuvioTokens.Opacity.hover,
|
||||
pressed = NuvioTokens.Opacity.pressed,
|
||||
subtle = NuvioTokens.Opacity.subtle,
|
||||
medium = NuvioTokens.Opacity.medium,
|
||||
strong = NuvioTokens.Opacity.strong,
|
||||
overlayLight = NuvioTokens.Opacity.overlayLight,
|
||||
overlayMedium = NuvioTokens.Opacity.overlayMedium,
|
||||
overlayHeavy = NuvioTokens.Opacity.overlayHeavy,
|
||||
visible = NuvioTokens.Opacity.visible,
|
||||
),
|
||||
icons = NuvioIconTokens(
|
||||
xs = NuvioTokens.Icon.xs,
|
||||
sm = NuvioTokens.Icon.sm,
|
||||
md = NuvioTokens.Icon.md,
|
||||
lg = NuvioTokens.Icon.lg,
|
||||
xl = NuvioTokens.Icon.xl,
|
||||
xxl = NuvioTokens.Icon.xxl,
|
||||
),
|
||||
breakpoints = NuvioBreakpointTokens(
|
||||
phone = NuvioTokens.Breakpoint.phone,
|
||||
largePhone = NuvioTokens.Breakpoint.largePhone,
|
||||
tablet = NuvioTokens.Breakpoint.tablet,
|
||||
largeTablet = NuvioTokens.Breakpoint.largeTablet,
|
||||
desktop = NuvioTokens.Breakpoint.desktop,
|
||||
playerWide = NuvioTokens.Breakpoint.playerWide,
|
||||
),
|
||||
components = NuvioComponentTokens(
|
||||
navItemShape = RoundedCornerShape(NuvioTokens.Radius.xl),
|
||||
navIconSize = NuvioTokens.Icon.xl,
|
||||
navItemMaxWidth = 150.dp,
|
||||
sheetMaxWidth = 520.dp,
|
||||
dialogMaxWidth = 460.dp,
|
||||
chipHorizontalPadding = NuvioTokens.Space.s14,
|
||||
chipVerticalPadding = NuvioTokens.Space.s8,
|
||||
posterRadius = NuvioTokens.Radius.poster,
|
||||
avatarSize = NuvioTokens.Space.s48,
|
||||
playerPanelMaxWidth = 600.dp,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -5,7 +5,6 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
data class NuvioTypeScale(
|
||||
val labelXs: TextStyle,
|
||||
|
|
@ -22,16 +21,16 @@ data class NuvioTypeScale(
|
|||
|
||||
internal val LocalNuvioTypeScale = staticCompositionLocalOf {
|
||||
NuvioTypeScale(
|
||||
labelXs = TextStyle(fontSize = 11.sp, lineHeight = 14.sp, fontWeight = FontWeight.Medium),
|
||||
labelSm = TextStyle(fontSize = 12.sp, lineHeight = 15.sp, fontWeight = FontWeight.Medium),
|
||||
bodySm = TextStyle(fontSize = 13.sp, lineHeight = 18.sp, fontWeight = FontWeight.Normal),
|
||||
bodyMd = TextStyle(fontSize = 14.sp, lineHeight = 20.sp, fontWeight = FontWeight.Normal),
|
||||
bodyLg = TextStyle(fontSize = 16.sp, lineHeight = 22.sp, fontWeight = FontWeight.Medium),
|
||||
titleSm = TextStyle(fontSize = 18.sp, lineHeight = 22.sp, fontWeight = FontWeight.Bold),
|
||||
titleMd = TextStyle(fontSize = 22.sp, lineHeight = 26.sp, fontWeight = FontWeight.Bold),
|
||||
titleLg = TextStyle(fontSize = 28.sp, lineHeight = 32.sp, fontWeight = FontWeight.Bold),
|
||||
displaySm = TextStyle(fontSize = 32.sp, lineHeight = 36.sp, fontWeight = FontWeight.ExtraBold),
|
||||
displayMd = TextStyle(fontSize = 48.sp, lineHeight = 52.sp, fontWeight = FontWeight.ExtraBold),
|
||||
labelXs = TextStyle(fontSize = NuvioTokens.Type.labelXs, lineHeight = NuvioTokens.LineHeight.labelXs, fontWeight = FontWeight.Medium),
|
||||
labelSm = TextStyle(fontSize = NuvioTokens.Type.labelSm, lineHeight = NuvioTokens.LineHeight.labelSm, fontWeight = FontWeight.Medium),
|
||||
bodySm = TextStyle(fontSize = NuvioTokens.Type.bodySm, lineHeight = NuvioTokens.LineHeight.bodySm, fontWeight = FontWeight.Normal),
|
||||
bodyMd = TextStyle(fontSize = NuvioTokens.Type.bodyMd, lineHeight = NuvioTokens.LineHeight.bodyMd, fontWeight = FontWeight.Normal),
|
||||
bodyLg = TextStyle(fontSize = NuvioTokens.Type.bodyLg, lineHeight = NuvioTokens.LineHeight.bodyLg, fontWeight = FontWeight.Medium),
|
||||
titleSm = TextStyle(fontSize = NuvioTokens.Type.titleSm, lineHeight = NuvioTokens.LineHeight.titleSm, fontWeight = FontWeight.Bold),
|
||||
titleMd = TextStyle(fontSize = NuvioTokens.Type.titleMd, lineHeight = NuvioTokens.LineHeight.titleMd, fontWeight = FontWeight.Bold),
|
||||
titleLg = TextStyle(fontSize = NuvioTokens.Type.titleLg, lineHeight = NuvioTokens.LineHeight.titleLg, fontWeight = FontWeight.Bold),
|
||||
displaySm = TextStyle(fontSize = NuvioTokens.Type.displaySm, lineHeight = NuvioTokens.LineHeight.displaySm, fontWeight = FontWeight.ExtraBold),
|
||||
displayMd = TextStyle(fontSize = NuvioTokens.Type.displayMd, lineHeight = NuvioTokens.LineHeight.displayMd, fontWeight = FontWeight.ExtraBold),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import androidx.compose.ui.graphics.Color
|
|||
data class ThemeColorPalette(
|
||||
val secondary: Color,
|
||||
val secondaryVariant: Color,
|
||||
val nativeAccentHex: String,
|
||||
val onSecondary: Color = Color.White,
|
||||
val onSecondaryVariant: Color = Color.White,
|
||||
val focusRing: Color,
|
||||
|
|
@ -19,6 +20,7 @@ object ThemeColors {
|
|||
val Crimson = ThemeColorPalette(
|
||||
secondary = Color(0xFFE53935),
|
||||
secondaryVariant = Color(0xFFC62828),
|
||||
nativeAccentHex = "#E53935",
|
||||
focusRing = Color(0xFFFF5252),
|
||||
focusBackground = Color(0xFF3D1A1A),
|
||||
background = Color(0xFF0D0D0D),
|
||||
|
|
@ -29,6 +31,7 @@ object ThemeColors {
|
|||
val Ocean = ThemeColorPalette(
|
||||
secondary = Color(0xFF1E88E5),
|
||||
secondaryVariant = Color(0xFF1565C0),
|
||||
nativeAccentHex = "#1E88E5",
|
||||
focusRing = Color(0xFF42A5F5),
|
||||
focusBackground = Color(0xFF1A2D3D),
|
||||
background = Color(0xFF0D0D0F),
|
||||
|
|
@ -39,6 +42,7 @@ object ThemeColors {
|
|||
val Violet = ThemeColorPalette(
|
||||
secondary = Color(0xFF8E24AA),
|
||||
secondaryVariant = Color(0xFF6A1B9A),
|
||||
nativeAccentHex = "#8E24AA",
|
||||
focusRing = Color(0xFFAB47BC),
|
||||
focusBackground = Color(0xFF2D1A3D),
|
||||
background = Color(0xFF0D0D0F),
|
||||
|
|
@ -49,6 +53,7 @@ object ThemeColors {
|
|||
val Emerald = ThemeColorPalette(
|
||||
secondary = Color(0xFF43A047),
|
||||
secondaryVariant = Color(0xFF2E7D32),
|
||||
nativeAccentHex = "#43A047",
|
||||
focusRing = Color(0xFF66BB6A),
|
||||
focusBackground = Color(0xFF1A3D1E),
|
||||
background = Color(0xFF0D0D0D),
|
||||
|
|
@ -59,6 +64,7 @@ object ThemeColors {
|
|||
val Amber = ThemeColorPalette(
|
||||
secondary = Color(0xFFFB8C00),
|
||||
secondaryVariant = Color(0xFFEF6C00),
|
||||
nativeAccentHex = "#FB8C00",
|
||||
focusRing = Color(0xFFFFA726),
|
||||
focusBackground = Color(0xFF3D2D1A),
|
||||
background = Color(0xFF0F0D0D),
|
||||
|
|
@ -69,6 +75,7 @@ object ThemeColors {
|
|||
val Rose = ThemeColorPalette(
|
||||
secondary = Color(0xFFD81B60),
|
||||
secondaryVariant = Color(0xFFC2185B),
|
||||
nativeAccentHex = "#D81B60",
|
||||
focusRing = Color(0xFFEC407A),
|
||||
focusBackground = Color(0xFF3D1A2D),
|
||||
background = Color(0xFF0D0D0D),
|
||||
|
|
@ -79,6 +86,7 @@ object ThemeColors {
|
|||
val White = ThemeColorPalette(
|
||||
secondary = Color(0xFFF5F5F5),
|
||||
secondaryVariant = Color(0xFFE0E0E0),
|
||||
nativeAccentHex = "#F5F5F5",
|
||||
onSecondary = Color(0xFF111111),
|
||||
onSecondaryVariant = Color(0xFF111111),
|
||||
focusRing = Color(0xFFFFFFFF),
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ import androidx.compose.material3.Text
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.nuvio.app.features.trakt.TraktListTab
|
||||
import nuvio.composeapp.generated.resources.Res
|
||||
import nuvio.composeapp.generated.resources.action_cancel
|
||||
|
|
@ -49,35 +48,36 @@ fun TraktListPickerDialog(
|
|||
onDismiss: () -> Unit,
|
||||
) {
|
||||
if (!visible) return
|
||||
val tokens = MaterialTheme.nuvio
|
||||
|
||||
BasicAlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = MaterialTheme.colorScheme.surface,
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
color = tokens.colors.surfaceDialog,
|
||||
shape = RoundedCornerShape(NuvioTokens.Radius.xl),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(18.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
modifier = Modifier.padding(tokens.spacing.cardPadding),
|
||||
verticalArrangement = Arrangement.spacedBy(tokens.spacing.listGap),
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
color = tokens.colors.textPrimary,
|
||||
)
|
||||
Text(
|
||||
text = stringResource(Res.string.compose_trakt_list_picker_subtitle),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
color = tokens.colors.textMuted,
|
||||
)
|
||||
|
||||
if (!errorMessage.isNullOrBlank()) {
|
||||
Text(
|
||||
text = errorMessage,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
color = tokens.colors.danger,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -85,21 +85,21 @@ fun TraktListPickerDialog(
|
|||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(280.dp),
|
||||
.height(NuvioTokens.Space.s80 + NuvioTokens.Space.s80 + NuvioTokens.Space.s80 + NuvioTokens.Space.s40),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(tokens.spacing.listGap),
|
||||
) {
|
||||
CircularProgressIndicator(
|
||||
strokeWidth = 2.dp,
|
||||
modifier = Modifier.size(24.dp),
|
||||
strokeWidth = tokens.borders.medium,
|
||||
modifier = Modifier.size(tokens.icons.lg),
|
||||
)
|
||||
Text(
|
||||
text = stringResource(Res.string.compose_trakt_list_picker_loading),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
color = tokens.colors.textMuted,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -107,8 +107,8 @@ fun TraktListPickerDialog(
|
|||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(280.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
.height(NuvioTokens.Space.s80 + NuvioTokens.Space.s80 + NuvioTokens.Space.s80 + NuvioTokens.Space.s40),
|
||||
verticalArrangement = Arrangement.spacedBy(tokens.spacing.controlGap),
|
||||
) {
|
||||
items(items = tabs, key = { it.key }) { tab ->
|
||||
val selected = membership[tab.key] == true
|
||||
|
|
@ -117,27 +117,27 @@ fun TraktListPickerDialog(
|
|||
.fillMaxWidth()
|
||||
.background(
|
||||
color = if (selected) {
|
||||
MaterialTheme.colorScheme.primary.copy(alpha = 0.14f)
|
||||
tokens.colors.accent.copy(alpha = tokens.opacity.selected)
|
||||
} else {
|
||||
MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.4f)
|
||||
tokens.colors.surfaceCard.copy(alpha = tokens.opacity.medium)
|
||||
},
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
shape = tokens.shapes.compactCard,
|
||||
)
|
||||
.clickable(enabled = !isPending) { onToggle(tab.key) }
|
||||
.padding(horizontal = 14.dp, vertical = 12.dp),
|
||||
.padding(horizontal = NuvioTokens.Space.s14, vertical = tokens.spacing.listGap),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = tab.title,
|
||||
modifier = Modifier.weight(1f),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
color = tokens.colors.textPrimary,
|
||||
)
|
||||
if (selected) {
|
||||
androidx.compose.material3.Icon(
|
||||
imageVector = Icons.Rounded.Check,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
tint = tokens.colors.accent,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -147,14 +147,14 @@ fun TraktListPickerDialog(
|
|||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp, Alignment.End),
|
||||
horizontalArrangement = Arrangement.spacedBy(NuvioTokens.Space.s10, Alignment.End),
|
||||
) {
|
||||
Button(
|
||||
onClick = onDismiss,
|
||||
enabled = !isPending,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||
contentColor = MaterialTheme.colorScheme.onSurface,
|
||||
containerColor = tokens.colors.surfaceCard,
|
||||
contentColor = tokens.colors.textPrimary,
|
||||
),
|
||||
) {
|
||||
Text(stringResource(Res.string.action_cancel))
|
||||
|
|
@ -165,9 +165,9 @@ fun TraktListPickerDialog(
|
|||
) {
|
||||
if (isPending) {
|
||||
CircularProgressIndicator(
|
||||
color = MaterialTheme.colorScheme.onPrimary,
|
||||
strokeWidth = 2.dp,
|
||||
modifier = Modifier.size(16.dp),
|
||||
color = tokens.colors.onAccent,
|
||||
strokeWidth = tokens.borders.medium,
|
||||
modifier = Modifier.size(tokens.icons.sm),
|
||||
)
|
||||
} else {
|
||||
Text(stringResource(Res.string.action_save))
|
||||
|
|
|
|||
|
|
@ -30,10 +30,8 @@ import androidx.compose.foundation.lazy.items
|
|||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
|
||||
import androidx.compose.material.icons.rounded.Check
|
||||
import androidx.compose.material.icons.rounded.Refresh
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Icon
|
||||
|
|
@ -55,10 +53,10 @@ import androidx.compose.ui.layout.ContentScale
|
|||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import coil3.compose.AsyncImage
|
||||
import com.nuvio.app.core.ui.NuvioTokens
|
||||
import com.nuvio.app.core.ui.nuvio
|
||||
import com.nuvio.app.features.debrid.DebridSettingsRepository
|
||||
import com.nuvio.app.features.details.MetaVideo
|
||||
import com.nuvio.app.features.streams.StreamItem
|
||||
|
|
@ -97,12 +95,12 @@ fun PlayerEpisodesPanel(
|
|||
onDismiss: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val colorScheme = MaterialTheme.colorScheme
|
||||
val tokens = MaterialTheme.nuvio
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
enter = fadeIn(tween(200)),
|
||||
exit = fadeOut(tween(200)),
|
||||
enter = fadeIn(tween(NuvioTokens.Motion.normalMillis)),
|
||||
exit = fadeOut(tween(NuvioTokens.Motion.normalMillis)),
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
|
|
@ -112,22 +110,24 @@ fun PlayerEpisodesPanel(
|
|||
interactionSource = remember { MutableInteractionSource() },
|
||||
onClick = onDismiss,
|
||||
)
|
||||
.background(colorScheme.scrim.copy(alpha = 0.52f)),
|
||||
.background(tokens.colors.overlayScrim.copy(alpha = tokens.opacity.medium)),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
enter = slideInVertically(tween(300)) { it / 3 } + fadeIn(tween(300)),
|
||||
exit = slideOutVertically(tween(250)) { it / 3 } + fadeOut(tween(250)),
|
||||
enter = slideInVertically(tween(NuvioTokens.Motion.sheetEnterMillis)) { it / 3 } +
|
||||
fadeIn(tween(NuvioTokens.Motion.sheetEnterMillis)),
|
||||
exit = slideOutVertically(tween(NuvioTokens.Motion.sheetExitMillis)) { it / 3 } +
|
||||
fadeOut(tween(NuvioTokens.Motion.sheetExitMillis)),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.widthIn(max = 520.dp)
|
||||
.widthIn(max = tokens.components.playerPanelMaxWidth)
|
||||
.fillMaxWidth(0.92f)
|
||||
.heightIn(max = 620.dp)
|
||||
.clip(RoundedCornerShape(24.dp))
|
||||
.background(colorScheme.surface)
|
||||
.border(1.dp, colorScheme.outlineVariant.copy(alpha = 0.8f), RoundedCornerShape(24.dp))
|
||||
.heightIn(max = tokens.components.dialogMaxWidth + NuvioTokens.Space.s64)
|
||||
.clip(tokens.shapes.playerPanel)
|
||||
.background(tokens.colors.surfaceSheet)
|
||||
.border(tokens.borders.thin, tokens.colors.borderDefault, tokens.shapes.playerPanel)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
|
|
@ -186,7 +186,7 @@ private fun EpisodesListSubView(
|
|||
onEpisodeSelected: (MetaVideo) -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
val colorScheme = MaterialTheme.colorScheme
|
||||
val tokens = MaterialTheme.nuvio
|
||||
|
||||
val groupedEpisodes = remember(episodes) {
|
||||
episodes
|
||||
|
|
@ -251,14 +251,14 @@ private fun EpisodesListSubView(
|
|||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 20.dp, vertical = 16.dp),
|
||||
.padding(horizontal = tokens.spacing.sheetPadding, vertical = tokens.spacing.cardPadding),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(Res.string.compose_player_panel_episodes),
|
||||
color = colorScheme.onSurface,
|
||||
fontSize = 18.sp,
|
||||
color = tokens.colors.textPrimary,
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
PanelChipButton(label = stringResource(Res.string.action_close), onClick = onDismiss)
|
||||
|
|
@ -270,9 +270,9 @@ private fun EpisodesListSubView(
|
|||
state = seasonListState,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 20.dp)
|
||||
.padding(bottom = 12.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
.padding(horizontal = tokens.spacing.sheetPadding)
|
||||
.padding(bottom = tokens.spacing.listGap),
|
||||
horizontalArrangement = Arrangement.spacedBy(tokens.spacing.controlGap),
|
||||
) {
|
||||
items(availableSeasons, key = { season -> season }) { season ->
|
||||
val label = if (season == 0) {
|
||||
|
|
@ -297,21 +297,21 @@ private fun EpisodesListSubView(
|
|||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 40.dp),
|
||||
.padding(vertical = NuvioTokens.Space.s40),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(Res.string.compose_player_no_episodes_available),
|
||||
color = colorScheme.onSurfaceVariant,
|
||||
fontSize = 14.sp,
|
||||
color = tokens.colors.textMuted,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
LazyColumn(
|
||||
state = episodeListState,
|
||||
modifier = Modifier.padding(horizontal = 12.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
contentPadding = androidx.compose.foundation.layout.PaddingValues(bottom = 16.dp),
|
||||
modifier = Modifier.padding(horizontal = tokens.spacing.cardPaddingCompact),
|
||||
verticalArrangement = Arrangement.spacedBy(NuvioTokens.Space.s4),
|
||||
contentPadding = androidx.compose.foundation.layout.PaddingValues(bottom = tokens.spacing.cardPadding),
|
||||
) {
|
||||
itemsIndexed(
|
||||
items = seasonEpisodes,
|
||||
|
|
@ -352,27 +352,27 @@ private fun EpisodeRow(
|
|||
blurUnwatchedEpisodes: Boolean,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
val colorScheme = MaterialTheme.colorScheme
|
||||
val tokens = MaterialTheme.nuvio
|
||||
val shouldBlurArtwork = blurUnwatchedEpisodes && !isWatched && !isCurrent
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.clip(tokens.shapes.compactCard)
|
||||
.background(
|
||||
if (isCurrent) colorScheme.primaryContainer.copy(alpha = 0.55f) else Color.Transparent,
|
||||
if (isCurrent) tokens.colors.overlaySelected else Color.Transparent,
|
||||
)
|
||||
.then(
|
||||
if (isCurrent) {
|
||||
Modifier.border(1.dp, colorScheme.primary.copy(alpha = 0.45f), RoundedCornerShape(12.dp))
|
||||
Modifier.border(tokens.borders.thin, tokens.colors.borderSelected, tokens.shapes.compactCard)
|
||||
} else {
|
||||
Modifier
|
||||
},
|
||||
)
|
||||
.clickable(onClick = onClick)
|
||||
.padding(horizontal = 12.dp, vertical = 10.dp),
|
||||
.padding(horizontal = NuvioTokens.Space.s12, vertical = NuvioTokens.Space.s10),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(tokens.spacing.listGap),
|
||||
) {
|
||||
// Thumbnail
|
||||
if (episode.thumbnail != null) {
|
||||
|
|
@ -380,10 +380,10 @@ private fun EpisodeRow(
|
|||
model = episode.thumbnail,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.width(80.dp)
|
||||
.height(48.dp)
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.then(if (shouldBlurArtwork) Modifier.blur(18.dp) else Modifier),
|
||||
.width(NuvioTokens.Space.s80)
|
||||
.height(NuvioTokens.Space.s48)
|
||||
.clip(tokens.shapes.compactCard)
|
||||
.then(if (shouldBlurArtwork) Modifier.blur(NuvioTokens.Space.s18) else Modifier),
|
||||
contentScale = ContentScale.Crop,
|
||||
)
|
||||
}
|
||||
|
|
@ -391,7 +391,7 @@ private fun EpisodeRow(
|
|||
Column(modifier = Modifier.weight(1f)) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(tokens.spacing.controlGap),
|
||||
) {
|
||||
val episodeLabel = buildString {
|
||||
if (episode.season != null && episode.episode != null) {
|
||||
|
|
@ -409,22 +409,22 @@ private fun EpisodeRow(
|
|||
if (episodeLabel.isNotBlank()) {
|
||||
Text(
|
||||
text = episodeLabel,
|
||||
color = colorScheme.onSurfaceVariant,
|
||||
fontSize = 11.sp,
|
||||
color = tokens.colors.textMuted,
|
||||
fontSize = NuvioTokens.Type.labelXs,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
}
|
||||
if (isCurrent) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(999.dp))
|
||||
.background(colorScheme.primaryContainer)
|
||||
.padding(horizontal = 6.dp, vertical = 2.dp),
|
||||
.clip(tokens.shapes.chip)
|
||||
.background(tokens.colors.accent)
|
||||
.padding(horizontal = NuvioTokens.Space.s6, vertical = NuvioTokens.Space.s2),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(Res.string.compose_player_playing),
|
||||
color = colorScheme.onPrimaryContainer,
|
||||
fontSize = 9.sp,
|
||||
color = tokens.colors.onAccent,
|
||||
fontSize = NuvioTokens.Type.labelXs,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
}
|
||||
|
|
@ -432,8 +432,8 @@ private fun EpisodeRow(
|
|||
}
|
||||
Text(
|
||||
text = episode.title,
|
||||
color = colorScheme.onSurface,
|
||||
fontSize = 13.sp,
|
||||
color = tokens.colors.textPrimary,
|
||||
fontSize = NuvioTokens.Type.bodySm,
|
||||
fontWeight = FontWeight.Medium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
|
|
@ -441,8 +441,8 @@ private fun EpisodeRow(
|
|||
episode.overview?.let { overview ->
|
||||
Text(
|
||||
text = overview,
|
||||
color = colorScheme.onSurfaceVariant,
|
||||
fontSize = 11.sp,
|
||||
color = tokens.colors.textSecondary,
|
||||
fontSize = NuvioTokens.Type.labelXs,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
|
|
@ -462,7 +462,7 @@ private fun EpisodeStreamsSubView(
|
|||
onReload: () -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
val colorScheme = MaterialTheme.colorScheme
|
||||
val tokens = MaterialTheme.nuvio
|
||||
val debridSettings by remember {
|
||||
DebridSettingsRepository.ensureLoaded()
|
||||
DebridSettingsRepository.uiState
|
||||
|
|
@ -476,14 +476,14 @@ private fun EpisodeStreamsSubView(
|
|||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 20.dp, vertical = 16.dp),
|
||||
.padding(horizontal = tokens.spacing.sheetPadding, vertical = tokens.spacing.cardPadding),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(Res.string.compose_player_panel_streams),
|
||||
color = colorScheme.onSurface,
|
||||
fontSize = 18.sp,
|
||||
color = tokens.colors.textPrimary,
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
PanelChipButton(label = stringResource(Res.string.action_close), onClick = onDismiss)
|
||||
|
|
@ -493,10 +493,10 @@ private fun EpisodeStreamsSubView(
|
|||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 20.dp)
|
||||
.padding(bottom = 8.dp),
|
||||
.padding(horizontal = tokens.spacing.sheetPadding)
|
||||
.padding(bottom = tokens.spacing.controlGap),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(tokens.spacing.controlGap),
|
||||
) {
|
||||
PanelChipButton(
|
||||
label = stringResource(Res.string.action_back),
|
||||
|
|
@ -524,8 +524,8 @@ private fun EpisodeStreamsSubView(
|
|||
append(episode.title)
|
||||
}
|
||||
},
|
||||
color = colorScheme.onSurfaceVariant,
|
||||
fontSize = 12.sp,
|
||||
color = tokens.colors.textMuted,
|
||||
fontSize = NuvioTokens.Type.labelSm,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.weight(1f),
|
||||
|
|
@ -541,9 +541,9 @@ private fun EpisodeStreamsSubView(
|
|||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.horizontalScroll(rememberScrollState())
|
||||
.padding(horizontal = 20.dp)
|
||||
.padding(bottom = 12.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
.padding(horizontal = tokens.spacing.sheetPadding)
|
||||
.padding(bottom = tokens.spacing.listGap),
|
||||
horizontalArrangement = Arrangement.spacedBy(tokens.spacing.controlGap),
|
||||
) {
|
||||
AddonFilterChip(
|
||||
label = stringResource(Res.string.collections_tab_all),
|
||||
|
|
@ -569,13 +569,13 @@ private fun EpisodeStreamsSubView(
|
|||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 40.dp),
|
||||
.padding(vertical = NuvioTokens.Space.s40),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
CircularProgressIndicator(
|
||||
color = colorScheme.primary,
|
||||
strokeWidth = 2.dp,
|
||||
modifier = Modifier.size(28.dp),
|
||||
color = tokens.colors.accent,
|
||||
strokeWidth = tokens.borders.medium,
|
||||
modifier = Modifier.size(tokens.icons.lg + NuvioTokens.Space.s4),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -584,13 +584,13 @@ private fun EpisodeStreamsSubView(
|
|||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 40.dp),
|
||||
.padding(vertical = NuvioTokens.Space.s40),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(Res.string.compose_player_no_streams_found),
|
||||
color = colorScheme.onSurfaceVariant,
|
||||
fontSize = 14.sp,
|
||||
color = tokens.colors.textMuted,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -598,9 +598,9 @@ private fun EpisodeStreamsSubView(
|
|||
else -> {
|
||||
val streams = streamsUiState.filteredGroups.flatMap { it.streams }
|
||||
LazyColumn(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(6.dp),
|
||||
contentPadding = androidx.compose.foundation.layout.PaddingValues(bottom = 16.dp),
|
||||
modifier = Modifier.padding(horizontal = tokens.spacing.cardPadding),
|
||||
verticalArrangement = Arrangement.spacedBy(NuvioTokens.Space.s6),
|
||||
contentPadding = androidx.compose.foundation.layout.PaddingValues(bottom = tokens.spacing.cardPadding),
|
||||
) {
|
||||
itemsIndexed(
|
||||
items = streams,
|
||||
|
|
@ -624,23 +624,23 @@ private fun EpisodeSourceStreamRow(
|
|||
enabled: Boolean,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
val colorScheme = MaterialTheme.colorScheme
|
||||
val tokens = MaterialTheme.nuvio
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.background(colorScheme.surfaceVariant.copy(alpha = 0.35f))
|
||||
.clip(tokens.shapes.compactCard)
|
||||
.background(tokens.colors.surfaceCard)
|
||||
.clickable(enabled = enabled, onClick = onClick)
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
.padding(horizontal = tokens.spacing.cardPadding, vertical = tokens.spacing.listGap),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(tokens.spacing.listGap),
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = stream.streamLabel,
|
||||
color = colorScheme.onSurface,
|
||||
fontSize = 14.sp,
|
||||
color = tokens.colors.textPrimary,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = FontWeight.Medium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
|
|
@ -649,8 +649,8 @@ private fun EpisodeSourceStreamRow(
|
|||
if (subtitle != stream.streamLabel) {
|
||||
Text(
|
||||
text = subtitle,
|
||||
color = colorScheme.onSurfaceVariant,
|
||||
fontSize = 12.sp,
|
||||
color = tokens.colors.textSecondary,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
|
|
@ -658,8 +658,8 @@ private fun EpisodeSourceStreamRow(
|
|||
}
|
||||
Text(
|
||||
text = stream.addonName,
|
||||
color = colorScheme.onSurfaceVariant,
|
||||
fontSize = 11.sp,
|
||||
color = tokens.colors.textMuted,
|
||||
fontSize = NuvioTokens.Type.labelXs,
|
||||
fontStyle = FontStyle.Italic,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
|
|
|
|||
|
|
@ -23,13 +23,10 @@ import androidx.compose.foundation.layout.height
|
|||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Refresh
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
|
|
@ -43,13 +40,12 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.nuvio.app.core.ui.NuvioTokens
|
||||
import com.nuvio.app.core.ui.nuvio
|
||||
import com.nuvio.app.features.debrid.DebridSettingsRepository
|
||||
import com.nuvio.app.features.streams.StreamBadge
|
||||
import com.nuvio.app.features.streams.StreamBadgeImage
|
||||
|
|
@ -74,7 +70,7 @@ fun PlayerSourcesPanel(
|
|||
onDismiss: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val colorScheme = MaterialTheme.colorScheme
|
||||
val tokens = MaterialTheme.nuvio
|
||||
val debridSettings by remember {
|
||||
DebridSettingsRepository.ensureLoaded()
|
||||
DebridSettingsRepository.uiState
|
||||
|
|
@ -86,8 +82,8 @@ fun PlayerSourcesPanel(
|
|||
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
enter = fadeIn(tween(200)),
|
||||
exit = fadeOut(tween(200)),
|
||||
enter = fadeIn(tween(NuvioTokens.Motion.normalMillis)),
|
||||
exit = fadeOut(tween(NuvioTokens.Motion.normalMillis)),
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
|
|
@ -97,22 +93,24 @@ fun PlayerSourcesPanel(
|
|||
interactionSource = remember { MutableInteractionSource() },
|
||||
onClick = onDismiss,
|
||||
)
|
||||
.background(colorScheme.scrim.copy(alpha = 0.52f)),
|
||||
.background(tokens.colors.overlayScrim.copy(alpha = tokens.opacity.medium)),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
enter = slideInVertically(tween(300)) { it / 3 } + fadeIn(tween(300)),
|
||||
exit = slideOutVertically(tween(250)) { it / 3 } + fadeOut(tween(250)),
|
||||
enter = slideInVertically(tween(NuvioTokens.Motion.sheetEnterMillis)) { it / 3 } +
|
||||
fadeIn(tween(NuvioTokens.Motion.sheetEnterMillis)),
|
||||
exit = slideOutVertically(tween(NuvioTokens.Motion.sheetExitMillis)) { it / 3 } +
|
||||
fadeOut(tween(NuvioTokens.Motion.sheetExitMillis)),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.widthIn(max = 520.dp)
|
||||
.widthIn(max = tokens.components.playerPanelMaxWidth)
|
||||
.fillMaxWidth(0.92f)
|
||||
.heightIn(max = 600.dp)
|
||||
.clip(RoundedCornerShape(24.dp))
|
||||
.background(colorScheme.surface)
|
||||
.border(1.dp, colorScheme.outlineVariant.copy(alpha = 0.8f), RoundedCornerShape(24.dp))
|
||||
.heightIn(max = tokens.components.dialogMaxWidth + NuvioTokens.Space.s40)
|
||||
.clip(tokens.shapes.playerPanel)
|
||||
.background(tokens.colors.surfaceSheet)
|
||||
.border(tokens.borders.thin, tokens.colors.borderDefault, tokens.shapes.playerPanel)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
|
|
@ -124,17 +122,17 @@ fun PlayerSourcesPanel(
|
|||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 20.dp, vertical = 16.dp),
|
||||
.padding(horizontal = tokens.spacing.sheetPadding, vertical = tokens.spacing.cardPadding),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(Res.string.compose_player_panel_sources),
|
||||
color = colorScheme.onSurface,
|
||||
fontSize = 18.sp,
|
||||
color = tokens.colors.textPrimary,
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(tokens.spacing.controlGap)) {
|
||||
PanelChipButton(
|
||||
label = stringResource(Res.string.compose_action_reload),
|
||||
icon = Icons.Rounded.Refresh,
|
||||
|
|
@ -156,9 +154,9 @@ fun PlayerSourcesPanel(
|
|||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.horizontalScroll(rememberScrollState())
|
||||
.padding(horizontal = 20.dp)
|
||||
.padding(bottom = 12.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
.padding(horizontal = tokens.spacing.sheetPadding)
|
||||
.padding(bottom = tokens.spacing.listGap),
|
||||
horizontalArrangement = Arrangement.spacedBy(tokens.spacing.controlGap),
|
||||
) {
|
||||
AddonFilterChip(
|
||||
label = stringResource(Res.string.collections_tab_all),
|
||||
|
|
@ -184,13 +182,13 @@ fun PlayerSourcesPanel(
|
|||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 40.dp),
|
||||
.padding(vertical = NuvioTokens.Space.s40),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
CircularProgressIndicator(
|
||||
color = colorScheme.primary,
|
||||
strokeWidth = 2.dp,
|
||||
modifier = Modifier.size(28.dp),
|
||||
color = tokens.colors.accent,
|
||||
strokeWidth = tokens.borders.medium,
|
||||
modifier = Modifier.size(tokens.icons.lg + NuvioTokens.Space.s4),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -199,13 +197,13 @@ fun PlayerSourcesPanel(
|
|||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 40.dp),
|
||||
.padding(vertical = NuvioTokens.Space.s40),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(Res.string.compose_player_no_streams_found),
|
||||
color = colorScheme.onSurfaceVariant,
|
||||
fontSize = 14.sp,
|
||||
color = tokens.colors.textMuted,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -213,9 +211,9 @@ fun PlayerSourcesPanel(
|
|||
else -> {
|
||||
val streams = streamsUiState.filteredGroups.flatMap { it.streams }
|
||||
LazyColumn(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(6.dp),
|
||||
contentPadding = androidx.compose.foundation.layout.PaddingValues(bottom = 16.dp),
|
||||
modifier = Modifier.padding(horizontal = tokens.spacing.cardPadding),
|
||||
verticalArrangement = Arrangement.spacedBy(NuvioTokens.Space.s6),
|
||||
contentPadding = androidx.compose.foundation.layout.PaddingValues(bottom = tokens.spacing.cardPadding),
|
||||
) {
|
||||
itemsIndexed(
|
||||
items = streams,
|
||||
|
|
@ -254,36 +252,36 @@ private fun SourceStreamRow(
|
|||
badgePlacement: StreamBadgePlacement,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
val colorScheme = MaterialTheme.colorScheme
|
||||
val cardShape = RoundedCornerShape(12.dp)
|
||||
val tokens = MaterialTheme.nuvio
|
||||
val cardShape = tokens.shapes.compactCard
|
||||
val badgeImages = stream.badges.filter { it.imageURL.isNotBlank() }
|
||||
val hasBadgeMetadata = badgeImages.isNotEmpty() || (showFileSizeBadges && stream.behaviorHints.videoSize != null)
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = 68.dp)
|
||||
.heightIn(min = NuvioTokens.Space.s64 + NuvioTokens.Space.s4)
|
||||
.shadow(
|
||||
elevation = 2.dp,
|
||||
elevation = tokens.elevation.raised,
|
||||
shape = cardShape,
|
||||
ambientColor = Color.Black.copy(alpha = 0.04f),
|
||||
spotColor = Color.Black.copy(alpha = 0.04f),
|
||||
ambientColor = tokens.colors.overlayScrim.copy(alpha = tokens.opacity.subtle),
|
||||
spotColor = tokens.colors.overlayScrim.copy(alpha = tokens.opacity.subtle),
|
||||
)
|
||||
.clip(cardShape)
|
||||
.background(
|
||||
if (isCurrent) colorScheme.primaryContainer.copy(alpha = 0.4f) else Color.White.copy(alpha = 0.05f),
|
||||
if (isCurrent) tokens.colors.overlaySelected else tokens.colors.surfaceCard,
|
||||
)
|
||||
.then(
|
||||
if (isCurrent) {
|
||||
Modifier.border(1.dp, colorScheme.primary.copy(alpha = 0.45f), cardShape)
|
||||
Modifier.border(tokens.borders.thin, tokens.colors.borderSelected, cardShape)
|
||||
} else {
|
||||
Modifier
|
||||
},
|
||||
)
|
||||
.clickable(enabled = enabled, onClick = onClick)
|
||||
.padding(14.dp),
|
||||
.padding(tokens.spacing.cardPaddingCompact),
|
||||
verticalAlignment = Alignment.Top,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(tokens.spacing.listGap),
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
if (hasBadgeMetadata && badgePlacement == StreamBadgePlacement.TOP) {
|
||||
|
|
@ -292,35 +290,33 @@ private fun SourceStreamRow(
|
|||
stream = stream,
|
||||
showFileSizeBadges = showFileSizeBadges,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
Spacer(modifier = Modifier.height(NuvioTokens.Space.s6))
|
||||
}
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(tokens.spacing.controlGap),
|
||||
) {
|
||||
Text(
|
||||
text = stream.streamLabel,
|
||||
color = colorScheme.onSurface,
|
||||
color = tokens.colors.textPrimary,
|
||||
style = MaterialTheme.typography.bodyMedium.copy(
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
lineHeight = 20.sp,
|
||||
letterSpacing = 0.1.sp,
|
||||
letterSpacing = NuvioTokens.LetterSpacing.none,
|
||||
),
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
if (isCurrent) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(999.dp))
|
||||
.background(colorScheme.primaryContainer)
|
||||
.padding(horizontal = 8.dp, vertical = 3.dp),
|
||||
.clip(tokens.shapes.chip)
|
||||
.background(tokens.colors.accent)
|
||||
.padding(horizontal = NuvioTokens.Space.s8, vertical = NuvioTokens.Space.s3),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(Res.string.compose_player_playing),
|
||||
color = colorScheme.onPrimaryContainer,
|
||||
fontSize = 10.sp,
|
||||
color = tokens.colors.onAccent,
|
||||
fontSize = NuvioTokens.Type.labelXs,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
}
|
||||
|
|
@ -329,18 +325,15 @@ private fun SourceStreamRow(
|
|||
|
||||
val subtitle = stream.streamSubtitle
|
||||
if (!subtitle.isNullOrBlank() && subtitle != stream.streamLabel) {
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
Spacer(modifier = Modifier.height(NuvioTokens.Space.s2))
|
||||
Text(
|
||||
text = subtitle,
|
||||
style = MaterialTheme.typography.bodySmall.copy(
|
||||
fontSize = 12.sp,
|
||||
lineHeight = 18.sp,
|
||||
),
|
||||
color = colorScheme.onSurfaceVariant,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = tokens.colors.textSecondary,
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
Spacer(modifier = Modifier.height(NuvioTokens.Space.s6))
|
||||
if (badgePlacement == StreamBadgePlacement.BOTTOM) {
|
||||
SourceStreamBadgeRow(
|
||||
badgeImages = badgeImages,
|
||||
|
|
@ -349,9 +342,9 @@ private fun SourceStreamRow(
|
|||
) {
|
||||
Text(
|
||||
text = stream.addonName,
|
||||
modifier = if (hasBadgeMetadata) Modifier.padding(start = 4.dp) else Modifier,
|
||||
color = colorScheme.onSurfaceVariant,
|
||||
fontSize = 11.sp,
|
||||
modifier = if (hasBadgeMetadata) Modifier.padding(start = NuvioTokens.Space.s4) else Modifier,
|
||||
color = tokens.colors.textMuted,
|
||||
fontSize = NuvioTokens.Type.labelXs,
|
||||
fontStyle = FontStyle.Italic,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
|
|
@ -360,8 +353,8 @@ private fun SourceStreamRow(
|
|||
} else {
|
||||
Text(
|
||||
text = stream.addonName,
|
||||
color = colorScheme.onSurfaceVariant,
|
||||
fontSize = 11.sp,
|
||||
color = tokens.colors.textMuted,
|
||||
fontSize = NuvioTokens.Type.labelXs,
|
||||
fontStyle = FontStyle.Italic,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
|
|
@ -382,7 +375,7 @@ private fun SourceStreamBadgeRow(
|
|||
Row(
|
||||
modifier = modifier.horizontalScroll(rememberScrollState()),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(NuvioTokens.Space.s4),
|
||||
) {
|
||||
badgeImages.forEach { badge ->
|
||||
StreamBadgeImage(badge = badge)
|
||||
|
|
@ -402,46 +395,46 @@ internal fun AddonFilterChip(
|
|||
hasError: Boolean = false,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
val colorScheme = MaterialTheme.colorScheme
|
||||
val tokens = MaterialTheme.nuvio
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(20.dp))
|
||||
.clip(tokens.shapes.chip)
|
||||
.background(
|
||||
when {
|
||||
isSelected -> colorScheme.primaryContainer
|
||||
else -> colorScheme.surfaceVariant.copy(alpha = 0.92f)
|
||||
isSelected -> tokens.colors.overlaySelected
|
||||
else -> tokens.colors.surfacePopover
|
||||
},
|
||||
)
|
||||
.then(
|
||||
if (isSelected) {
|
||||
Modifier.border(1.dp, colorScheme.primary.copy(alpha = 0.4f), RoundedCornerShape(20.dp))
|
||||
Modifier.border(tokens.borders.thin, tokens.colors.borderSelected, tokens.shapes.chip)
|
||||
} else {
|
||||
Modifier.border(1.dp, colorScheme.outlineVariant.copy(alpha = 0.7f), RoundedCornerShape(20.dp))
|
||||
Modifier.border(tokens.borders.thin, tokens.colors.borderSubtle, tokens.shapes.chip)
|
||||
},
|
||||
)
|
||||
.clickable(onClick = onClick)
|
||||
.padding(horizontal = 14.dp, vertical = 8.dp),
|
||||
.padding(horizontal = NuvioTokens.Space.s14, vertical = NuvioTokens.Space.s8),
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(NuvioTokens.Space.s6),
|
||||
) {
|
||||
if (isLoading) {
|
||||
CircularProgressIndicator(
|
||||
color = colorScheme.primary,
|
||||
strokeWidth = 1.5.dp,
|
||||
modifier = Modifier.size(12.dp),
|
||||
color = tokens.colors.accent,
|
||||
strokeWidth = tokens.borders.thin + NuvioTokens.Space.hairline,
|
||||
modifier = Modifier.size(NuvioTokens.Icon.xs),
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = label,
|
||||
color = when {
|
||||
hasError -> colorScheme.error
|
||||
isSelected -> colorScheme.onPrimaryContainer
|
||||
else -> colorScheme.onSurfaceVariant
|
||||
hasError -> tokens.colors.danger
|
||||
isSelected -> tokens.colors.textPrimary
|
||||
else -> tokens.colors.textMuted
|
||||
},
|
||||
fontSize = 12.sp,
|
||||
fontSize = NuvioTokens.Type.labelSm,
|
||||
fontWeight = if (isSelected) FontWeight.SemiBold else FontWeight.Normal,
|
||||
)
|
||||
}
|
||||
|
|
@ -454,32 +447,32 @@ internal fun PanelChipButton(
|
|||
onClick: () -> Unit,
|
||||
icon: androidx.compose.ui.graphics.vector.ImageVector? = null,
|
||||
) {
|
||||
val colorScheme = MaterialTheme.colorScheme
|
||||
val tokens = MaterialTheme.nuvio
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.background(colorScheme.surfaceVariant.copy(alpha = 0.9f))
|
||||
.border(1.dp, colorScheme.outlineVariant.copy(alpha = 0.7f), RoundedCornerShape(16.dp))
|
||||
.clip(tokens.shapes.compactCard)
|
||||
.background(tokens.colors.surfacePopover)
|
||||
.border(tokens.borders.thin, tokens.colors.borderSubtle, tokens.shapes.compactCard)
|
||||
.clickable(onClick = onClick)
|
||||
.padding(horizontal = 12.dp, vertical = 6.dp),
|
||||
.padding(horizontal = NuvioTokens.Space.s12, vertical = NuvioTokens.Space.s6),
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(NuvioTokens.Space.s4),
|
||||
) {
|
||||
if (icon != null) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
tint = colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.size(14.dp),
|
||||
tint = tokens.colors.textMuted,
|
||||
modifier = Modifier.size(NuvioTokens.Space.s14),
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = label,
|
||||
color = colorScheme.onSurfaceVariant,
|
||||
fontSize = 12.sp,
|
||||
color = tokens.colors.textMuted,
|
||||
fontSize = NuvioTokens.Type.labelSm,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,8 +27,6 @@ import androidx.compose.foundation.layout.imePadding
|
|||
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.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.rounded.Backspace
|
||||
import androidx.compose.material.icons.rounded.Add
|
||||
|
|
@ -69,6 +67,8 @@ import androidx.compose.ui.window.Popup
|
|||
import androidx.compose.ui.window.PopupProperties
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import coil3.compose.AsyncImage
|
||||
import com.nuvio.app.core.ui.NuvioTokens
|
||||
import com.nuvio.app.core.ui.nuvio
|
||||
import com.nuvio.app.isIos
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -87,6 +87,7 @@ fun ProfileSwitcherTab(
|
|||
triggerContent: (@Composable (selected: Boolean) -> Unit)? = null,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
val profileState by ProfileRepository.state.collectAsStateWithLifecycle()
|
||||
val activeProfile = profileState.activeProfile
|
||||
val profiles = profileState.profiles
|
||||
|
|
@ -243,7 +244,7 @@ fun ProfileSwitcherTab(
|
|||
if (popupVisible && profiles.isNotEmpty()) {
|
||||
Popup(
|
||||
alignment = Alignment.BottomCenter,
|
||||
offset = IntOffset(0, with(density) { -64.dp.roundToPx() }),
|
||||
offset = IntOffset(0, with(density) { -NuvioTokens.Space.s64.roundToPx() }),
|
||||
properties = PopupProperties(focusable = true),
|
||||
onDismissRequest = { showPopup = false },
|
||||
) {
|
||||
|
|
@ -256,17 +257,17 @@ fun ProfileSwitcherTab(
|
|||
scaleY = popupScale.value
|
||||
translationY = popupTranslateY.value
|
||||
}
|
||||
.shadow(16.dp, RoundedCornerShape(28.dp))
|
||||
.shadow(tokens.elevation.overlay, tokens.shapes.sheet)
|
||||
.background(
|
||||
MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||
RoundedCornerShape(28.dp),
|
||||
tokens.colors.surfaceSheet,
|
||||
tokens.shapes.sheet,
|
||||
)
|
||||
.padding(16.dp),
|
||||
.padding(tokens.spacing.sheetPadding),
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
// Profile avatars row
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(tokens.spacing.cardPadding),
|
||||
verticalAlignment = Alignment.Top,
|
||||
) {
|
||||
profiles.forEachIndexed { index, profile ->
|
||||
|
|
@ -340,6 +341,7 @@ private fun PopupAddProfileBubble(
|
|||
delayMs: Int,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
val itemAlpha = remember { Animatable(0f) }
|
||||
val itemScale = remember { Animatable(0.4f) }
|
||||
|
||||
|
|
@ -365,36 +367,36 @@ private fun PopupAddProfileBubble(
|
|||
scaleX = itemScale.value
|
||||
scaleY = itemScale.value
|
||||
}
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.clip(tokens.shapes.compactCard)
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = null,
|
||||
onClick = onClick,
|
||||
)
|
||||
.padding(4.dp),
|
||||
.padding(NuvioTokens.Space.s4),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.clip(CircleShape)
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant)
|
||||
.border(1.5.dp, MaterialTheme.colorScheme.outline.copy(alpha = 0.4f), CircleShape),
|
||||
.size(tokens.components.avatarSize)
|
||||
.clip(tokens.shapes.avatar)
|
||||
.background(tokens.colors.surfaceCard)
|
||||
.border(tokens.borders.thin + NuvioTokens.Space.hairline, tokens.colors.borderDefault.copy(alpha = tokens.opacity.medium), tokens.shapes.avatar),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Add,
|
||||
contentDescription = stringResource(Res.string.compose_profile_add_profile),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.size(22.dp),
|
||||
tint = tokens.colors.textMuted,
|
||||
modifier = Modifier.size(tokens.icons.md + NuvioTokens.Space.s2),
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Spacer(modifier = Modifier.height(NuvioTokens.Space.s4))
|
||||
|
||||
Text(
|
||||
text = stringResource(Res.string.compose_profile_add_profile),
|
||||
style = MaterialTheme.typography.labelSmall.copy(fontSize = 10.sp),
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
style = MaterialTheme.typography.labelSmall.copy(fontSize = NuvioTokens.Type.labelXs),
|
||||
color = tokens.colors.textMuted,
|
||||
fontWeight = FontWeight.Medium,
|
||||
textAlign = TextAlign.Center,
|
||||
maxLines = 1,
|
||||
|
|
@ -413,6 +415,7 @@ private fun PopupProfileBubble(
|
|||
onBoundsChanged: (Rect) -> Unit,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
val avatarColor = remember(profile.avatarColorHex) { parseHexColor(profile.avatarColorHex) }
|
||||
val avatarItem = remember(profile.avatarId, avatars) {
|
||||
profile.avatarId?.let { id -> avatars.find { it.id == id } }
|
||||
|
|
@ -458,13 +461,13 @@ private fun PopupProfileBubble(
|
|||
scaleX = itemScale.value * pressScale
|
||||
scaleY = itemScale.value * pressScale
|
||||
}
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.clip(tokens.shapes.compactCard)
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = null,
|
||||
onClick = onClick,
|
||||
)
|
||||
.padding(4.dp),
|
||||
.padding(NuvioTokens.Space.s4),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.size(52.dp),
|
||||
|
|
@ -473,7 +476,7 @@ private fun PopupProfileBubble(
|
|||
Box(
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.clip(CircleShape)
|
||||
.clip(tokens.shapes.avatar)
|
||||
.background(
|
||||
if (avatarImageUrl != null) {
|
||||
avatarItem?.bgColor?.let { parseHexColor(it) } ?: avatarColor
|
||||
|
|
@ -484,19 +487,19 @@ private fun PopupProfileBubble(
|
|||
.then(
|
||||
when {
|
||||
isSelected -> Modifier.border(
|
||||
2.5.dp,
|
||||
MaterialTheme.colorScheme.primary,
|
||||
CircleShape,
|
||||
tokens.borders.medium + NuvioTokens.Space.hairline,
|
||||
tokens.colors.borderSelected,
|
||||
tokens.shapes.avatar,
|
||||
)
|
||||
isActive -> Modifier.border(
|
||||
2.dp,
|
||||
tokens.borders.medium,
|
||||
avatarColor.copy(alpha = 0.6f),
|
||||
CircleShape,
|
||||
tokens.shapes.avatar,
|
||||
)
|
||||
avatarImageUrl == null -> Modifier.border(
|
||||
1.5.dp,
|
||||
tokens.borders.thin + NuvioTokens.Space.hairline,
|
||||
avatarColor.copy(alpha = 0.3f),
|
||||
CircleShape,
|
||||
tokens.shapes.avatar,
|
||||
)
|
||||
else -> Modifier
|
||||
},
|
||||
|
|
@ -507,13 +510,13 @@ private fun PopupProfileBubble(
|
|||
AsyncImage(
|
||||
model = avatarImageUrl,
|
||||
contentDescription = profile.name,
|
||||
modifier = Modifier.size(48.dp).clip(CircleShape),
|
||||
modifier = Modifier.size(48.dp).clip(tokens.shapes.avatar),
|
||||
contentScale = ContentScale.Crop,
|
||||
)
|
||||
} else if (profile.name.isNotBlank()) {
|
||||
Text(
|
||||
text = profile.name.take(1).uppercase(),
|
||||
style = MaterialTheme.typography.titleMedium.copy(fontSize = 18.sp),
|
||||
style = MaterialTheme.typography.titleMedium.copy(fontSize = NuvioTokens.Type.titleSm),
|
||||
color = avatarColor,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
|
|
@ -522,7 +525,7 @@ private fun PopupProfileBubble(
|
|||
imageVector = Icons.Rounded.Person,
|
||||
contentDescription = null,
|
||||
tint = avatarColor,
|
||||
modifier = Modifier.size(24.dp),
|
||||
modifier = Modifier.size(tokens.icons.lg),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -533,30 +536,29 @@ private fun PopupProfileBubble(
|
|||
modifier = Modifier
|
||||
.align(Alignment.BottomEnd)
|
||||
.size(18.dp)
|
||||
.clip(CircleShape)
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant)
|
||||
.border(1.5.dp, MaterialTheme.colorScheme.surfaceContainerHigh, CircleShape),
|
||||
.clip(tokens.shapes.avatar)
|
||||
.background(tokens.colors.surfacePopover)
|
||||
.border(tokens.borders.thin + NuvioTokens.Space.hairline, tokens.colors.surfaceSheet, tokens.shapes.avatar),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Lock,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
tint = tokens.colors.textMuted,
|
||||
modifier = Modifier.size(10.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Spacer(modifier = Modifier.height(NuvioTokens.Space.s4))
|
||||
|
||||
Text(
|
||||
text = profile.name.ifBlank {
|
||||
stringResource(Res.string.profile_label_number, profile.profileIndex)
|
||||
},
|
||||
style = MaterialTheme.typography.labelSmall.copy(fontSize = 10.sp),
|
||||
color = if (isSelected) MaterialTheme.colorScheme.primary
|
||||
else MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
style = MaterialTheme.typography.labelSmall.copy(fontSize = NuvioTokens.Type.labelXs),
|
||||
color = if (isSelected) tokens.colors.accent else tokens.colors.textMuted,
|
||||
fontWeight = if (isActive || isSelected) FontWeight.Bold else FontWeight.Medium,
|
||||
textAlign = TextAlign.Center,
|
||||
maxLines = 1,
|
||||
|
|
@ -588,6 +590,7 @@ private fun InlinePinEntry(
|
|||
onCancel: () -> Unit,
|
||||
verifyPin: suspend (String) -> PinVerifyResult,
|
||||
) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
var pin by remember { mutableStateOf("") }
|
||||
var error by remember { mutableStateOf<String?>(null) }
|
||||
var isVerifying by remember { mutableStateOf(false) }
|
||||
|
|
@ -596,18 +599,18 @@ private fun InlinePinEntry(
|
|||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.padding(top = 16.dp),
|
||||
modifier = Modifier.padding(top = tokens.spacing.cardPadding),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(Res.string.pin_enter_for, profileName),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
color = tokens.colors.textMuted,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(14.dp))
|
||||
Spacer(modifier = Modifier.height(NuvioTokens.Space.s14))
|
||||
|
||||
// PIN dots with bounce animation
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(tokens.spacing.listGap)) {
|
||||
repeat(4) { index ->
|
||||
val filled = index < pin.length
|
||||
val dotScale = remember { Animatable(1f) }
|
||||
|
|
@ -625,9 +628,9 @@ private fun InlinePinEntry(
|
|||
}
|
||||
|
||||
val dotColor = when {
|
||||
error != null -> MaterialTheme.colorScheme.error
|
||||
filled -> MaterialTheme.colorScheme.primary
|
||||
else -> MaterialTheme.colorScheme.outline
|
||||
error != null -> tokens.colors.danger
|
||||
filled -> tokens.colors.accent
|
||||
else -> tokens.colors.borderDefault
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
|
|
@ -635,11 +638,11 @@ private fun InlinePinEntry(
|
|||
scaleX = dotScale.value
|
||||
scaleY = dotScale.value
|
||||
}
|
||||
.size(14.dp)
|
||||
.clip(CircleShape)
|
||||
.size(NuvioTokens.Space.s14)
|
||||
.clip(tokens.shapes.avatar)
|
||||
.then(
|
||||
if (filled) Modifier.background(dotColor)
|
||||
else Modifier.border(2.dp, dotColor, CircleShape),
|
||||
else Modifier.border(tokens.borders.medium, dotColor, tokens.shapes.avatar),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -654,12 +657,12 @@ private fun InlinePinEntry(
|
|||
Text(
|
||||
text = error.orEmpty(),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
modifier = Modifier.padding(top = 8.dp),
|
||||
color = tokens.colors.danger,
|
||||
modifier = Modifier.padding(top = tokens.spacing.controlGap),
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(14.dp))
|
||||
Spacer(modifier = Modifier.height(NuvioTokens.Space.s14))
|
||||
|
||||
// Compact number pad
|
||||
CompactPinKeypad(
|
||||
|
|
@ -696,17 +699,17 @@ private fun InlinePinEntry(
|
|||
},
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Spacer(modifier = Modifier.height(tokens.spacing.controlGap))
|
||||
|
||||
Text(
|
||||
text = stringResource(Res.string.pin_cancel),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
color = tokens.colors.accent,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.clip(tokens.shapes.compactCard)
|
||||
.clickable(onClick = onCancel)
|
||||
.padding(horizontal = 16.dp, vertical = 6.dp),
|
||||
.padding(horizontal = tokens.spacing.cardPadding, vertical = NuvioTokens.Space.s6),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -716,6 +719,7 @@ private fun CompactPinKeypad(
|
|||
onDigit: (String) -> Unit,
|
||||
onBackspace: () -> Unit,
|
||||
) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
val rows = listOf(
|
||||
listOf("1", "2", "3"),
|
||||
listOf("4", "5", "6"),
|
||||
|
|
@ -723,45 +727,45 @@ private fun CompactPinKeypad(
|
|||
listOf("", "0", "⌫"),
|
||||
)
|
||||
|
||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(tokens.spacing.controlGap)) {
|
||||
rows.forEach { row ->
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally),
|
||||
horizontalArrangement = Arrangement.spacedBy(tokens.spacing.controlGap, Alignment.CenterHorizontally),
|
||||
) {
|
||||
row.forEach { key ->
|
||||
when (key) {
|
||||
"" -> Spacer(modifier = Modifier.size(48.dp))
|
||||
"" -> Spacer(modifier = Modifier.size(tokens.components.avatarSize))
|
||||
"⌫" -> {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.clip(CircleShape)
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant)
|
||||
.size(tokens.components.avatarSize)
|
||||
.clip(tokens.shapes.avatar)
|
||||
.background(tokens.colors.surfaceCard)
|
||||
.clickable(onClick = onBackspace),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Rounded.Backspace,
|
||||
contentDescription = stringResource(Res.string.pin_backspace),
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
modifier = Modifier.size(20.dp),
|
||||
tint = tokens.colors.textPrimary,
|
||||
modifier = Modifier.size(tokens.icons.md),
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.clip(CircleShape)
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant)
|
||||
.size(tokens.components.avatarSize)
|
||||
.clip(tokens.shapes.avatar)
|
||||
.background(tokens.colors.surfaceCard)
|
||||
.clickable { onDigit(key) },
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(
|
||||
text = key,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
color = tokens.colors.textPrimary,
|
||||
fontWeight = FontWeight.Medium,
|
||||
)
|
||||
}
|
||||
|
|
@ -780,6 +784,7 @@ fun ActiveProfileMiniAvatar(
|
|||
selected: Boolean,
|
||||
size: Int = 24,
|
||||
) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
if (profile == null) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Person,
|
||||
|
|
@ -798,7 +803,7 @@ fun ActiveProfileMiniAvatar(
|
|||
}
|
||||
|
||||
val borderColor = if (selected) {
|
||||
MaterialTheme.colorScheme.primary
|
||||
tokens.colors.borderSelected
|
||||
} else {
|
||||
avatarColor.copy(alpha = 0.5f)
|
||||
}
|
||||
|
|
@ -806,7 +811,7 @@ fun ActiveProfileMiniAvatar(
|
|||
Box(
|
||||
modifier = Modifier
|
||||
.size(size.dp)
|
||||
.clip(CircleShape)
|
||||
.clip(tokens.shapes.avatar)
|
||||
.background(
|
||||
if (avatarImageUrl != null) {
|
||||
avatarItem?.bgColor?.let { parseHexColor(it) } ?: avatarColor
|
||||
|
|
@ -814,14 +819,14 @@ fun ActiveProfileMiniAvatar(
|
|||
avatarColor.copy(alpha = 0.15f)
|
||||
},
|
||||
)
|
||||
.border(1.5.dp, borderColor, CircleShape),
|
||||
.border(tokens.borders.thin + NuvioTokens.Space.hairline, borderColor, tokens.shapes.avatar),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
if (avatarImageUrl != null) {
|
||||
AsyncImage(
|
||||
model = avatarImageUrl,
|
||||
contentDescription = profile.name,
|
||||
modifier = Modifier.size(size.dp).clip(CircleShape),
|
||||
modifier = Modifier.size(size.dp).clip(tokens.shapes.avatar),
|
||||
contentScale = ContentScale.Crop,
|
||||
)
|
||||
} else if (profile.name.isNotBlank()) {
|
||||
|
|
|
|||
|
|
@ -45,9 +45,11 @@ import androidx.compose.ui.text.font.FontWeight
|
|||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.nuvio.app.core.ui.NuvioTokens
|
||||
import com.nuvio.app.core.ui.NuvioActionLabel
|
||||
import com.nuvio.app.core.ui.NuvioBackButton
|
||||
import com.nuvio.app.core.ui.NuvioSectionLabel
|
||||
import com.nuvio.app.core.ui.nuvio
|
||||
import com.nuvio.app.core.ui.nuvioBlockPointerPassthrough
|
||||
import com.nuvio.app.features.home.HomeCatalogSettingsItem
|
||||
import nuvio.composeapp.generated.resources.Res
|
||||
|
|
@ -69,15 +71,14 @@ private fun SettingsCard(
|
|||
modifier: Modifier = Modifier,
|
||||
content: @Composable ColumnScope.() -> Unit,
|
||||
) {
|
||||
val colorScheme = MaterialTheme.colorScheme
|
||||
val isAmoled = colorScheme.background == Color.Black && colorScheme.surface == Color(0xFF050505)
|
||||
val tokens = MaterialTheme.nuvio
|
||||
Surface(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
color = if (isAmoled) Color(0xFF0B0B0B) else colorScheme.surface,
|
||||
shape = RoundedCornerShape(if (isTablet) 20.dp else 16.dp),
|
||||
color = tokens.colors.surface,
|
||||
shape = if (isTablet) RoundedCornerShape(NuvioTokens.Radius.xl) else tokens.shapes.compactCard,
|
||||
border = BorderStroke(
|
||||
0.5.dp,
|
||||
colorScheme.outlineVariant.copy(alpha = if (isAmoled) 0.24f else 0.16f),
|
||||
tokens.borders.hairline,
|
||||
tokens.colors.borderSubtle,
|
||||
),
|
||||
) {
|
||||
Column(content = content)
|
||||
|
|
@ -100,10 +101,11 @@ internal fun SettingsGroup(
|
|||
|
||||
@Composable
|
||||
internal fun SettingsGroupDivider(isTablet: Boolean) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
HorizontalDivider(
|
||||
modifier = Modifier.padding(start = if (isTablet) 78.dp else 66.dp),
|
||||
thickness = 0.5.dp,
|
||||
color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.12f),
|
||||
modifier = Modifier.padding(start = if (isTablet) NuvioTokens.Space.s80 - NuvioTokens.Space.s2 else NuvioTokens.Space.s64 + NuvioTokens.Space.s2),
|
||||
thickness = tokens.borders.hairline,
|
||||
color = tokens.colors.borderSubtle,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -113,29 +115,30 @@ internal fun TabletPageHeader(
|
|||
showBack: Boolean,
|
||||
onBack: () -> Unit,
|
||||
) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.nuvioBlockPointerPassthrough(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(tokens.spacing.listGap),
|
||||
) {
|
||||
if (showBack) {
|
||||
NuvioBackButton(
|
||||
onClick = onBack,
|
||||
modifier = Modifier
|
||||
.size(36.dp),
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
contentColor = MaterialTheme.colorScheme.onSurface,
|
||||
buttonSize = 36.dp,
|
||||
iconSize = 20.dp,
|
||||
shape = tokens.shapes.compactCard,
|
||||
containerColor = tokens.colors.surface,
|
||||
contentColor = tokens.colors.textPrimary,
|
||||
buttonSize = NuvioTokens.Space.s36,
|
||||
iconSize = tokens.icons.md,
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.headlineLarge,
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
color = tokens.colors.textPrimary,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
}
|
||||
|
|
@ -148,24 +151,25 @@ internal fun SettingsSidebarItem(
|
|||
selected: Boolean,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
val primary = MaterialTheme.colorScheme.primary
|
||||
val background = if (selected) primary.copy(alpha = 0.10f) else Color.Transparent
|
||||
val iconChip = if (selected) primary.copy(alpha = 0.15f) else Color.Transparent
|
||||
val contentColor = if (selected) MaterialTheme.colorScheme.onSurface else MaterialTheme.colorScheme.onSurfaceVariant
|
||||
val tokens = MaterialTheme.nuvio
|
||||
val primary = tokens.colors.accent
|
||||
val background = if (selected) primary.copy(alpha = tokens.opacity.hover) else Color.Transparent
|
||||
val iconChip = if (selected) primary.copy(alpha = tokens.opacity.selected) else Color.Transparent
|
||||
val contentColor = if (selected) tokens.colors.textPrimary else tokens.colors.textMuted
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 12.dp, vertical = 2.dp)
|
||||
.background(background, RoundedCornerShape(10.dp))
|
||||
.padding(horizontal = tokens.spacing.listGap, vertical = NuvioTokens.Space.s2)
|
||||
.background(background, RoundedCornerShape(NuvioTokens.Space.s10))
|
||||
.clickable(onClick = onClick)
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
.padding(horizontal = tokens.spacing.screenHorizontal, vertical = tokens.spacing.listGap),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier.size(32.dp),
|
||||
modifier = Modifier.size(tokens.icons.xl),
|
||||
color = iconChip,
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
shape = RoundedCornerShape(NuvioTokens.Radius.md),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
|
|
@ -179,7 +183,7 @@ internal fun SettingsSidebarItem(
|
|||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
Spacer(modifier = Modifier.width(tokens.spacing.listGap))
|
||||
Text(
|
||||
text = label,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
|
|
@ -196,6 +200,7 @@ internal fun SettingsSection(
|
|||
actions: @Composable RowScope.() -> Unit = {},
|
||||
content: @Composable ColumnScope.() -> Unit,
|
||||
) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
Column {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
|
|
@ -209,7 +214,7 @@ internal fun SettingsSection(
|
|||
content = actions,
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(if (isTablet) 12.dp else 10.dp))
|
||||
Spacer(modifier = Modifier.height(if (isTablet) tokens.spacing.listGap else NuvioTokens.Space.s10))
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
|
@ -224,8 +229,8 @@ internal fun SettingsNavigationRow(
|
|||
isTablet: Boolean,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
val iconSize = if (isTablet) 42.dp else 36.dp
|
||||
val iconRadius = if (isTablet) 12.dp else 10.dp
|
||||
val verticalPadding = if (isTablet) 16.dp else 14.dp
|
||||
val horizontalPadding = if (isTablet) 20.dp else 16.dp
|
||||
|
||||
|
|
@ -234,7 +239,7 @@ internal fun SettingsNavigationRow(
|
|||
.fillMaxWidth()
|
||||
.clickable(enabled = enabled, onClick = onClick)
|
||||
.padding(horizontal = horizontalPadding, vertical = verticalPadding)
|
||||
.alpha(if (enabled) 1f else 0.55f),
|
||||
.alpha(if (enabled) NuvioTokens.Opacity.visible else tokens.opacity.medium),
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
|
|
@ -248,8 +253,8 @@ internal fun SettingsNavigationRow(
|
|||
if (icon != null || iconPainter != null) {
|
||||
Surface(
|
||||
modifier = Modifier.size(iconSize),
|
||||
color = MaterialTheme.colorScheme.primary.copy(alpha = 0.12f),
|
||||
shape = RoundedCornerShape(iconRadius),
|
||||
color = tokens.colors.accent.copy(alpha = tokens.opacity.pressed),
|
||||
shape = tokens.shapes.compactCard,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
|
|
@ -267,7 +272,7 @@ internal fun SettingsNavigationRow(
|
|||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
tint = tokens.colors.accent,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -278,14 +283,14 @@ internal fun SettingsNavigationRow(
|
|||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
color = tokens.colors.textPrimary,
|
||||
fontWeight = FontWeight.Medium,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
Text(
|
||||
text = description,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
color = tokens.colors.textMuted,
|
||||
modifier = Modifier.alpha(0.92f),
|
||||
)
|
||||
}
|
||||
|
|
@ -302,6 +307,7 @@ internal fun SettingsSwitchRow(
|
|||
isTablet: Boolean,
|
||||
onCheckedChange: (Boolean) -> Unit,
|
||||
) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
val verticalPadding = if (isTablet) 16.dp else 14.dp
|
||||
val horizontalPadding = if (isTablet) 20.dp else 16.dp
|
||||
|
||||
|
|
@ -318,20 +324,20 @@ internal fun SettingsSwitchRow(
|
|||
.weight(1f)
|
||||
.padding(end = 12.dp)
|
||||
.widthIn(max = if (isTablet) 560.dp else Dp.Unspecified)
|
||||
.alpha(if (enabled) 1f else 0.55f),
|
||||
.alpha(if (enabled) NuvioTokens.Opacity.visible else tokens.opacity.medium),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
color = tokens.colors.textPrimary,
|
||||
fontWeight = FontWeight.Medium,
|
||||
)
|
||||
if (!description.isNullOrBlank()) {
|
||||
Text(
|
||||
text = description,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
color = tokens.colors.textMuted,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -341,10 +347,10 @@ internal fun SettingsSwitchRow(
|
|||
enabled = enabled,
|
||||
modifier = Modifier.padding(start = 4.dp),
|
||||
colors = SwitchDefaults.colors(
|
||||
checkedThumbColor = MaterialTheme.colorScheme.onPrimary,
|
||||
checkedTrackColor = MaterialTheme.colorScheme.primary,
|
||||
uncheckedThumbColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
uncheckedTrackColor = MaterialTheme.colorScheme.outlineVariant,
|
||||
checkedThumbColor = tokens.colors.onAccent,
|
||||
checkedTrackColor = tokens.colors.accent,
|
||||
uncheckedThumbColor = tokens.colors.textMuted,
|
||||
uncheckedTrackColor = tokens.colors.borderDefault,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -361,6 +367,7 @@ internal fun HomescreenCatalogRow(
|
|||
dragHandleScope: ReorderableCollectionItemScope,
|
||||
onPinnedDragAttempt: () -> Unit = {},
|
||||
) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
val horizontalPadding = if (isTablet) 20.dp else 16.dp
|
||||
val verticalPadding = if (isTablet) 18.dp else 16.dp
|
||||
val hapticFeedback = LocalHapticFeedback.current
|
||||
|
|
@ -387,7 +394,7 @@ internal fun HomescreenCatalogRow(
|
|||
Text(
|
||||
text = item.displayTitle,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
color = tokens.colors.textPrimary,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
|
|
@ -399,7 +406,7 @@ internal fun HomescreenCatalogRow(
|
|||
item.addonName
|
||||
},
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
color = tokens.colors.textMuted,
|
||||
)
|
||||
Text(
|
||||
text = buildString {
|
||||
|
|
@ -427,7 +434,7 @@ internal fun HomescreenCatalogRow(
|
|||
}
|
||||
},
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
color = tokens.colors.textMuted,
|
||||
)
|
||||
}
|
||||
Row(
|
||||
|
|
@ -438,10 +445,10 @@ internal fun HomescreenCatalogRow(
|
|||
checked = item.enabled,
|
||||
onCheckedChange = onEnabledChange,
|
||||
colors = SwitchDefaults.colors(
|
||||
checkedThumbColor = MaterialTheme.colorScheme.onPrimary,
|
||||
checkedTrackColor = MaterialTheme.colorScheme.primary,
|
||||
uncheckedThumbColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
uncheckedTrackColor = MaterialTheme.colorScheme.outlineVariant,
|
||||
checkedThumbColor = tokens.colors.onAccent,
|
||||
checkedTrackColor = tokens.colors.accent,
|
||||
uncheckedThumbColor = tokens.colors.textMuted,
|
||||
uncheckedTrackColor = tokens.colors.borderDefault,
|
||||
),
|
||||
)
|
||||
if (item.isPinnedToTop) {
|
||||
|
|
@ -451,7 +458,7 @@ internal fun HomescreenCatalogRow(
|
|||
Icon(
|
||||
imageVector = Icons.Rounded.Lock,
|
||||
contentDescription = stringResource(Res.string.settings_homescreen_pinned),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f),
|
||||
tint = tokens.colors.textMuted.copy(alpha = tokens.opacity.medium),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
|
|
@ -471,7 +478,7 @@ internal fun HomescreenCatalogRow(
|
|||
Icon(
|
||||
imageVector = Icons.Rounded.Menu,
|
||||
contentDescription = stringResource(Res.string.settings_homescreen_reorder),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
tint = tokens.colors.textMuted,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -490,11 +497,11 @@ internal fun HomescreenCatalogRow(
|
|||
label = { Text(stringResource(Res.string.settings_homescreen_display_name)) },
|
||||
placeholder = { Text(item.defaultTitle) },
|
||||
colors = OutlinedTextFieldDefaults.colors(
|
||||
focusedBorderColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.75f),
|
||||
unfocusedBorderColor = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.42f),
|
||||
focusedContainerColor = MaterialTheme.colorScheme.surface,
|
||||
unfocusedContainerColor = MaterialTheme.colorScheme.surface,
|
||||
disabledContainerColor = MaterialTheme.colorScheme.surface,
|
||||
focusedBorderColor = tokens.colors.borderFocus.copy(alpha = tokens.opacity.strong),
|
||||
unfocusedBorderColor = tokens.colors.borderDefault.copy(alpha = tokens.opacity.medium),
|
||||
focusedContainerColor = tokens.colors.surface,
|
||||
unfocusedContainerColor = tokens.colors.surface,
|
||||
disabledContainerColor = tokens.colors.surface,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.Column
|
|||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.AccountCircle
|
||||
import androidx.compose.material.icons.rounded.Close
|
||||
|
|
@ -43,6 +42,8 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.nuvio.app.core.ui.NuvioTokens
|
||||
import com.nuvio.app.core.ui.nuvio
|
||||
import com.nuvio.app.isIos
|
||||
import nuvio.composeapp.generated.resources.*
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
|
|
@ -974,12 +975,12 @@ private fun SettingsSearchRevealItem(
|
|||
AnimatedVisibility(
|
||||
visibleState = visibleState,
|
||||
enter = expandVertically(
|
||||
animationSpec = tween(durationMillis = 220),
|
||||
animationSpec = tween(durationMillis = NuvioTokens.Motion.normalMillis),
|
||||
expandFrom = Alignment.Top,
|
||||
) + fadeIn(
|
||||
animationSpec = tween(durationMillis = 180),
|
||||
animationSpec = tween(durationMillis = NuvioTokens.Motion.fastMillis),
|
||||
) + slideInVertically(
|
||||
animationSpec = tween(durationMillis = 220),
|
||||
animationSpec = tween(durationMillis = NuvioTokens.Motion.normalMillis),
|
||||
initialOffsetY = { -it / 4 },
|
||||
),
|
||||
) {
|
||||
|
|
@ -992,17 +993,18 @@ private fun SettingsSearchField(
|
|||
query: String,
|
||||
onQueryChange: (String) -> Unit,
|
||||
) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
OutlinedTextField(
|
||||
value = query,
|
||||
onValueChange = onQueryChange,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
singleLine = true,
|
||||
shape = RoundedCornerShape(14.dp),
|
||||
shape = tokens.shapes.compactCard,
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Search,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
tint = tokens.colors.textMuted,
|
||||
)
|
||||
},
|
||||
trailingIcon = if (query.isNotBlank()) {
|
||||
|
|
@ -1011,7 +1013,7 @@ private fun SettingsSearchField(
|
|||
Icon(
|
||||
imageVector = Icons.Rounded.Close,
|
||||
contentDescription = stringResource(Res.string.compose_search_clear),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
tint = tokens.colors.textMuted,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1021,23 +1023,24 @@ private fun SettingsSearchField(
|
|||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(Res.string.settings_search_placeholder),
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
color = tokens.colors.textMuted,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
)
|
||||
},
|
||||
textStyle = MaterialTheme.typography.bodyLarge.copy(color = MaterialTheme.colorScheme.onSurface),
|
||||
textStyle = MaterialTheme.typography.bodyLarge.copy(color = tokens.colors.textPrimary),
|
||||
colors = OutlinedTextFieldDefaults.colors(
|
||||
focusedBorderColor = MaterialTheme.colorScheme.outline,
|
||||
unfocusedBorderColor = MaterialTheme.colorScheme.outline,
|
||||
focusedContainerColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||
unfocusedContainerColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||
cursorColor = MaterialTheme.colorScheme.primary,
|
||||
focusedBorderColor = tokens.colors.borderFocus,
|
||||
unfocusedBorderColor = tokens.colors.borderDefault,
|
||||
focusedContainerColor = tokens.colors.surfaceCard,
|
||||
unfocusedContainerColor = tokens.colors.surfaceCard,
|
||||
cursorColor = tokens.colors.accent,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SettingsSearchEmptyState(isTablet: Boolean) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
SettingsSection(
|
||||
title = stringResource(Res.string.settings_search_results_section),
|
||||
isTablet = isTablet,
|
||||
|
|
@ -1051,7 +1054,7 @@ private fun SettingsSearchEmptyState(isTablet: Boolean) {
|
|||
Text(
|
||||
text = stringResource(Res.string.settings_search_empty),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
color = tokens.colors.textPrimary,
|
||||
fontWeight = FontWeight.Medium,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ import androidx.compose.foundation.layout.width
|
|||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Delete
|
||||
import androidx.compose.material.icons.rounded.Style
|
||||
|
|
@ -47,6 +46,8 @@ import androidx.compose.ui.text.font.FontWeight
|
|||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.nuvio.app.core.ui.NuvioTokens
|
||||
import com.nuvio.app.core.ui.nuvio
|
||||
import com.nuvio.app.features.streams.STREAM_BADGE_IMPORT_LIMIT
|
||||
import com.nuvio.app.features.streams.StreamBadgeChip
|
||||
import com.nuvio.app.features.streams.StreamBadgeChipSize
|
||||
|
|
@ -177,18 +178,19 @@ private fun StreamBadgePositionDialog(
|
|||
onPlacementSelected: (StreamBadgePlacement) -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
BasicAlertDialog(onDismissRequest = onDismiss) {
|
||||
SettingsDialogSurface(title = stringResource(Res.string.settings_stream_badge_position_dialog_title)) {
|
||||
Text(
|
||||
text = stringResource(Res.string.settings_stream_badge_position_dialog_description),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
color = tokens.colors.textSecondary,
|
||||
)
|
||||
StreamBadgePlacement.entries.forEach { placement ->
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(tokens.spacing.controlGap),
|
||||
) {
|
||||
RadioButton(
|
||||
selected = placement == selectedPlacement,
|
||||
|
|
@ -197,7 +199,7 @@ private fun StreamBadgePositionDialog(
|
|||
Text(
|
||||
text = streamBadgePlacementLabel(placement),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
color = tokens.colors.textPrimary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -219,6 +221,7 @@ private fun BadgeUrlManagerDialog(
|
|||
currentRules: StreamBadgeRules,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
val scope = rememberCoroutineScope()
|
||||
val imports = currentRules.normalized().imports
|
||||
var draftUrl by rememberSaveable { mutableStateOf("") }
|
||||
|
|
@ -231,7 +234,7 @@ private fun BadgeUrlManagerDialog(
|
|||
Text(
|
||||
text = stringResource(Res.string.settings_stream_badge_urls_description, STREAM_BADGE_IMPORT_LIMIT),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
color = tokens.colors.textSecondary,
|
||||
)
|
||||
OutlinedTextField(
|
||||
value = draftUrl,
|
||||
|
|
@ -246,16 +249,16 @@ private fun BadgeUrlManagerDialog(
|
|||
maxLines = 4,
|
||||
enabled = !isImporting,
|
||||
colors = OutlinedTextFieldDefaults.colors(
|
||||
focusedBorderColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.75f),
|
||||
unfocusedBorderColor = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.42f),
|
||||
focusedContainerColor = MaterialTheme.colorScheme.surface,
|
||||
unfocusedContainerColor = MaterialTheme.colorScheme.surface,
|
||||
disabledContainerColor = MaterialTheme.colorScheme.surface,
|
||||
focusedBorderColor = tokens.colors.borderFocus.copy(alpha = tokens.opacity.strong),
|
||||
unfocusedBorderColor = tokens.colors.borderDefault.copy(alpha = tokens.opacity.medium),
|
||||
focusedContainerColor = tokens.colors.surface,
|
||||
unfocusedContainerColor = tokens.colors.surface,
|
||||
disabledContainerColor = tokens.colors.surface,
|
||||
),
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.End),
|
||||
horizontalArrangement = Arrangement.spacedBy(tokens.spacing.controlGap, Alignment.End),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
|
|
@ -265,7 +268,7 @@ private fun BadgeUrlManagerDialog(
|
|||
STREAM_BADGE_IMPORT_LIMIT,
|
||||
),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
color = tokens.colors.textMuted,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
Button(
|
||||
|
|
@ -289,9 +292,9 @@ private fun BadgeUrlManagerDialog(
|
|||
) {
|
||||
if (isImporting) {
|
||||
CircularProgressIndicator(
|
||||
strokeWidth = 2.dp,
|
||||
modifier = Modifier.size(16.dp),
|
||||
color = MaterialTheme.colorScheme.onPrimary,
|
||||
strokeWidth = tokens.borders.medium,
|
||||
modifier = Modifier.size(tokens.icons.sm),
|
||||
color = tokens.colors.onAccent,
|
||||
)
|
||||
} else {
|
||||
Text(text = stringResource(Res.string.action_import), maxLines = 1)
|
||||
|
|
@ -302,7 +305,7 @@ private fun BadgeUrlManagerDialog(
|
|||
Text(
|
||||
text = message,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
color = tokens.colors.danger,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -310,14 +313,14 @@ private fun BadgeUrlManagerDialog(
|
|||
Text(
|
||||
text = stringResource(Res.string.settings_fusion_badges_empty),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
color = tokens.colors.textMuted,
|
||||
)
|
||||
} else {
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(max = 300.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(tokens.spacing.controlGap),
|
||||
) {
|
||||
items(
|
||||
items = imports,
|
||||
|
|
@ -343,7 +346,7 @@ private fun BadgeUrlManagerDialog(
|
|||
}
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.End),
|
||||
horizontalArrangement = Arrangement.spacedBy(tokens.spacing.controlGap, Alignment.End),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
TextButton(
|
||||
|
|
@ -373,26 +376,27 @@ private fun BadgeUrlRow(
|
|||
onPreview: () -> Unit,
|
||||
onDelete: () -> Unit,
|
||||
) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
val containerColor = if (import.isActive) {
|
||||
MaterialTheme.colorScheme.primary.copy(alpha = 0.12f)
|
||||
tokens.colors.overlaySelected
|
||||
} else {
|
||||
MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.35f)
|
||||
tokens.colors.surfaceCard
|
||||
}
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
shape = tokens.shapes.compactCard,
|
||||
color = containerColor,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 12.dp, vertical = 10.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
.padding(horizontal = NuvioTokens.Space.s12, vertical = NuvioTokens.Space.s10),
|
||||
verticalArrangement = Arrangement.spacedBy(tokens.spacing.controlGap),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(tokens.spacing.controlGap),
|
||||
) {
|
||||
if (showActiveChoice) {
|
||||
RadioButton(
|
||||
|
|
@ -404,7 +408,7 @@ private fun BadgeUrlRow(
|
|||
Text(
|
||||
text = import.sourceUrl,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
color = tokens.colors.textPrimary,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.weight(1f),
|
||||
|
|
@ -413,7 +417,7 @@ private fun BadgeUrlRow(
|
|||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.End),
|
||||
horizontalArrangement = Arrangement.spacedBy(tokens.spacing.controlGap, Alignment.End),
|
||||
) {
|
||||
val status = if (import.isActive) {
|
||||
stringResource(Res.string.settings_fusion_badge_url_active)
|
||||
|
|
@ -428,7 +432,7 @@ private fun BadgeUrlRow(
|
|||
import.groups.size,
|
||||
),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
color = tokens.colors.textMuted,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
TextButton(
|
||||
|
|
@ -438,9 +442,9 @@ private fun BadgeUrlRow(
|
|||
Icon(
|
||||
imageVector = Icons.Rounded.Visibility,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(16.dp),
|
||||
modifier = Modifier.size(tokens.icons.sm),
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Spacer(modifier = Modifier.width(NuvioTokens.Space.s4))
|
||||
Text(text = stringResource(Res.string.settings_fusion_badge_preview_action), maxLines = 1)
|
||||
}
|
||||
IconButton(
|
||||
|
|
@ -463,6 +467,7 @@ private fun BadgePreviewDialog(
|
|||
import: StreamBadgeImport,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
val sections = badgePreviewSections(import)
|
||||
val badgeCount = sections.sumOf { it.filters.size }
|
||||
|
||||
|
|
@ -471,27 +476,27 @@ private fun BadgePreviewDialog(
|
|||
Text(
|
||||
text = import.sourceUrl,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
color = tokens.colors.textSecondary,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Text(
|
||||
text = stringResource(Res.string.settings_fusion_badge_preview_count, badgeCount),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
color = tokens.colors.textMuted,
|
||||
)
|
||||
if (sections.isEmpty()) {
|
||||
Text(
|
||||
text = stringResource(Res.string.settings_fusion_badge_preview_empty),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
color = tokens.colors.textMuted,
|
||||
)
|
||||
} else {
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(max = 460.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(14.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(tokens.spacing.railGap),
|
||||
) {
|
||||
items(
|
||||
items = sections,
|
||||
|
|
@ -499,18 +504,18 @@ private fun BadgePreviewDialog(
|
|||
) { section ->
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(tokens.spacing.controlGap),
|
||||
) {
|
||||
Text(
|
||||
text = section.title,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
color = tokens.colors.textPrimary,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
FlowRow(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(5.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(5.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(NuvioTokens.Space.s5),
|
||||
verticalArrangement = Arrangement.spacedBy(NuvioTokens.Space.s5),
|
||||
) {
|
||||
section.filters.forEach { filter ->
|
||||
StreamBadgeChip(
|
||||
|
|
@ -544,23 +549,24 @@ private fun SettingsDialogSurface(
|
|||
title: String,
|
||||
content: @Composable ColumnScope.() -> Unit,
|
||||
) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
color = MaterialTheme.colorScheme.surface,
|
||||
shape = tokens.shapes.dialog,
|
||||
color = tokens.colors.surfaceDialog,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(20.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
modifier = Modifier.padding(tokens.spacing.dialogPadding),
|
||||
verticalArrangement = Arrangement.spacedBy(tokens.spacing.listGap),
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
color = tokens.colors.textPrimary,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
content()
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
Spacer(modifier = Modifier.height(NuvioTokens.Space.s2))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.nuvio.app.features.settings
|
|||
|
||||
import com.nuvio.app.core.ui.AppTheme
|
||||
import com.nuvio.app.core.ui.NativeTabBridge
|
||||
import com.nuvio.app.core.ui.ThemeColors
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
|
@ -95,12 +96,5 @@ object ThemeSettingsRepository {
|
|||
}
|
||||
}
|
||||
|
||||
private fun AppTheme.nativeTabAccentHex(): String = when (this) {
|
||||
AppTheme.CRIMSON -> "#E53935"
|
||||
AppTheme.OCEAN -> "#1E88E5"
|
||||
AppTheme.VIOLET -> "#8E24AA"
|
||||
AppTheme.EMERALD -> "#43A047"
|
||||
AppTheme.AMBER -> "#FB8C00"
|
||||
AppTheme.ROSE -> "#D81B60"
|
||||
AppTheme.WHITE -> "#F5F5F5"
|
||||
}
|
||||
private fun AppTheme.nativeTabAccentHex(): String =
|
||||
ThemeColors.getColorPalette(this).nativeAccentHex
|
||||
|
|
|
|||
|
|
@ -19,20 +19,21 @@ import androidx.compose.ui.text.font.FontWeight
|
|||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import coil3.compose.AsyncImage
|
||||
import com.nuvio.app.core.i18n.localizedByteUnit
|
||||
import com.nuvio.app.core.ui.NuvioTokens
|
||||
import com.nuvio.app.core.ui.nuvio
|
||||
import kotlin.math.round
|
||||
import nuvio.composeapp.generated.resources.Res
|
||||
import nuvio.composeapp.generated.resources.streams_size
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
|
||||
internal object StreamBadgeChipDefaults {
|
||||
val shape = RoundedCornerShape(6.dp)
|
||||
val fileSizeHorizontalPadding = 6.dp
|
||||
val fileSizeFontSize: TextUnit = 12.sp
|
||||
val fileSizeLineHeight: TextUnit = 16.sp
|
||||
val fileSizeLetterSpacing: TextUnit = 0.sp
|
||||
val shape = RoundedCornerShape(NuvioTokens.Radius.sm)
|
||||
val fileSizeHorizontalPadding = NuvioTokens.Space.s6
|
||||
val fileSizeFontSize: TextUnit = NuvioTokens.Type.labelSm
|
||||
val fileSizeLineHeight: TextUnit = NuvioTokens.LineHeight.bodySm
|
||||
val fileSizeLetterSpacing: TextUnit = NuvioTokens.LetterSpacing.none
|
||||
}
|
||||
|
||||
internal enum class StreamBadgeChipSize(
|
||||
|
|
@ -83,7 +84,7 @@ internal fun StreamBadgeChip(
|
|||
chipModifier = chipModifier.background(Color(backgroundColorArgb), shape)
|
||||
}
|
||||
if (outlineColorArgb != null) {
|
||||
chipModifier = chipModifier.border(1.dp, Color(outlineColorArgb), shape)
|
||||
chipModifier = chipModifier.border(NuvioTokens.Border.thin, Color(outlineColorArgb), shape)
|
||||
}
|
||||
|
||||
Box(
|
||||
|
|
@ -117,6 +118,7 @@ internal fun StreamBadgeImage(badge: StreamBadge) {
|
|||
|
||||
@Composable
|
||||
internal fun StreamFileSizeBadge(stream: StreamItem) {
|
||||
val tokens = MaterialTheme.nuvio
|
||||
val bytes = stream.behaviorHints.videoSize ?: return
|
||||
val gib = bytes.toDouble() / (1024.0 * 1024.0 * 1024.0)
|
||||
val sizeLabel = if (gib >= 1.0) {
|
||||
|
|
@ -132,8 +134,8 @@ internal fun StreamFileSizeBadge(stream: StreamItem) {
|
|||
modifier = Modifier
|
||||
.height(StreamBadgeChipSize.STREAM.containerHeight)
|
||||
.clip(badgeShape)
|
||||
.background(Color(0xFF0A0C0C))
|
||||
.border(1.dp, Color(0xFF0A0C0C), badgeShape)
|
||||
.background(tokens.colors.surfacePopover)
|
||||
.border(tokens.borders.thin, tokens.colors.borderSubtle, badgeShape)
|
||||
.padding(horizontal = StreamBadgeChipDefaults.fileSizeHorizontalPadding),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
|
|
@ -145,7 +147,7 @@ internal fun StreamFileSizeBadge(stream: StreamItem) {
|
|||
fontWeight = FontWeight.Bold,
|
||||
letterSpacing = StreamBadgeChipDefaults.fileSizeLetterSpacing,
|
||||
),
|
||||
color = Color.White,
|
||||
color = tokens.colors.textPrimary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package com.nuvio.app
|
|||
import androidx.compose.ui.window.ComposeUIViewController
|
||||
import platform.UIKit.UIColor
|
||||
|
||||
private val nuvioBackgroundColor = UIColor(red = 0.008, green = 0.016, blue = 0.016, alpha = 1.0)
|
||||
private val nuvioBackgroundColor = UIColor(red = 0.051, green = 0.051, blue = 0.051, alpha = 1.0)
|
||||
|
||||
fun MainViewController() = ComposeUIViewController {
|
||||
App()
|
||||
|
|
|
|||
Loading…
Reference in a new issue