Adjust theme colors (#302)

Adjusts some theme colors for various controls

Fixes #295
This commit is contained in:
damontecres 2025-11-23 19:50:28 -05:00 committed by GitHub
parent 5fbe12e6cf
commit 7e21033f58
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 438 additions and 52 deletions

View file

@ -13,7 +13,9 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.tv.material3.Icon
import androidx.tv.material3.MaterialTheme
import com.github.damontecres.wholphin.preferences.AppThemeColors
import com.github.damontecres.wholphin.ui.PreviewTvSpec
import com.github.damontecres.wholphin.ui.theme.LocalTheme
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
@Composable
@ -21,15 +23,42 @@ fun WatchedIcon(modifier: Modifier = Modifier) {
Icon(
imageVector = Icons.Default.Check,
contentDescription = null,
tint = Color.White,
tint = WatchedIconColor(),
modifier =
modifier
.background(MaterialTheme.colorScheme.border.copy(alpha = 1f), shape = CircleShape)
.background(WatchedIconBackground(), shape = CircleShape)
.border(.5.dp, Color.Black, CircleShape)
.padding(2.dp),
)
}
@Composable
fun WatchedIconBackground(): Color =
when (LocalTheme.current) {
AppThemeColors.UNRECOGNIZED,
AppThemeColors.PURPLE,
AppThemeColors.BLUE,
AppThemeColors.GREEN,
AppThemeColors.ORANGE,
AppThemeColors.BOLD_BLUE,
-> MaterialTheme.colorScheme.border.copy(alpha = 1f)
AppThemeColors.OLED_BLACK -> MaterialTheme.colorScheme.secondaryContainer
}
@Composable
fun WatchedIconColor(): Color =
when (LocalTheme.current) {
AppThemeColors.UNRECOGNIZED,
AppThemeColors.PURPLE,
AppThemeColors.BLUE,
AppThemeColors.GREEN,
AppThemeColors.ORANGE,
AppThemeColors.BOLD_BLUE,
AppThemeColors.OLED_BLACK,
-> Color.White // MaterialTheme.colorScheme.onSurface
}
@PreviewTvSpec
@Composable
private fun WatchedIconPreview() {

View file

@ -20,9 +20,12 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.compositeOver
import androidx.compose.ui.unit.dp
import androidx.tv.material3.MaterialTheme
import com.github.damontecres.wholphin.preferences.AppThemeColors
import com.github.damontecres.wholphin.ui.handleDPadKeyEvents
import com.github.damontecres.wholphin.ui.theme.LocalTheme
/**
* A TV capable control for choosing a value
@ -46,6 +49,9 @@ fun SliderBar(
var currentValue by remember(value) { mutableLongStateOf(value) }
val percent = (currentValue - min).toFloat() / (max - min)
val activeColor = SliderActiveColor(isFocused)
val inactiveColor = SliderInactiveColor(isFocused)
val handleSeekEventModifier =
Modifier.handleDPadKeyEvents(
triggerOnAction = KeyEvent.ACTION_DOWN,
@ -85,14 +91,14 @@ fun SliderBar(
onDraw = {
val yOffset = size.height.div(2)
drawLine(
color = color.copy(alpha = 0.15f),
color = inactiveColor,
start = Offset(x = 0f, y = yOffset),
end = Offset(x = size.width, y = yOffset),
strokeWidth = size.height,
cap = StrokeCap.Round,
)
drawLine(
color = color,
color = activeColor,
start = Offset(x = 0f, y = yOffset),
end =
Offset(
@ -112,3 +118,57 @@ fun SliderBar(
)
}
}
@Composable
fun SliderActiveColor(focused: Boolean): Color {
val theme = LocalTheme.current
return when (theme) {
AppThemeColors.UNRECOGNIZED,
AppThemeColors.PURPLE,
AppThemeColors.BLUE,
AppThemeColors.GREEN,
AppThemeColors.ORANGE,
-> MaterialTheme.colorScheme.border
AppThemeColors.BOLD_BLUE ->
if (focused) {
MaterialTheme.colorScheme.border
} else {
MaterialTheme.colorScheme.border
}
AppThemeColors.OLED_BLACK ->
if (focused) {
MaterialTheme.colorScheme.primaryContainer
} else {
MaterialTheme.colorScheme.border
}
}
}
@Composable
fun SliderInactiveColor(focused: Boolean): Color {
val theme = LocalTheme.current
return when (theme) {
AppThemeColors.UNRECOGNIZED,
AppThemeColors.PURPLE,
->
MaterialTheme.colorScheme.border
.copy(alpha = .25f)
.compositeOver(MaterialTheme.colorScheme.surfaceVariant)
.copy(alpha = .66f)
AppThemeColors.BLUE,
AppThemeColors.GREEN,
AppThemeColors.ORANGE,
AppThemeColors.BOLD_BLUE,
-> MaterialTheme.colorScheme.secondaryContainer.copy(alpha = .66f)
AppThemeColors.OLED_BLACK ->
if (focused) {
MaterialTheme.colorScheme.tertiaryContainer
} else {
MaterialTheme.colorScheme.primaryContainer
}
}
}

View file

@ -73,6 +73,7 @@ import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.data.NavDrawerItemRepository
import com.github.damontecres.wholphin.data.model.JellyfinServer
import com.github.damontecres.wholphin.data.model.JellyfinUser
import com.github.damontecres.wholphin.preferences.AppThemeColors
import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.services.NavigationManager
import com.github.damontecres.wholphin.ui.FontAwesome
@ -82,6 +83,7 @@ import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.ui.preferences.PreferenceScreenOption
import com.github.damontecres.wholphin.ui.setValueOnMain
import com.github.damontecres.wholphin.ui.spacedByWithFooter
import com.github.damontecres.wholphin.ui.theme.LocalTheme
import com.github.damontecres.wholphin.ui.toServerString
import com.github.damontecres.wholphin.ui.tryRequestFocus
import dagger.hilt.android.lifecycle.HiltViewModel
@ -647,17 +649,43 @@ fun navItemColor(
focused: Boolean,
drawerOpen: Boolean,
): Color {
val alpha =
when {
drawerOpen -> .75f
selected && !drawerOpen -> .5f
else -> .2f
val theme = LocalTheme.current
if (theme == AppThemeColors.OLED_BLACK) {
return when {
selected && focused -> Color.Black
selected && !drawerOpen -> Color.White.copy(alpha = .5f)
selected && drawerOpen -> Color.White.copy(alpha = .85f)
focused -> Color.Black.copy(alpha = .5f)
drawerOpen -> Color(0xFF707070)
else -> Color(0xFF505050).copy(alpha = .66f)
}
return when {
selected -> MaterialTheme.colorScheme.border
focused -> LocalContentColor.current
else -> MaterialTheme.colorScheme.onSurface
}.copy(alpha = alpha)
} else {
val alpha =
when {
drawerOpen -> .85f
selected && !drawerOpen -> .5f
else -> .2f
}
return when {
selected && focused ->
when (theme) {
AppThemeColors.UNRECOGNIZED,
AppThemeColors.PURPLE,
AppThemeColors.BLUE,
AppThemeColors.GREEN,
AppThemeColors.ORANGE,
-> MaterialTheme.colorScheme.border
AppThemeColors.BOLD_BLUE,
AppThemeColors.OLED_BLACK,
-> MaterialTheme.colorScheme.primary
}
selected -> MaterialTheme.colorScheme.border
focused -> LocalContentColor.current
else -> MaterialTheme.colorScheme.onSurface
}.copy(alpha = alpha)
}
}
val DrawerState.isOpen: Boolean get() = this.currentValue == DrawerValue.Open

View file

@ -5,8 +5,12 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.tv.material3.ListItem
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Switch
import androidx.tv.material3.SwitchColors
import androidx.tv.material3.SwitchDefaults
import com.github.damontecres.wholphin.preferences.AppThemeColors
import com.github.damontecres.wholphin.ui.theme.LocalTheme
@Composable
fun SwitchPreference(
@ -52,12 +56,32 @@ fun SwitchPreference(
Switch(
checked = value,
onCheckedChange = { onClick.invoke() },
colors =
SwitchDefaults
.colors(),
colors = SwitchColors(),
)
},
interactionSource = interactionSource,
modifier = modifier,
)
}
@Composable
fun SwitchColors(): SwitchColors {
val theme = LocalTheme.current
return when (theme) {
AppThemeColors.UNRECOGNIZED,
AppThemeColors.PURPLE,
AppThemeColors.BLUE,
->
SwitchDefaults.colors()
AppThemeColors.GREEN,
AppThemeColors.ORANGE,
AppThemeColors.BOLD_BLUE,
AppThemeColors.OLED_BLACK,
->
SwitchDefaults.colors(
checkedThumbColor = MaterialTheme.colorScheme.onPrimary,
uncheckedThumbColor = MaterialTheme.colorScheme.onPrimary,
)
}
}

View file

@ -1,8 +1,8 @@
package com.github.damontecres.wholphin.ui.theme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import androidx.tv.material3.MaterialTheme
import com.github.damontecres.wholphin.preferences.AppThemeColors
import com.github.damontecres.wholphin.ui.theme.colors.BlueThemeColors
@ -12,21 +12,8 @@ import com.github.damontecres.wholphin.ui.theme.colors.OledThemeColors
import com.github.damontecres.wholphin.ui.theme.colors.OrangeThemeColors
import com.github.damontecres.wholphin.ui.theme.colors.PurpleThemeColors
@Immutable
data class ColorFamily(
val color: Color,
val onColor: Color,
val colorContainer: Color,
val onColorContainer: Color,
)
val unspecified_scheme =
ColorFamily(
Color.Unspecified,
Color.Unspecified,
Color.Unspecified,
Color.Unspecified,
)
val LocalTheme =
compositionLocalOf<AppThemeColors> { AppThemeColors.PURPLE }
@Composable
fun WholphinTheme(
@ -50,14 +37,16 @@ fun WholphinTheme(
darkTheme -> themeColors.darkScheme
else -> themeColors.lightScheme
}
androidx.compose.material3.MaterialTheme(
colorScheme = if (darkTheme) themeColors.darkSchemeMaterial else themeColors.lightSchemeMaterial,
typography = androidx.compose.material3.Typography(),
) {
MaterialTheme(
colorScheme = colorScheme,
typography = AppTypography,
content = content,
)
CompositionLocalProvider(LocalTheme provides appThemeColors) {
androidx.compose.material3.MaterialTheme(
colorScheme = if (darkTheme) themeColors.darkSchemeMaterial else themeColors.lightSchemeMaterial,
typography = androidx.compose.material3.Typography(),
) {
MaterialTheme(
colorScheme = colorScheme,
typography = AppTypography,
content = content,
)
}
}
}

View file

@ -0,0 +1,256 @@
package com.github.damontecres.wholphin.ui.theme
import android.content.res.Configuration.UI_MODE_TYPE_TELEVISION
import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.FocusInteraction
import androidx.compose.foundation.interaction.Interaction
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
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.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.tv.material3.LocalContentColor
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.NavigationDrawerScope
import androidx.tv.material3.Text
import androidx.tv.material3.surfaceColorAtElevation
import com.github.damontecres.wholphin.preferences.AppPreference
import com.github.damontecres.wholphin.preferences.AppThemeColors
import com.github.damontecres.wholphin.ui.AspectRatios
import com.github.damontecres.wholphin.ui.cards.BannerCard
import com.github.damontecres.wholphin.ui.cards.SeasonCard
import com.github.damontecres.wholphin.ui.cards.WatchedIcon
import com.github.damontecres.wholphin.ui.nav.NavDrawerItem
import com.github.damontecres.wholphin.ui.nav.NavItem
import com.github.damontecres.wholphin.ui.preferences.SliderPreference
import com.github.damontecres.wholphin.ui.preferences.SwitchPreference
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
@Preview(
device = "spec:width=1200dp,height=2000dp",
backgroundColor = 0xFF383535,
uiMode = UI_MODE_TYPE_TELEVISION,
)
@Composable
private fun ThemePreview() {
Column {
LazyVerticalGrid(
columns = GridCells.Fixed(2),
contentPadding = PaddingValues(16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
items(AppThemeColors.entries.filterNot { it == AppThemeColors.UNRECOGNIZED }) {
ThemeExample(it)
}
}
}
}
@Composable
private fun ThemeExample(theme: AppThemeColors) {
val source = remember { PreviewInteractionSource() }
WholphinTheme(appThemeColors = theme) {
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface) {
Column(Modifier.background(MaterialTheme.colorScheme.background)) {
Text(
text = theme.toString(),
color = Color.White,
)
Column(
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
Row(
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth(),
) {
WatchedIcon()
BannerCard(
name = "Card",
imageUrl = null,
onClick = { },
onLongClick = {},
playPercent = .5,
cardHeight = 64.dp,
)
BannerCard(
name = "Card",
imageUrl = null,
onClick = { },
onLongClick = {},
playPercent = .5,
cardHeight = 64.dp,
interactionSource = source,
)
SeasonCard(
title = "Card",
subtitle = "2025",
name = "N/A",
imageUrl = "abc",
isFavorite = true,
isPlayed = true,
unplayedItemCount = 2,
playedPercentage = 50.0,
onClick = { },
onLongClick = {},
imageHeight = 120.dp,
interactionSource = source,
showImageOverlay = true,
aspectRatio = AspectRatios.TALL,
)
SeasonCard(
title = "Watched",
subtitle = "2025",
name = "N/A",
imageUrl = "abc",
isFavorite = false,
isPlayed = true,
unplayedItemCount = 2,
playedPercentage = 0.0,
onClick = { },
onLongClick = {},
imageHeight = 120.dp,
// interactionSource = source,
showImageOverlay = true,
aspectRatio = AspectRatios.SQUARE,
)
}
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
SliderPreference(
preference = AppPreference.AutoPlayNextDelay,
title = "Slider",
summary = "5 seconds",
value = 30,
onChange = {},
modifier = Modifier.weight(1f),
)
SliderPreference(
preference = AppPreference.AutoPlayNextDelay,
title = "Slider",
summary = "5 seconds",
value = 30,
onChange = {},
modifier = Modifier.weight(1f),
interactionSource = source,
)
}
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
SwitchPreference(
title = "Switch",
value = false,
onClick = {},
summaryOn = "Enabled",
summaryOff = "Disabled",
modifier = Modifier.weight(1f),
)
SwitchPreference(
title = "Switch",
value = false,
onClick = {},
summaryOn = "Enabled",
summaryOff = "Disabled",
modifier = Modifier.weight(1f),
interactionSource = source,
)
}
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
SwitchPreference(
title = "Switch",
value = true,
onClick = {},
summaryOn = "Enabled",
summaryOff = "Disabled",
modifier = Modifier.weight(1f),
)
SwitchPreference(
title = "Switch",
value = true,
onClick = {},
summaryOn = "Enabled",
summaryOff = "Disabled",
modifier = Modifier.weight(1f),
interactionSource = source,
)
}
val navScope = NavScope(true)
Row(
horizontalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier.background(MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp)),
) {
navScope.apply {
NavItem(
library = NavDrawerItem.Favorites,
onClick = { },
selected = false,
moreExpanded = false,
drawerOpen = true,
modifier = Modifier,
)
NavItem(
library = NavDrawerItem.Favorites,
onClick = { },
selected = false,
moreExpanded = false,
drawerOpen = true,
modifier = Modifier,
interactionSource = source,
)
}
}
Row(
horizontalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier.background(MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp)),
) {
navScope.apply {
NavItem(
library = NavDrawerItem.Favorites,
onClick = { },
selected = true,
moreExpanded = false,
drawerOpen = true,
modifier = Modifier,
)
NavItem(
library = NavDrawerItem.Favorites,
onClick = { },
selected = true,
moreExpanded = false,
drawerOpen = true,
modifier = Modifier,
interactionSource = source,
)
}
}
}
}
}
}
}
class PreviewInteractionSource : MutableInteractionSource {
override val interactions: Flow<Interaction>
get() = flowOf(FocusInteraction.Focus())
override suspend fun emit(interaction: Interaction) {
}
override fun tryEmit(interaction: Interaction): Boolean = false
}
class NavScope(
override val hasFocus: Boolean,
) : NavigationDrawerScope

View file

@ -51,7 +51,7 @@ val BlueThemeColors =
val onSecondaryDark = Color(0xFF283141)
val secondaryContainerDark = Color(0xFF3E4759)
val onSecondaryContainerDark = Color(0xFFDAE2F9)
val tertiaryDark = Color(0xFFCABEFF)
val tertiaryDark = Color(0xFFBECEFF)
val onTertiaryDark = Color(0xFF31285F)
val tertiaryContainerDark = Color(0xFF483F77)
val onTertiaryContainerDark = Color(0xFFE6DEFF)

View file

@ -52,7 +52,7 @@ val BoldBlueThemeColors =
val onSecondaryDark = Color(0xFF283141)
val secondaryContainerDark = Color(0xFF3E4759)
val onSecondaryContainerDark = Color(0xFFDAE2F9)
val tertiaryDark = Color(0xFFCABEFF)
val tertiaryDark = Color(0xFFBEC9FF)
val onTertiaryDark = Color(0xFF28345F)
val tertiaryContainerDark = Color(0xFF8CDDE8)
val onTertiaryContainerDark = Color(0xFFE6DEFF)

View file

@ -51,7 +51,7 @@ val GreenThemeColors =
val onSecondaryDark = Color(0xFF223526)
val secondaryContainerDark = Color(0xFF394B3C)
val onSecondaryContainerDark = Color(0xFFD2E8D3)
val tertiaryDark = Color(0xFFA2CED9)
val tertiaryDark = Color(0xFF81D29C)
val onTertiaryDark = Color(0xFF01363F)
val tertiaryContainerDark = Color(0xFF204D56)
val onTertiaryContainerDark = Color(0xFFBDEAF5)

View file

@ -46,13 +46,13 @@ val OledThemeColors =
val onDark = Color(0xC0FFFFFF)
val primaryDark = Color(0xFFFFFFFF)
val onPrimaryDark = Color(0xC04D4B4B)
val primaryDark = Color(0xC04D4B4B)
val onPrimaryDark = Color(0xFFFFFFFF)
val primaryContainerDark = Color(0xFF505050)
val onPrimaryContainerDark = onDark
val secondaryDark = Color(0xFF808080)
val onSecondaryDark = onDark
val secondaryContainerDark = Color(0xFF3E4759)
val secondaryContainerDark = Color(0xFF505050)
val onSecondaryContainerDark = onDark
val tertiaryDark = Color(0xFFDEDEDE)
val onTertiaryDark = Color(0xFF363535)
@ -71,7 +71,7 @@ val OledThemeColors =
val outlineDark = Color(0xFF8E9099)
val outlineVariantDark = Color(0xFF44474F)
val scrimDark = Color(0xFF000000)
val inverseSurfaceDark = onDark
val inverseSurfaceDark = Color(0xFFE8E8E8)
val inverseOnSurfaceDark = Color(0xFF000000)
val inversePrimaryDark = Color(0xC0F9F4FF)
val surfaceDimDark = Color(0xFF121318)

View file

@ -50,7 +50,7 @@ val OrangeThemeColors =
val onPrimaryContainerDark = Color(0xFFFFE086)
val secondaryDark = Color(0xFFD4C5A1)
val onSecondaryDark = Color(0xFF383016)
val secondaryContainerDark = Color(0xFF50462A)
val secondaryContainerDark = Color(0xFF524127)
val onSecondaryContainerDark = Color(0xFFF1E1BB)
val tertiaryDark = Color(0xFFFCE574)
val onTertiaryDark = Color(0xFF492900)

View file

@ -42,7 +42,7 @@ val PurpleThemeColors =
val onSecondaryDark = Color(0xFF3B167D)
val secondaryContainerDark = Color(0xFF533295)
val onSecondaryContainerDark = Color(0xFFC2A6FF)
val tertiaryDark = Color(0xFFC071F8)
val tertiaryDark = Color(0xFFA071F8)
val onTertiaryDark = Color(0xFF5E0052)
val tertiaryContainerDark = Color(0xFFB800A3)
val onTertiaryContainerDark = Color(0xFFFFD7F0)