diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index ee18b487e..ff4be0f3d 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -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("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 diff --git a/composeApp/src/androidMain/res/values/colors.xml b/composeApp/src/androidMain/res/values/colors.xml index d324146e4..da3edf19a 100644 --- a/composeApp/src/androidMain/res/values/colors.xml +++ b/composeApp/src/androidMain/res/values/colors.xml @@ -1,4 +1,4 @@ - #020404 + #0D0D0D diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt index d6db99608..32b14c525 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt @@ -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) } } } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioBottomSheet.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioBottomSheet.kt index af5f9c265..a4731e376 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioBottomSheet.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioBottomSheet.kt @@ -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), ) -} \ No newline at end of file +} diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioComponents.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioComponents.kt index 347fdedfe..27029a306 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioComponents.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioComponents.kt @@ -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, ) } } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioContinueWatchingActionSheet.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioContinueWatchingActionSheet.kt index 693017d33..f19d3b21e 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioContinueWatchingActionSheet.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioContinueWatchingActionSheet.kt @@ -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, ) diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioDropdownChip.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioDropdownChip.kt index 85ea9052a..23c8d9b16 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioDropdownChip.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioDropdownChip.kt @@ -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), ) } }, diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioFloatingPrompt.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioFloatingPrompt.kt index b7b15ef15..e6214c9e2 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioFloatingPrompt.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioFloatingPrompt.kt @@ -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), ) } } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioNavigationBar.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioNavigationBar.kt index 5176b6a2d..2d3eb4b4a 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioNavigationBar.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioNavigationBar.kt @@ -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() diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioNetworkOfflineCard.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioNetworkOfflineCard.kt index f751e6df9..66240b671 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioNetworkOfflineCard.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioNetworkOfflineCard.kt @@ -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, diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioPosterActionSheet.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioPosterActionSheet.kt index ef0a421ca..6ad66ea4a 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioPosterActionSheet.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioPosterActionSheet.kt @@ -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, ) diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioProgressBar.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioProgressBar.kt index fc711ea04..00a3458ce 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioProgressBar.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioProgressBar.kt @@ -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), ) } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioShelfComponents.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioShelfComponents.kt index b0cf02fda..fafed653f 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioShelfComponents.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioShelfComponents.kt @@ -63,9 +63,10 @@ fun 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), ) } } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioTheme.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioTheme.kt index d86a1a81f..6cc3997b9 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioTheme.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioTheme.kt @@ -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, diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioTokenPreview.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioTokenPreview.kt new file mode 100644 index 000000000..f03cc4939 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioTokenPreview.kt @@ -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.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), + ) + } +} diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioTokens.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioTokens.kt new file mode 100644 index 000000000..79d9ffb58 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioTokens.kt @@ -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, + ), + ) +} diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioTypeScale.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioTypeScale.kt index 91ac705b4..d15ce4597 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioTypeScale.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioTypeScale.kt @@ -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), ) } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/ThemeColors.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/ThemeColors.kt index 5ee217bfe..5efd3aefa 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/ThemeColors.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/ThemeColors.kt @@ -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), diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/TraktListPickerDialog.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/TraktListPickerDialog.kt index 8684d5ae5..a9894b30b 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/TraktListPickerDialog.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/TraktListPickerDialog.kt @@ -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)) diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerEpisodesPanel.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerEpisodesPanel.kt index 02f88cc49..f6a32fe12 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerEpisodesPanel.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerEpisodesPanel.kt @@ -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, diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerSourcesPanel.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerSourcesPanel.kt index 83ef6aa17..ec1fbe891 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerSourcesPanel.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerSourcesPanel.kt @@ -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, ) } } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/profiles/ProfileSwitcherTab.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/profiles/ProfileSwitcherTab.kt index a399c8220..5e812b28e 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/profiles/ProfileSwitcherTab.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/profiles/ProfileSwitcherTab.kt @@ -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(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()) { diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsComponents.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsComponents.kt index ff6a17d77..edfc75da2 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsComponents.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsComponents.kt @@ -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, ), ) } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsSearch.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsSearch.kt index d19ccef29..d5fb310db 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsSearch.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsSearch.kt @@ -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, ) } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/StreamsSettingsPage.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/StreamsSettingsPage.kt index bb39a7505..7075eb1a1 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/StreamsSettingsPage.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/StreamsSettingsPage.kt @@ -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)) } } } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/ThemeSettingsRepository.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/ThemeSettingsRepository.kt index 2f1221ddd..431b9d20e 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/ThemeSettingsRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/ThemeSettingsRepository.kt @@ -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 diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/streams/StreamBadgeChip.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/streams/StreamBadgeChip.kt index 4e98fe27c..f0e9ebd2a 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/streams/StreamBadgeChip.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/streams/StreamBadgeChip.kt @@ -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, ) } } diff --git a/composeApp/src/iosMain/kotlin/com/nuvio/app/MainViewController.kt b/composeApp/src/iosMain/kotlin/com/nuvio/app/MainViewController.kt index 349dee5bb..18e9d4169 100644 --- a/composeApp/src/iosMain/kotlin/com/nuvio/app/MainViewController.kt +++ b/composeApp/src/iosMain/kotlin/com/nuvio/app/MainViewController.kt @@ -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()