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