From d60ddb7aa2bd4db1ed3fe9ba03022f598f220f12 Mon Sep 17 00:00:00 2001 From: papi <20916260+papi-ux@users.noreply.github.com> Date: Wed, 24 Jun 2026 18:26:31 -0400 Subject: [PATCH] feat(ui): add Portable Chrome Nova theme --- CHANGELOG.md | 4 +++ .../java/com/papi/nova/ui/NovaThemeManager.kt | 19 +++++++++-- .../papi/nova/ui/compose/NovaComposeTheme.kt | 19 ++++++++++- app/src/main/res/values-v31/styles.xml | 6 ++++ app/src/main/res/values/arrays.xml | 2 ++ app/src/main/res/values/colors_nova.xml | 26 +++++++++++++++ app/src/main/res/values/strings.xml | 1 + app/src/main/res/values/styles.xml | 26 +++++++++++++++ app/src/main/res/xml/preferences.xml | 2 +- .../com/papi/nova/ui/NovaThemeManagerTest.kt | 24 ++++++++++++++ .../papi/nova/ui/NovaThemeResourcesTest.kt | 9 ++--- .../ui/compose/NovaLibrarySurfacesTest.kt | 33 +++++++++++++++++++ 12 files changed, 163 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 315814ec..64f386bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## Unreleased + +- Adds a Portable Chrome Nova theme matching the Polaris dim Moonlight-grey early-2000s silver palette, including settings/theme-picker entries and restrained Compose library surfaces. + ## 1.1.3 - 2026-06-12 Nova 1.1.3 is the public APK publishing patch for the 1.1.2 confidence build. It preserves the same handheld Library, Command Center, NovaHUD, Insert-key, stale-session recovery, input, crash, and dependency hardening work from 1.1.2, while bumping the Android package version so GitHub Releases / Obtainium installs can update cleanly. diff --git a/app/src/main/java/com/papi/nova/ui/NovaThemeManager.kt b/app/src/main/java/com/papi/nova/ui/NovaThemeManager.kt index aea68ab9..8c5f22e6 100644 --- a/app/src/main/java/com/papi/nova/ui/NovaThemeManager.kt +++ b/app/src/main/java/com/papi/nova/ui/NovaThemeManager.kt @@ -19,6 +19,7 @@ object NovaThemeManager { const val THEME_POLARIS = "polaris" const val THEME_OLED = "oled" const val THEME_MIAMI = "miami" + const val THEME_PORTABLE_CHROME = "portable_chrome" const val THEME_HIGH_CONTRAST = "high_contrast" const val THEME_MATERIAL_YOU = "material_you" @@ -34,6 +35,8 @@ object NovaThemeManager { theme == THEME_OLED -> activity.setTheme(R.style.AppTheme_OLED) theme == THEME_MIAMI && isSettings -> activity.setTheme(R.style.SettingsTheme_Miami) theme == THEME_MIAMI -> activity.setTheme(R.style.AppTheme_Miami) + theme == THEME_PORTABLE_CHROME && isSettings -> activity.setTheme(R.style.SettingsTheme_PortableChrome) + theme == THEME_PORTABLE_CHROME -> activity.setTheme(R.style.AppTheme_PortableChrome) theme == THEME_HIGH_CONTRAST && isSettings -> activity.setTheme(R.style.SettingsTheme_HighContrast) theme == THEME_HIGH_CONTRAST -> activity.setTheme(R.style.AppTheme_HighContrast) theme == THEME_MATERIAL_YOU && isSettings -> activity.setTheme(R.style.SettingsTheme_MaterialYou) @@ -70,7 +73,7 @@ object NovaThemeManager { private fun normalizeTheme(theme: String?): String { return when (theme) { - THEME_POLARIS, THEME_OLED, THEME_MIAMI, THEME_HIGH_CONTRAST, THEME_MATERIAL_YOU -> theme + THEME_POLARIS, THEME_OLED, THEME_MIAMI, THEME_PORTABLE_CHROME, THEME_HIGH_CONTRAST, THEME_MATERIAL_YOU -> theme else -> THEME_POLARIS } } @@ -86,6 +89,7 @@ object NovaThemeManager { fun isOled(context: Context): Boolean = getTheme(context) == THEME_OLED fun isMiami(context: Context): Boolean = getTheme(context) == THEME_MIAMI + fun isPortableChrome(context: Context): Boolean = getTheme(context) == THEME_PORTABLE_CHROME fun isHighContrast(context: Context): Boolean = getTheme(context) == THEME_HIGH_CONTRAST fun isMaterialYou(context: Context): Boolean = getTheme(context) == THEME_MATERIAL_YOU @@ -93,7 +97,8 @@ object NovaThemeManager { val next = when (getTheme(context)) { THEME_POLARIS -> THEME_OLED THEME_OLED -> THEME_MIAMI - THEME_MIAMI -> THEME_HIGH_CONTRAST + THEME_MIAMI -> THEME_PORTABLE_CHROME + THEME_PORTABLE_CHROME -> THEME_HIGH_CONTRAST THEME_HIGH_CONTRAST -> if (isMaterialYouAvailable()) THEME_MATERIAL_YOU else THEME_POLARIS THEME_MATERIAL_YOU -> THEME_POLARIS else -> THEME_POLARIS @@ -106,6 +111,7 @@ object NovaThemeManager { return when (theme) { THEME_OLED -> context.getString(R.string.nova_theme_oled_label) THEME_MIAMI -> context.getString(R.string.nova_theme_miami_label) + THEME_PORTABLE_CHROME -> context.getString(R.string.nova_theme_portable_chrome_label) THEME_HIGH_CONTRAST -> context.getString(R.string.nova_theme_high_contrast_label) THEME_MATERIAL_YOU -> context.getString(R.string.nova_theme_material_you_label) else -> context.getString(R.string.nova_theme_polaris_label) @@ -130,6 +136,7 @@ object NovaThemeManager { return when { isOled(context) -> Color.BLACK isMiami(context) -> ContextCompat.getColor(context, R.color.nova_miami_bg_window) + isPortableChrome(context) -> ContextCompat.getColor(context, R.color.nova_portable_bg_window) isHighContrast(context) -> ContextCompat.getColor(context, R.color.nova_hc_bg_window) isMaterialYou(context) && isMaterialYouAvailable() -> resolveThemeColor(context, android.R.attr.colorBackground, ContextCompat.getColor(context, R.color.nova_bg_window)) @@ -160,6 +167,7 @@ object NovaThemeManager { return when { isOled(context) -> ContextCompat.getColor(context, R.color.nova_oled_bg_card) isMiami(context) -> ContextCompat.getColor(context, R.color.nova_miami_bg_card) + isPortableChrome(context) -> ContextCompat.getColor(context, R.color.nova_portable_bg_card) isHighContrast(context) -> ContextCompat.getColor(context, R.color.nova_hc_bg_card) isMaterialYou(context) && isMaterialYouAvailable() -> resolveThemeColor(context, com.google.android.material.R.attr.colorSurface, ContextCompat.getColor(context, R.color.nova_bg_card)) @@ -172,6 +180,7 @@ object NovaThemeManager { return when { isOled(context) -> ContextCompat.getColor(context, R.color.nova_oled_dialog_bg) isMiami(context) -> ContextCompat.getColor(context, R.color.nova_miami_dialog_bg) + isPortableChrome(context) -> ContextCompat.getColor(context, R.color.nova_portable_dialog_bg) isHighContrast(context) -> ContextCompat.getColor(context, R.color.nova_hc_dialog_bg) isMaterialYou(context) && isMaterialYouAvailable() -> resolveThemeColor(context, com.google.android.material.R.attr.colorSurface, ContextCompat.getColor(context, R.color.nova_dialog_bg)) @@ -196,6 +205,7 @@ object NovaThemeManager { return when { isOled(context) -> ContextCompat.getColor(context, R.color.nova_oled_accent) isMiami(context) -> ContextCompat.getColor(context, R.color.nova_miami_accent) + isPortableChrome(context) -> ContextCompat.getColor(context, R.color.nova_portable_accent) isHighContrast(context) -> ContextCompat.getColor(context, R.color.nova_hc_accent) else -> ContextCompat.getColor(context, R.color.nova_accent) } @@ -206,6 +216,7 @@ object NovaThemeManager { return when { isOled(context) -> ContextCompat.getColor(context, R.color.nova_oled_accent_surface) isMiami(context) -> ContextCompat.getColor(context, R.color.nova_miami_accent_surface) + isPortableChrome(context) -> ContextCompat.getColor(context, R.color.nova_portable_accent_surface) isHighContrast(context) -> ContextCompat.getColor(context, R.color.nova_hc_accent_surface) else -> ContextCompat.getColor(context, R.color.nova_accent_surface) } @@ -216,6 +227,7 @@ object NovaThemeManager { return when { isOled(context) -> ContextCompat.getColor(context, R.color.nova_oled_divider) isMiami(context) -> ContextCompat.getColor(context, R.color.nova_miami_divider) + isPortableChrome(context) -> ContextCompat.getColor(context, R.color.nova_portable_divider) isHighContrast(context) -> ContextCompat.getColor(context, R.color.nova_hc_divider) isMaterialYou(context) && isMaterialYouAvailable() -> resolveThemeColor(context, com.google.android.material.R.attr.colorOutline, ContextCompat.getColor(context, R.color.nova_divider)) @@ -228,6 +240,7 @@ object NovaThemeManager { return when { isOled(context) -> ContextCompat.getColor(context, R.color.nova_oled_text_primary) isMiami(context) -> ContextCompat.getColor(context, R.color.nova_miami_text_primary) + isPortableChrome(context) -> ContextCompat.getColor(context, R.color.nova_portable_text_primary) isHighContrast(context) -> ContextCompat.getColor(context, R.color.nova_hc_text_primary) isMaterialYou(context) && isMaterialYouAvailable() -> resolveThemeColor(context, android.R.attr.textColorPrimary, ContextCompat.getColor(context, R.color.nova_text_primary)) @@ -240,6 +253,7 @@ object NovaThemeManager { return when { isOled(context) -> ContextCompat.getColor(context, R.color.nova_oled_text_secondary) isMiami(context) -> ContextCompat.getColor(context, R.color.nova_miami_text_secondary) + isPortableChrome(context) -> ContextCompat.getColor(context, R.color.nova_portable_text_secondary) isHighContrast(context) -> ContextCompat.getColor(context, R.color.nova_hc_text_secondary) isMaterialYou(context) && isMaterialYouAvailable() -> resolveThemeColor(context, android.R.attr.textColorSecondary, ContextCompat.getColor(context, R.color.nova_text_secondary)) @@ -252,6 +266,7 @@ object NovaThemeManager { return when { isOled(context) -> ContextCompat.getColor(context, R.color.nova_oled_text_muted) isMiami(context) -> ContextCompat.getColor(context, R.color.nova_miami_text_muted) + isPortableChrome(context) -> ContextCompat.getColor(context, R.color.nova_portable_text_muted) isHighContrast(context) -> ContextCompat.getColor(context, R.color.nova_hc_text_muted) isMaterialYou(context) && isMaterialYouAvailable() -> resolveThemeColor(context, android.R.attr.textColorSecondary, ContextCompat.getColor(context, R.color.nova_text_muted)) diff --git a/app/src/main/java/com/papi/nova/ui/compose/NovaComposeTheme.kt b/app/src/main/java/com/papi/nova/ui/compose/NovaComposeTheme.kt index f653677d..4df508fb 100644 --- a/app/src/main/java/com/papi/nova/ui/compose/NovaComposeTheme.kt +++ b/app/src/main/java/com/papi/nova/ui/compose/NovaComposeTheme.kt @@ -77,12 +77,14 @@ val LocalNovaLibrarySurfaces = staticCompositionLocalOf { fun NovaComposeColors.librarySurfaces(theme: String): NovaLibrarySurfaces { val isOled = theme == NovaThemeManager.THEME_OLED val isMiami = theme == NovaThemeManager.THEME_MIAMI + val isPortableChrome = theme == NovaThemeManager.THEME_PORTABLE_CHROME val isHighContrast = theme == NovaThemeManager.THEME_HIGH_CONTRAST val isMaterialYou = theme == NovaThemeManager.THEME_MATERIAL_YOU return NovaLibrarySurfaces( backgroundScrim = when { isOled -> Color.Transparent isMiami -> window.copy(alpha = 0.60f) + isPortableChrome -> Color.Black.copy(alpha = 0.20f) isHighContrast -> Color.Black.copy(alpha = 0.72f) isMaterialYou -> window.copy(alpha = 0.28f) else -> window.copy(alpha = 0.56f) @@ -90,6 +92,7 @@ fun NovaComposeColors.librarySurfaces(theme: String): NovaLibrarySurfaces { panel = when { isOled -> dialog.copy(alpha = 0.88f) isMiami -> dialog.copy(alpha = 0.82f) + isPortableChrome -> dialog.copy(alpha = 0.90f) isHighContrast -> dialog.copy(alpha = 0.96f) isMaterialYou -> card.copy(alpha = 0.76f) else -> dialog.copy(alpha = 0.64f) @@ -97,6 +100,7 @@ fun NovaComposeColors.librarySurfaces(theme: String): NovaLibrarySurfaces { panelBorder = when { isOled -> divider.copy(alpha = 0.78f) isMiami -> accent.copy(alpha = 0.18f) + isPortableChrome -> divider.copy(alpha = 0.62f) isHighContrast -> divider.copy(alpha = 0.92f) isMaterialYou -> divider.copy(alpha = 0.46f) else -> divider.copy(alpha = 0.44f) @@ -104,6 +108,7 @@ fun NovaComposeColors.librarySurfaces(theme: String): NovaLibrarySurfaces { tile = when { isOled -> card.copy(alpha = 0.90f) isMiami -> card.copy(alpha = 0.82f) + isPortableChrome -> card.copy(alpha = 0.88f) isHighContrast -> card.copy(alpha = 0.98f) isMaterialYou -> card.copy(alpha = 0.78f) else -> card.copy(alpha = 0.74f) @@ -111,12 +116,14 @@ fun NovaComposeColors.librarySurfaces(theme: String): NovaLibrarySurfaces { tileBorder = when { isOled -> divider.copy(alpha = 0.78f) isMiami -> divider.copy(alpha = 0.58f) + isPortableChrome -> divider.copy(alpha = 0.62f) isHighContrast -> divider.copy(alpha = 0.90f) else -> divider.copy(alpha = 0.50f) }, control = when { isOled -> card.copy(alpha = 0.78f) isMiami -> card.copy(alpha = 0.76f) + isPortableChrome -> card.copy(alpha = 0.84f) isHighContrast -> card.copy(alpha = 1f) isMaterialYou -> card.copy(alpha = 0.70f) else -> card.copy(alpha = 0.72f) @@ -124,6 +131,7 @@ fun NovaComposeColors.librarySurfaces(theme: String): NovaLibrarySurfaces { selectedControl = accent.copy(alpha = when { isHighContrast -> 0.34f isMiami -> 0.22f + isPortableChrome -> 0.20f isOled -> 0.22f else -> 0.18f }), @@ -131,12 +139,14 @@ fun NovaComposeColors.librarySurfaces(theme: String): NovaLibrarySurfaces { focusHalo = accent.copy(alpha = when { isHighContrast -> 0.36f isMiami -> 0.28f + isPortableChrome -> 0.20f isOled -> 0.24f else -> 0.18f }), mediaPlaceholder = when { isOled -> Color(0xFF08080C) isMiami -> Color(0xFF2C1734) + isPortableChrome -> Color(0xFFB8C1CC) isHighContrast -> Color(0xFF111827) isMaterialYou -> card.copy(alpha = 1f) else -> divider.copy(alpha = 1f) @@ -152,12 +162,14 @@ fun NovaComposeColors.librarySurfaces(theme: String): NovaLibrarySurfaces { focusedArtworkAlpha = when { isOled -> 0.10f isMiami -> 0.26f + isPortableChrome -> 0.16f isMaterialYou -> 0.18f else -> 0.24f }, focusedArtworkScrim = Color.Black.copy(alpha = when { isOled -> 0.82f isMiami -> 0.76f + isPortableChrome -> 0.64f else -> 0.72f }), particlesEnabled = !isOled, @@ -165,6 +177,7 @@ fun NovaComposeColors.librarySurfaces(theme: String): NovaLibrarySurfaces { isOled -> 0f isHighContrast -> 0.28f isMiami -> 0.68f + isPortableChrome -> 0.32f isMaterialYou -> 0.42f else -> 1f } @@ -190,7 +203,11 @@ fun NovaComposeTheme(content: @Composable () -> Unit) { onAccent = Color( ContextCompat.getColor( context, - if (theme == NovaThemeManager.THEME_MIAMI) R.color.nova_miami_void else R.color.nova_ice + when (theme) { + NovaThemeManager.THEME_MIAMI -> R.color.nova_miami_void + NovaThemeManager.THEME_PORTABLE_CHROME -> R.color.nova_portable_on_accent + else -> R.color.nova_ice + } ) ) ) diff --git a/app/src/main/res/values-v31/styles.xml b/app/src/main/res/values-v31/styles.xml index 1dc362db..e8374403 100644 --- a/app/src/main/res/values-v31/styles.xml +++ b/app/src/main/res/values-v31/styles.xml @@ -27,4 +27,10 @@ @color/nova_miami_bg_window @color/nova_miami_bg_window + diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index e30cab3a..fefbe78e 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -4,6 +4,7 @@ Polaris Aurora Console OLED Miami Nebula + Portable Chrome High Contrast Material You @@ -11,6 +12,7 @@ polaris oled miami + portable_chrome high_contrast material_you diff --git a/app/src/main/res/values/colors_nova.xml b/app/src/main/res/values/colors_nova.xml index 1903b218..9040e8e5 100644 --- a/app/src/main/res/values/colors_nova.xml +++ b/app/src/main/res/values/colors_nova.xml @@ -55,6 +55,32 @@ #73FF5CAB #22FF5CAB + + #FF98A6B5 + #FFB8C1CC + #FFA7B2BE + #FF7F8C9A + #FFD6DDE5 + #FFEAF0F5 + #FFB8C1CC + #FFB8C1CC + #E6D6DDE5 + #FFE0E6EC + #FFC8D1DC + #FFD6DDE5 + #FFC7D1DD + #FF7F8C9A + #FF25313D + #FF4F5D6B + #FF667584 + #FF557395 + #22557395 + #33557395 + #22557395 + #FF315B45 + #FF315B45 + #FF557395 + #FFFFFFFF #FF05070c #F00f172a diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 79f39878..06faa290 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -50,6 +50,7 @@ Polaris Aurora Console OLED Miami Nebula + Portable Chrome High Contrast Material You Open Nova Library diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 84a206e9..0873fd63 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -55,6 +55,20 @@ @color/nova_miami_text_primary + + +