Skip to content
8 changes: 4 additions & 4 deletions app/src/main/kotlin/com/arflix/tv/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ import com.arflix.tv.util.DeviceType
import com.arflix.tv.util.DEVICE_MODE_OVERRIDE_KEY
import com.arflix.tv.util.SKIP_PROFILE_SELECTION_KEY
import com.arflix.tv.util.OLED_BLACK_BACKGROUND_KEY
import com.arflix.tv.util.FOCUS_BORDER_COLOR_KEY
import com.arflix.tv.util.ACCENT_COLOR_KEY
import com.arflix.tv.util.LocalDeviceType
import com.arflix.tv.util.LocalHasTouchScreen
import com.arflix.tv.util.LocalAppLanguage
Expand Down Expand Up @@ -266,8 +266,8 @@ class MainActivity : ComponentActivity() {
val oledBlackBackground by remember {
this@MainActivity.settingsDataStore.data.map { it[OLED_BLACK_BACKGROUND_KEY] ?: false }
}.collectAsStateWithLifecycle(initialValue = false)
val focusBorderColorName by remember {
this@MainActivity.settingsDataStore.data.map { it[FOCUS_BORDER_COLOR_KEY] }
val accentColorName by remember {
this@MainActivity.settingsDataStore.data.map { it[ACCENT_COLOR_KEY] }
}.collectAsStateWithLifecycle(initialValue = null)
val activeProfileId by remember {
profileRepository.get().activeProfileId
Expand Down Expand Up @@ -324,7 +324,7 @@ class MainActivity : ComponentActivity() {
) {
ArflixTvTheme(
oledBlackBackground = oledBlackBackground,
focusBorderColorName = focusBorderColorName
accentColorName = accentColorName
) {
val startupState by startupViewModel.state.collectAsStateWithLifecycle()
ArflixApp(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import com.arflix.tv.ui.components.normalizeCardLayoutMode
import com.arflix.tv.ui.components.profileCatalogueRowLayoutModeKey
import com.arflix.tv.util.LAST_APP_LANGUAGE_KEY
import com.arflix.tv.util.AppLogger
import com.arflix.tv.util.FOCUS_BORDER_COLOR_KEY
import com.arflix.tv.util.ACCENT_COLOR_KEY
import com.arflix.tv.util.OLED_BLACK_BACKGROUND_KEY
import com.arflix.tv.util.SKIP_PROFILE_SELECTION_KEY
import com.arflix.tv.util.settingsDataStore
Expand Down Expand Up @@ -396,7 +396,9 @@ class CloudSyncRepository @Inject constructor(
root.put("dnsProvider", globalDnsProvider)
root.put("customUserAgent", prefs[customUserAgentKey] ?: "")
root.put("oledBlackBackground", prefs[OLED_BLACK_BACKGROUND_KEY] ?: false)
root.put("focusBorderColor", prefs[FOCUS_BORDER_COLOR_KEY] ?: "White")
val accentColor = prefs[ACCENT_COLOR_KEY] ?: "White"
root.put("accentColor", accentColor)
root.put("focusBorderColor", accentColor)
root.put("subtitleUsageJson", prefs[subtitleUsageKey()] ?: "")
root.put("subtitleSettingsUpdatedAt", prefs[subtitleSettingsUpdatedAtKey()]?.toLongOrNull() ?: 0L)
root.put("skipProfileSelection", prefs[SKIP_PROFILE_SELECTION_KEY] ?: false)
Expand Down Expand Up @@ -880,6 +882,7 @@ class CloudSyncRepository @Inject constructor(
root.has("dnsProvider") ||
root.has("customUserAgent") ||
root.has("oledBlackBackground") ||
root.has("accentColor") ||
root.has("focusBorderColor")
) {
context.settingsDataStore.edit { prefs ->
Expand All @@ -901,8 +904,11 @@ class CloudSyncRepository @Inject constructor(
if (root.has("oledBlackBackground")) {
prefs[OLED_BLACK_BACKGROUND_KEY] = root.optBoolean("oledBlackBackground", false)
}
if (root.has("focusBorderColor")) {
prefs[FOCUS_BORDER_COLOR_KEY] = root.optString("focusBorderColor", "White").ifBlank { "White" }
if (root.has("accentColor") || root.has("focusBorderColor")) {
prefs[ACCENT_COLOR_KEY] = root.optString(
"accentColor",
root.optString("focusBorderColor", "White")
).ifBlank { "White" }
}
}
restoredDnsProvider?.let { OkHttpProvider.setDnsProvider(OkHttpProvider.parseDnsProvider(it)) }
Expand Down
17 changes: 11 additions & 6 deletions app/src/main/kotlin/com/arflix/tv/ui/components/AppTopBar.kt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import androidx.tv.material3.ExperimentalTvMaterial3Api
import androidx.tv.material3.Text
import com.arflix.tv.data.model.Profile
import com.arflix.tv.ui.skin.ArvioSkin
import com.arflix.tv.ui.skin.resolveAccentColor
import com.arflix.tv.ui.theme.AnimationConstants
import com.arflix.tv.ui.theme.ArflixTypography
import androidx.compose.ui.res.stringResource
Expand Down Expand Up @@ -193,6 +194,8 @@ private fun TopBarNavChip(
isFocused: Boolean,
isSelected: Boolean
) {
val accent = resolveAccentColor(fallback = Color.White)

val containerColor by animateColorAsState(
targetValue = when {
isFocused -> Color.White.copy(alpha = 0.2f)
Expand All @@ -204,17 +207,17 @@ private fun TopBarNavChip(
)
val iconColor by animateColorAsState(
targetValue = when {
isFocused -> Color.White
isSelected -> Color.White.copy(alpha = 0.92f)
isFocused -> Color.White // focused icon stays white (wins over selected)
isSelected -> accent // selected icon gets accent
else -> Color.White.copy(alpha = 0.62f)
},
animationSpec = tween(AnimationConstants.DURATION_FAST),
label = "topbar_icon_color"
)
val textColor by animateColorAsState(
targetValue = when {
isFocused -> Color.White
isSelected -> Color.White.copy(alpha = 0.92f)
isFocused -> Color.White // focused text stays white (wins over selected)
isSelected -> accent // selected text gets accent
else -> Color.White.copy(alpha = 0.68f)
},
animationSpec = tween(AnimationConstants.DURATION_FAST),
Expand Down Expand Up @@ -270,10 +273,12 @@ private fun TopBarSettingsGear(
isSelected: Boolean,
hasBadge: Boolean = false
) {
val accent = resolveAccentColor(fallback = Color.White)

val iconColor by animateColorAsState(
targetValue = when {
isFocused -> Color.White
isSelected -> Color.White.copy(alpha = 0.92f)
isFocused -> Color.White // focused stays white (wins over selected)
isSelected -> accent // selected settings gear gets accent
else -> Color.White.copy(alpha = 0.5f)
},
animationSpec = tween(AnimationConstants.DURATION_FAST),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.key.Key
import com.arflix.tv.ui.skin.resolveFocusBorderColor
import com.arflix.tv.ui.skin.resolveAccentColor
import androidx.compose.ui.input.key.KeyEventType
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.onKeyEvent
Expand Down Expand Up @@ -545,7 +545,7 @@ private fun HorizontalKnownForCard(
) {
val cardWidth = 220.dp
val cardHeight = 124.dp // 16:9 aspect ratio
val focusRingColor = resolveFocusBorderColor(fallback = Color.White)
val focusRingColor = resolveAccentColor(fallback = Color.White)

Column(
modifier = Modifier.width(cardWidth),
Expand Down
9 changes: 6 additions & 3 deletions app/src/main/kotlin/com/arflix/tv/ui/components/Sidebar.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import androidx.tv.material3.ExperimentalTvMaterial3Api
import androidx.tv.material3.Text
import com.arflix.tv.data.model.Profile
import com.arflix.tv.ui.skin.ArvioSkin
import com.arflix.tv.ui.skin.resolveAccentColor
import com.arflix.tv.ui.theme.AnimationConstants
import androidx.annotation.StringRes
import androidx.compose.ui.res.stringResource
Expand Down Expand Up @@ -211,11 +212,13 @@ private fun SidebarIcon(
isFocused: Boolean,
hasBadge: Boolean = false
) {
// Animated icon color - dark grey normally, pure white when focused
val accent = resolveAccentColor(fallback = Color.White)

// Animated icon color - white when focused, accent when selected-only
val iconColor by animateColorAsState(
targetValue = when {
isFocused -> Color.White // Pure white when focused
isSelected -> Color(0xFF666666) // Dark grey when selected
isFocused -> Color.White // white when D-pad navigating (wins over selected)
isSelected -> accent // ROYGBIV accent when selected (current screen)
else -> Color(0xFF444444) // Darker grey when unfocused
},
animationSpec = tween(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ import com.arflix.tv.ui.focus.rememberArvioDpadRepeatGate
import com.arflix.tv.ui.skin.ArvioFocusableSurface
import com.arflix.tv.ui.skin.ArvioSkin
import com.arflix.tv.ui.skin.rememberArvioCardShape
import com.arflix.tv.ui.skin.resolveAccentColor
import com.arflix.tv.ui.theme.AnimationConstants
import com.arflix.tv.ui.theme.ArflixTypography
import com.arflix.tv.ui.theme.BackgroundCard
Expand Down Expand Up @@ -3138,20 +3139,27 @@ private fun PremiumActionButton(
label = "button_scale"
)

// Animated background color - buttons only glow when focused
// Resolve accent color for focused button backgrounds
val accent = resolveAccentColor(fallback = Color.White)

// Animated background color - button fills with accent when focused
val backgroundColor by animateColorAsState(
targetValue = when {
isFocused && isPrimary -> Color.White
isFocused -> Color.White.copy(alpha = 0.95f)
isFocused && isPrimary -> accent
isFocused -> accent
Comment on lines +3148 to +3149
else -> Color.Transparent
},
animationSpec = tween(150),
label = "button_bg"
)

// Animated text/icon color - black when focused (on white bg), white otherwise
// Animated text/icon color - white on accent bg when focused, white otherwise
// Use dark text for light accent colors (White, Yellow) to ensure contrast
val contentColor by animateColorAsState(
targetValue = if (isFocused) Color.Black else Color.White.copy(alpha = 0.9f),
targetValue = if (isFocused) {
val l = 0.299f * accent.red + 0.587f * accent.green + 0.114f * accent.blue
if (l > 0.5f) Color.Black else Color.White
} else Color.White.copy(alpha = 0.9f),
animationSpec = tween(150),
label = "button_content"
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ import androidx.compose.ui.text.style.TextOverflow
import com.arflix.tv.util.LocalDeviceType
import com.arflix.tv.util.settingsDataStore
import com.arflix.tv.util.weightedSubtitleScore
import com.arflix.tv.ui.skin.LocalFocusBorderColorOverride
import com.arflix.tv.ui.skin.LocalAccentColorOverride
import com.arflix.tv.ui.theme.ArflixTypography
import com.arflix.tv.ui.theme.Pink
import com.arflix.tv.ui.theme.PurpleDark
Expand Down Expand Up @@ -199,7 +199,7 @@ fun PlayerScreen(
onBack: () -> Unit = {},
onPlayNext: (Int, Int, String?, String?, String?) -> Unit = { _, _, _, _, _ -> }
) {
val playerAccent = LocalFocusBorderColorOverride.current ?: Color.White
val playerAccent = LocalAccentColorOverride.current ?: Color.White
val context = LocalContext.current
val activity = remember(context) { context.findActivity() }
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
Expand Down Expand Up @@ -3181,7 +3181,7 @@ private fun PlayerIconButton(
onUpKey: () -> Unit = {},
onDownKey: () -> Unit = {}
) {
val btnAccent = LocalFocusBorderColorOverride.current ?: Color.White
val btnAccent = LocalAccentColorOverride.current ?: Color.White
var focused by remember { mutableStateOf(false) }
val scale by animateFloatAsState(if (focused) 1.15f else 1f, label = "iconScale")

Expand Down Expand Up @@ -3342,7 +3342,7 @@ private fun ErrorButton(
isPrimary: Boolean,
onClick: () -> Unit
) {
val btnAccent = LocalFocusBorderColorOverride.current ?: Color.White
val btnAccent = LocalAccentColorOverride.current ?: Color.White
val scale by animateFloatAsState(if (isFocused) 1.05f else 1f, label = "scale")

Box(
Expand Down Expand Up @@ -4853,7 +4853,7 @@ private fun PlayerSubtitleSettingsPanel(
onVerticalDecrease: () -> Unit,
onVerticalIncrease: () -> Unit
) {
val accent = LocalFocusBorderColorOverride.current ?: Color.White
val accent = LocalAccentColorOverride.current ?: Color.White

val absMs = if (syncOffsetMs < 0) -syncOffsetMs else syncOffsetMs
val offsetLabel = if (syncOffsetMs == 0L) "0.0s"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ import com.arflix.tv.ui.focus.arvioDpadFocusGroup
import com.arflix.tv.ui.skin.ArvioFocusableSurface
import com.arflix.tv.ui.skin.ArvioSkin
import com.arflix.tv.ui.skin.rememberArvioCardShape
import com.arflix.tv.ui.skin.resolveFocusBorderColor
import com.arflix.tv.ui.skin.resolveAccentColor
import com.arflix.tv.ui.theme.ArflixTypography
import com.arflix.tv.ui.theme.BackgroundCard
import com.arflix.tv.ui.theme.appBackgroundDark
Expand Down Expand Up @@ -804,14 +804,14 @@ private fun GlowChip(
val focused = isVisuallyFocused || (useSystemFocusForVisuals && systemFocused)
val active = focused || isSelected
val chipShape = RoundedCornerShape(7.dp)
val focusBorderColor = resolveFocusBorderColor(fallback = Color.White)
val accentColor = resolveAccentColor(fallback = Color.White)
val backgroundColor = when {
focused -> Color.White.copy(alpha = 0.12f)
isSelected -> Color.White.copy(alpha = 0.92f)
else -> Color.White.copy(alpha = 0.075f)
}
val borderColor = when {
focused -> focusBorderColor
focused -> accentColor
isSelected -> Color.White.copy(alpha = 0.92f)
else -> Color.White.copy(alpha = 0.24f)
}
Expand Down
Loading
Loading