From c05425b92266fe63666bf3ab416c7e3dca3ab96d Mon Sep 17 00:00:00 2001
From: hawkff <109485367+hawkff@users.noreply.github.com>
Date: Sat, 20 Jun 2026 21:25:55 -0400
Subject: [PATCH 1/2] fix(theme): color the in-progress status text (Dracula
yellow) and harden getColorAttr
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The 'Connecting…' / 'Testing…' status text fell into the colorOnPrimary branch, which
renders dark on Dracula's light-purple primary instead of yellow. Add a
statusConnectingColor attr (default colorOnPrimary; Dracula = yellow) and use it for the
Connecting state.
While wiring it up, surfaced and fixed a latent crash in getColorAttr: when a theme attr
resolves to a literal color value (resourceId == 0) rather than a @color resource ref,
ContextCompat.getColor(0) threw Resources$NotFoundException (Resource ID #0x0). Now use
the literal data when it is a color type, falling back to TRANSPARENT otherwise. This
hardens every getColorAttr caller, not just the new attr.
Verified on-device (Dracula): connect no longer crashes; in-progress status renders yellow.
---
.../java/io/nekohasekai/sagernet/ktx/Utils.kt | 20 ++++++++++++++++---
.../nekohasekai/sagernet/widget/StatsBar.kt | 4 +++-
app/src/main/res/values/attrs.xml | 3 +++
app/src/main/res/values/themes.xml | 4 ++++
4 files changed, 27 insertions(+), 4 deletions(-)
diff --git a/app/src/main/java/io/nekohasekai/sagernet/ktx/Utils.kt b/app/src/main/java/io/nekohasekai/sagernet/ktx/Utils.kt
index 3e176c8ed..8c43f98b8 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/ktx/Utils.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/ktx/Utils.kt
@@ -11,6 +11,7 @@ import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.res.Resources
+import android.graphics.Color
import android.os.Build
import android.system.Os
import android.system.OsConstants
@@ -284,9 +285,22 @@ fun Context.getColour(@ColorRes colorRes: Int): Int {
}
fun Context.getColorAttr(@AttrRes resId: Int): Int {
- return ContextCompat.getColor(this, TypedValue().also {
- theme.resolveAttribute(resId, it, true)
- }.resourceId)
+ val tv = TypedValue()
+ // resolveRefs = true follows ?attr chains (e.g. statusConnectingColor -> colorOnPrimary).
+ // The result is either a color-resource reference (resourceId != 0) or a literal color
+ // value (resourceId == 0); ContextCompat.getColor(0) would throw on the latter.
+ theme.resolveAttribute(resId, tv, true)
+ if (tv.resourceId != 0) {
+ return ContextCompat.getColor(this, tv.resourceId)
+ }
+ // No backing resource: the attr resolved directly to a literal value. Use it only
+ // when it is actually a color (e.g. ?attr/colorOnPrimary defined as a literal);
+ // otherwise fall back to a safe default rather than returning a garbage int.
+ return if (tv.type in TypedValue.TYPE_FIRST_COLOR_INT..TypedValue.TYPE_LAST_COLOR_INT) {
+ tv.data
+ } else {
+ Color.TRANSPARENT
+ }
}
val isExpert: Boolean by lazy { BuildConfig.DEBUG || DataStore.isExpert }
diff --git a/app/src/main/java/io/nekohasekai/sagernet/widget/StatsBar.kt b/app/src/main/java/io/nekohasekai/sagernet/widget/StatsBar.kt
index 821670db2..7cddcfc46 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/widget/StatsBar.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/widget/StatsBar.kt
@@ -109,11 +109,13 @@ class StatsBar @JvmOverloads constructor(
private fun setStatusColorByState(state: BaseService.State) {
// Connected = statusConnectedColor (green), Stopped/Stopping = statusStoppedColor
- // (red — "Shutting down…" reads as red), Connecting/other = colorOnPrimary.
+ // (red — "Shutting down…" reads as red), Connecting = statusConnectingColor (Dracula
+ // yellow; colorOnPrimary elsewhere), other = colorOnPrimary.
// Non-Dracula themes default these attrs to colorOnPrimary, so no change.
val attr = when (state) {
BaseService.State.Connected -> R.attr.statusConnectedColor
BaseService.State.Stopped, BaseService.State.Stopping -> R.attr.statusStoppedColor
+ BaseService.State.Connecting -> R.attr.statusConnectingColor
else -> com.google.android.material.R.attr.colorOnPrimary
}
statusText.setTextColor(context.getColorAttr(attr))
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index bda8ff5b5..f01246e2b 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -19,6 +19,9 @@
Dracula themes are unchanged; Dracula tints connected=green, stopped=red. -->
+
+
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index 021a3934a..f0e4e8c83 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -9,6 +9,7 @@
- ?android:attr/textColorPrimary
- ?attr/colorOnPrimary
- ?attr/colorOnPrimary
+ - ?attr/colorOnPrimary
- @color/material_red_500
- ?attr/colorOnPrimary
- ?attr/colorOnPrimary
@@ -81,6 +82,7 @@
- ?android:attr/textColorPrimary
- ?attr/colorOnPrimary
- ?attr/colorOnPrimary
+ - ?attr/colorOnPrimary
- @color/material_red_500
- ?attr/colorOnPrimary
- ?attr/colorOnPrimary
@@ -642,6 +644,7 @@
- @color/color_dracula_yellow
- @color/color_dracula_green
- @color/color_dracula_red
+ - @color/color_dracula_yellow
- @color/color_dracula_red
- @color/color_dracula_cyan
- @color/color_dracula_cyan
@@ -689,6 +692,7 @@
- @color/color_dracula_yellow
- @color/color_dracula_green
- @color/color_dracula_red
+ - @color/color_dracula_yellow
- @color/color_dracula_red
- @color/color_dracula_cyan
- @color/color_dracula_cyan
From cf4ec9ab7681245b87f9127c11aaa36dfbee6446 Mon Sep 17 00:00:00 2001
From: hawkff <109485367+hawkff@users.noreply.github.com>
Date: Sat, 20 Jun 2026 21:31:42 -0400
Subject: [PATCH 2/2] fix(theme): add statusConnectingColor to night-Dracula
and v26 base themes
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Per Greptile: Dracula forces night mode, so the active overrides live in
values-night/themes.xml — which had statusConnected/Stopped but not the new
statusConnectingColor, so connecting still inherited the dark base value in practice.
Add statusConnectingColor=yellow to both night Dracula blocks, and to the API26+ base
Theme.SagerNet in values-v26 (which redefines the status attrs) so the attr always
resolves. Day-mode values were added in the previous commit.
---
app/src/main/res/values-night/themes.xml | 2 ++
app/src/main/res/values-v26/themes.xml | 1 +
2 files changed, 3 insertions(+)
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
index ff5dc33b4..e146ce488 100644
--- a/app/src/main/res/values-night/themes.xml
+++ b/app/src/main/res/values-night/themes.xml
@@ -32,6 +32,7 @@
- @color/color_dracula_yellow
- @color/color_dracula_green
- @color/color_dracula_red
+ - @color/color_dracula_yellow
- @color/color_dracula_red
- @color/color_dracula_cyan
- @color/color_dracula_cyan
@@ -57,6 +58,7 @@
- @color/color_dracula_yellow
- @color/color_dracula_green
- @color/color_dracula_red
+ - @color/color_dracula_yellow
- @color/color_dracula_red
- @color/color_dracula_cyan
- @color/color_dracula_cyan
diff --git a/app/src/main/res/values-v26/themes.xml b/app/src/main/res/values-v26/themes.xml
index 6b1efb0c9..58a704e87 100644
--- a/app/src/main/res/values-v26/themes.xml
+++ b/app/src/main/res/values-v26/themes.xml
@@ -10,6 +10,7 @@
- ?android:attr/textColorPrimary
- ?attr/colorOnPrimary
- ?attr/colorOnPrimary
+ - ?attr/colorOnPrimary
- @color/material_red_500
- ?attr/colorOnPrimary
- ?attr/colorOnPrimary