Skip to content

Commit a7fe7a2

Browse files
authored
Add Dracula app theme (#38)
* Add Dracula app theme Add Dracula as theme option 23 with the official palette (purple primary, pink accent). The theme paints a full dark canvas (#282a36 background, #343746 surface, #f8f8f2 foreground) via values-night overrides scoped to the Dracula style only; other themes keep the Material DayNight defaults. Selecting Dracula forces night mode on so its dark canvas applies, and the user's prior night-mode setting is remembered and restored when switching to another theme. A manual night-mode change clears the pending restore. * Set itemShapeFillColor for Dracula nav drawer On-device logcat showed a recoverable ResourcesCompat warning: the NavigationView's @color/navigation_item ColorStateList self-references ?itemShapeFillColor, which the base theme never sets (only the Black theme does). Set it on the Dracula day and night styles so the selected nav-drawer item gets a proper Dracula-purple fill and the warning no longer fires. Pre-existing on all non-Black themes; fixed here for the new Dracula theme. Verified on Android: warning count 0, no crash. * Drop redundant nightTheme writes in Dracula toggle nightTheme.value (ListPreference.setValue -> persistString) already writes the same configurationStore key that DataStore.nightTheme (stringToInt -> putString) uses, and refreshes the picker UI. Remove the duplicate DataStore.nightTheme assignments in both the force-on and restore branches. Verified on-device: appTheme persists as 23 (Dracula), nightTheme persists as a string in the same format; no crash, no resource warnings. Addresses Greptile P2. * Resolve nav-drawer itemShapeFillColor warning for all themes @color/navigation_item self-references ?itemShapeFillColor, which the base Theme.SagerNet never set, so the ColorStateList failed to resolve (a recoverable ResourcesCompat warning) on every theme except Black. On-device theme-switching confirmed it firing on Brown/Grey/Red/VerdantMint. Set itemShapeFillColor=?colorPrimary on the base Theme.SagerNet, its Dialog variant, and the v26 base, fixing it repo-wide. Drop the now-redundant per-theme overrides added to Dracula (day + night); Black keeps its explicit override. Verified on device (API 33): Dracula, Grey, and Black all start with 0 warnings and no crash.
1 parent 251aa83 commit a7fe7a2

9 files changed

Lines changed: 130 additions & 2 deletions

File tree

app/src/main/java/io/nekohasekai/sagernet/Constants.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ object Key {
1414
const val APP_EXPERT = "isExpert"
1515
const val APP_THEME = "appTheme"
1616
const val NIGHT_THEME = "nightTheme"
17+
// Remembers the user's night-mode setting before the dark-only Dracula theme
18+
// forced it on, so it can be restored when switching to another theme.
19+
const val NIGHT_THEME_BEFORE_DRACULA = "nightThemeBeforeDracula"
1720
const val APP_LANGUAGE = "appLanguage"
1821
const val SERVICE_MODE = "serviceMode"
1922
const val MODE_VPN = "vpn"

app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ object DataStore : OnPreferenceDataStoreChangeListener {
103103
var isExpert by configurationStore.boolean(Key.APP_EXPERT)
104104
var appTheme by configurationStore.int(Key.APP_THEME)
105105
var nightTheme by configurationStore.stringToInt(Key.NIGHT_THEME)
106+
// -1 = not set (no Dracula override active). Otherwise holds the night-mode
107+
// value to restore when leaving the dark-only Dracula theme.
108+
var nightThemeBeforeDracula by configurationStore.int(Key.NIGHT_THEME_BEFORE_DRACULA) { -1 }
106109
var appLanguage by configurationStore.string(Key.APP_LANGUAGE) { "" }
107110
var serviceMode by configurationStore.string(Key.SERVICE_MODE) { Key.MODE_VPN }
108111

app/src/main/java/io/nekohasekai/sagernet/ui/SettingsPreferenceFragment.kt

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,37 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() {
6565
addPreferencesFromResource(R.xml.global_preferences)
6666

6767
val appTheme = findPreference<ColorPickerPreference>(Key.APP_THEME)!!
68+
val nightTheme = findPreference<SimpleMenuPreference>(Key.NIGHT_THEME)!!
6869
appTheme.setOnPreferenceChangeListener { _, newTheme ->
6970
if (DataStore.serviceState.started) {
7071
SagerNet.reloadService()
7172
}
72-
val theme = Theme.getTheme(newTheme as Int)
73+
val themeId = newTheme as Int
74+
val previousTheme = DataStore.appTheme // still the old value at this point
75+
// Dracula is a dark-only theme: force night mode on so its #282a36
76+
// canvas (values-night) takes effect instead of a washed-out light surface.
77+
// Remember the prior night-mode setting so it can be restored on exit.
78+
if (themeId == Theme.DRACULA) {
79+
if (previousTheme != Theme.DRACULA && DataStore.nightTheme != 1) {
80+
DataStore.nightThemeBeforeDracula = DataStore.nightTheme
81+
Theme.currentNightMode = 1
82+
// nightTheme.value persists to configurationStore (same key as
83+
// DataStore.nightTheme) and refreshes the picker, so no separate
84+
// DataStore.nightTheme write is needed.
85+
nightTheme.value = "1"
86+
Theme.applyNightTheme()
87+
}
88+
} else if (previousTheme == Theme.DRACULA) {
89+
// Leaving Dracula: restore the night-mode setting we forced on.
90+
val restore = DataStore.nightThemeBeforeDracula
91+
if (restore != -1) {
92+
DataStore.nightThemeBeforeDracula = -1
93+
Theme.currentNightMode = restore
94+
nightTheme.value = restore.toString()
95+
Theme.applyNightTheme()
96+
}
97+
}
98+
val theme = Theme.getTheme(themeId)
7399
app.setTheme(theme)
74100
requireActivity().apply {
75101
setTheme(theme)
@@ -78,9 +104,11 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() {
78104
true
79105
}
80106

81-
val nightTheme = findPreference<SimpleMenuPreference>(Key.NIGHT_THEME)!!
82107
nightTheme.setOnPreferenceChangeListener { _, newTheme ->
83108
Theme.currentNightMode = (newTheme as String).toInt()
109+
// A manual night-mode change takes precedence: drop any pending
110+
// Dracula restore so we don't override the user's choice later.
111+
DataStore.nightThemeBeforeDracula = -1
84112
Theme.applyNightTheme()
85113
true
86114
}

app/src/main/java/io/nekohasekai/sagernet/utils/Theme.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ object Theme {
3131
const val BLUE_GREY = 20
3232
const val BLACK = 21
3333
const val VERDANT_MINT = 22
34+
const val DRACULA = 23
3435

3536
private fun defaultTheme() = PINK_SSR
3637

@@ -74,6 +75,7 @@ object Theme {
7475
BLUE_GREY -> R.style.Theme_SagerNet_BlueGrey
7576
BLACK -> R.style.Theme_SagerNet_Black
7677
VERDANT_MINT -> R.style.Theme_SagerNet_VerdantMint
78+
DRACULA -> R.style.Theme_SagerNet_Dracula
7779
else -> getTheme(defaultTheme())
7880
}
7981
}
@@ -102,6 +104,7 @@ object Theme {
102104
BLUE_GREY -> R.style.Theme_SagerNet_Dialog_BlueGrey
103105
BLACK -> R.style.Theme_SagerNet_Dialog_Black
104106
VERDANT_MINT -> R.style.Theme_SagerNet_Dialog_VerdantMint
107+
DRACULA -> R.style.Theme_SagerNet_Dialog_Dracula
105108
else -> getDialogTheme(defaultTheme())
106109
}
107110
}

app/src/main/res/values-night/colors.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,9 @@
3434
<color name="card_elevated_yellow">#2C2A17</color>
3535
<color name="card_elevated_black">#151515</color>
3636
<color name="card_elevated_verdant_mint">#10241C</color>
37+
<!-- Dracula dark surfaces (official palette). -->
38+
<color name="card_elevated_dracula">#343746</color>
39+
<color name="dracula_background">#282A36</color>
40+
<color name="dracula_surface">#343746</color>
41+
<color name="dracula_on_surface">#F8F8F2</color>
3742
</resources>
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<resources>
3+
<!--
4+
Night-only surface overrides.
5+
6+
Other themes intentionally inherit the Material DayNight defaults, so we do
7+
NOT redeclare them here. Only the Dracula theme paints a full custom canvas:
8+
the official #282a36 background, #343746 surface, and #f8f8f2 foreground.
9+
10+
In day mode the Dracula style (values/themes.xml) keeps the standard light
11+
surfaces, so accents stay readable even if the user is not in dark mode.
12+
Selecting Dracula also forces night mode on (SettingsPreferenceFragment),
13+
which is what brings these overrides into effect.
14+
-->
15+
<style name="Theme.SagerNet.Dracula" parent="Theme.SagerNet">
16+
<item name="colorPrimary">@color/color_dracula</item>
17+
<item name="colorPrimaryDark">@color/color_dracula_dark</item>
18+
<item name="colorAccent">@color/color_dracula_accent</item>
19+
<item name="colorMaterial100">@color/color_dracula_100</item>
20+
<item name="colorMaterial300">@color/color_dracula_300</item>
21+
<item name="cardElevatedSurfaceColor">@color/card_elevated_dracula</item>
22+
23+
<!-- True Dracula dark canvas. -->
24+
<item name="colorSurface">@color/dracula_surface</item>
25+
<item name="colorOnSurface">@color/dracula_on_surface</item>
26+
<item name="android:colorBackground">@color/dracula_background</item>
27+
<item name="colorOnBackground">@color/dracula_on_surface</item>
28+
<item name="android:windowBackground">@color/dracula_background</item>
29+
</style>
30+
31+
<style name="Theme.SagerNet.Dialog.Dracula" parent="Theme.SagerNet.Dialog">
32+
<item name="colorPrimary">@color/color_dracula</item>
33+
<item name="colorPrimaryDark">@color/color_dracula_dark</item>
34+
<item name="colorAccent">@color/color_dracula_accent</item>
35+
<item name="colorMaterial100">@color/color_dracula_100</item>
36+
<item name="colorMaterial300">@color/color_dracula_300</item>
37+
<item name="cardElevatedSurfaceColor">@color/card_elevated_dracula</item>
38+
39+
<item name="colorSurface">@color/dracula_surface</item>
40+
<item name="colorOnSurface">@color/dracula_on_surface</item>
41+
<item name="android:colorBackground">@color/dracula_background</item>
42+
<item name="colorOnBackground">@color/dracula_on_surface</item>
43+
</style>
44+
</resources>

app/src/main/res/values-v26/themes.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
<!-- Base application theme for API 26+ -->
33
<style name="Theme.SagerNet" parent="Theme.MaterialComponents.DayNight.NoActionBar">
44
<item name="actionBarStyle">@style/Widget.MaterialComponents.ActionBar.Solid</item>
5+
<!-- Default nav-drawer selected-item fill; satisfies the
6+
?itemShapeFillColor self-reference in @color/navigation_item. -->
7+
<item name="itemShapeFillColor">?colorPrimary</item>
58
<item name="actionModeCloseDrawable">@drawable/ic_navigation_close</item>
69
<item name="colorButtonNormal">?colorAccent</item>
710
<item name="android:navigationBarColor">@android:color/transparent</item>

app/src/main/res/values/colors.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
<item>@color/material_blue_grey_500</item>
2727
<item>@color/material_light_black</item>
2828
<item>@color/color_verdant_mint</item>
29+
<item>@color/color_dracula</item>
2930
</integer-array>
3031

3132
<color name="material_amber_100">#FFECB3</color>
@@ -291,6 +292,15 @@
291292
<color name="color_verdant_mint_100">#B4D7C6</color>
292293
<color name="color_verdant_mint_300">#509177</color>
293294

295+
<!-- Dracula theme accents (https://draculatheme.com). Purple primary,
296+
pink accent. Surfaces are overridden in values-night for the true
297+
#282a36 canvas; see Theme.SagerNet.Dracula. -->
298+
<color name="color_dracula">#BD93F9</color>
299+
<color name="color_dracula_dark">#6272A4</color>
300+
<color name="color_dracula_accent">#FF79C6</color>
301+
<color name="color_dracula_100">#E9DDFB</color>
302+
<color name="color_dracula_300">#9A7BD0</color>
303+
294304
<color name="color_ng_black_accent">#9E9E9E</color>
295305
<color name="color_ng_black_primary">#2B2B2B</color>
296306

@@ -329,5 +339,6 @@
329339
<color name="card_elevated_yellow">#FFFDEF</color>
330340
<color name="card_elevated_black">#EEEEEE</color>
331341
<color name="card_elevated_verdant_mint">#EBF9F3</color>
342+
<color name="card_elevated_dracula">#F1EAFB</color>
332343

333344
</resources>

app/src/main/res/values/themes.xml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
<!-- Base application theme. -->
33
<style name="Theme.SagerNet" parent="Theme.MaterialComponents.DayNight.NoActionBar">
44
<item name="actionBarStyle">@style/Widget.MaterialComponents.ActionBar.Solid</item>
5+
<!-- Default nav-drawer selected-item fill; also satisfies the
6+
?itemShapeFillColor self-reference in @color/navigation_item so the
7+
ColorStateList resolves for every theme. Only Black overrides it;
8+
other themes (incl. Dracula) inherit this ?colorPrimary default. -->
9+
<item name="itemShapeFillColor">?colorPrimary</item>
510
<item name="actionModeCloseDrawable">@drawable/ic_navigation_close</item>
611
<item name="colorButtonNormal">?colorAccent</item>
712
<item name="android:navigationBarColor">?colorPrimaryDark</item>
@@ -58,6 +63,7 @@
5863

5964
<style name="Theme.SagerNet.Dialog" parent="Theme.MaterialComponents.DayNight.Dialog.Alert">
6065
<item name="actionBarStyle">@style/Widget.MaterialComponents.ActionBar.Solid</item>
66+
<item name="itemShapeFillColor">?colorPrimary</item>
6167
<item name="actionModeCloseDrawable">@drawable/ic_navigation_close</item>
6268
<item name="colorButtonNormal">?colorAccent</item>
6369
<item name="android:navigationBarColor">?colorPrimaryDark</item>
@@ -595,6 +601,19 @@
595601
<item name="cardElevatedSurfaceColor">@color/card_elevated_verdant_mint</item>
596602
</style>
597603

604+
<!-- Dracula: purple primary, pink accent. Accent colors apply in any mode;
605+
the true #282a36 dark canvas (colorSurface/colorBackground/colorOnSurface)
606+
is layered on in values-night/themes.xml. Selecting this theme forces
607+
night mode on (see SettingsPreferenceFragment). -->
608+
<style name="Theme.SagerNet.Dracula" parent="Theme.SagerNet">
609+
<item name="colorPrimary">@color/color_dracula</item>
610+
<item name="colorPrimaryDark">@color/color_dracula_dark</item>
611+
<item name="colorAccent">@color/color_dracula_accent</item>
612+
<item name="colorMaterial100">@color/color_dracula_100</item>
613+
<item name="colorMaterial300">@color/color_dracula_300</item>
614+
<item name="cardElevatedSurfaceColor">@color/card_elevated_dracula</item>
615+
</style>
616+
598617
<style name="Theme.SagerNet.Dialog.Black" parent="Theme.SagerNet.Dialog">
599618
<item name="colorPrimary">@color/color_ng_black_primary</item>
600619
<item name="colorPrimaryDark">@color/color_ng_black_primary</item>
@@ -622,6 +641,15 @@
622641
<item name="cardElevatedSurfaceColor">@color/card_elevated_verdant_mint</item>
623642
</style>
624643

644+
<style name="Theme.SagerNet.Dialog.Dracula" parent="Theme.SagerNet.Dialog">
645+
<item name="colorPrimary">@color/color_dracula</item>
646+
<item name="colorPrimaryDark">@color/color_dracula_dark</item>
647+
<item name="colorAccent">@color/color_dracula_accent</item>
648+
<item name="colorMaterial100">@color/color_dracula_100</item>
649+
<item name="colorMaterial300">@color/color_dracula_300</item>
650+
<item name="cardElevatedSurfaceColor">@color/card_elevated_dracula</item>
651+
</style>
652+
625653
<style name="Theme.Start" parent="Theme.MaterialComponents.DayNight">
626654
<item name="android:navigationBarColor">@android:color/transparent</item>
627655
<item name="colorPrimary">@android:color/transparent</item>

0 commit comments

Comments
 (0)