From 8adad9c708ceace0f82b6a158f5b4b1ccb2e23c2 Mon Sep 17 00:00:00 2001 From: Luis Guzman Date: Thu, 18 Jun 2026 21:19:23 +0000 Subject: [PATCH 1/7] feat(controller): semantic colour tokens + migrate Dashboard (theming T0) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Start the light-theme overhaul as a refactor-by-feature slice. The app already had a DayNight setup, but most screens hardcoded dark-tuned hex, so light mode breaks wherever that happens. Foundation (additive — nothing renamed or removed yet): - Add a semantic colour-token palette with full light/dark parity in values/colors.xml + values-night/colors.xml: surfaces (surface_background/ card/section), text (primary/secondary/disabled/on_accent), accent/accent_muted, status (success/warning/danger/info), divider_line, and data-viz (chart_*). QR colours (qr_foreground/qr_background) are intentionally fixed — no -night override — since a QR must stay black/white to scan. - Document the palette and the rule (no hardcoded colours; every token defined in both values and values-night) in CLAUDE.md (new "Theming" section). Pilot — Dashboard (the reference migration to copy): - fragment_dashboard.xml: replace the 7 hardcoded hex (#888888 -> @color/text_secondary; offline badge #555555 -> @color/surface_section). - DashboardFragment.java: replace the Color.parseColor gauge/battery colours with ContextCompat.getColor(R.color.status_warning/danger/success/info). Legacy dash_*/literal tokens are kept for now and retired as other screens migrate. Verify in light AND dark on device. --- CLAUDE.md | 28 +++++++++++++++ .../iiab/controller/DashboardFragment.java | 8 ++--- .../main/res/layout/fragment_dashboard.xml | 14 ++++---- .../app/src/main/res/values-night/colors.xml | 23 ++++++++++++ controller/app/src/main/res/values/colors.xml | 35 +++++++++++++++++++ 5 files changed, 97 insertions(+), 11 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index af7de61..5081c3a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -169,6 +169,34 @@ live-size data behind the reference slice. --- +## Theming (colors) — mandatory for all UI + +The app supports light and dark via `DayNight` (toggle in `MainActivity`), but most +screens historically hardcoded dark-tuned hex, so the light theme is broken wherever +that happens. We are migrating to one semantic colour-token system, screen by screen +(refactor-by-feature). + +Rules for any new or migrated UI: + +1. **No hardcoded colours.** No `#RRGGBB` in layouts and no `Color.parseColor("#…")` + / `Color.WHITE` etc. in code. Reference a semantic token instead: + `@color/` in XML, `ContextCompat.getColor(ctx, R.color.)` in code. +2. **Every token has a light value in `values/colors.xml` and a dark twin in + `values-night/colors.xml`.** If you add a token, add both. +3. **Use the semantic families**: surfaces (`surface_background`, `surface_card`, + `surface_section`), text (`text_primary`, `text_secondary`, `text_disabled`, + `text_on_accent`), `accent`/`accent_muted`, status + (`status_success/warning/danger/info`), `divider_line`, and data-viz + (`chart_storage/ram/swap/os/maps/wiki/track`). Pick by role, not by look. +4. **Legitimately fixed colours stay fixed:** a QR must be black/white to scan, so + `qr_foreground`/`qr_background` are mode-independent (no `-night` override). +5. **Verify both modes** on a device before merge. + +Legacy tokens (`dash_*`, `white`, `black`, `section_*`, `btn_*`) are retired as screens +migrate. Reference migration: the Dashboard (`fragment_dashboard` + `DashboardFragment`). + +--- + ## Tech-debt watch list (controller) — opportunistic targets Evident debt noticed while building the pilot. Chip away at these **only when diff --git a/controller/app/src/main/java/org/iiab/controller/DashboardFragment.java b/controller/app/src/main/java/org/iiab/controller/DashboardFragment.java index 17fa200..cecabb5 100644 --- a/controller/app/src/main/java/org/iiab/controller/DashboardFragment.java +++ b/controller/app/src/main/java/org/iiab/controller/DashboardFragment.java @@ -283,8 +283,8 @@ private void updateSystemStats() { int baseColorSwap = ContextCompat.getColor(requireContext(), R.color.dash_bar_swap); int baseColorStorage = ContextCompat.getColor(requireContext(), R.color.dash_bar_storage); - int warnColor = Color.parseColor("#FF9800"); // Orange - int dangerColor = Color.parseColor("#F44336"); // Red + int warnColor = ContextCompat.getColor(requireContext(), R.color.status_warning); // Orange + int dangerColor = ContextCompat.getColor(requireContext(), R.color.status_danger); // Red // RAM Gauge (Warning at 90%, Danger at 95%) int finalColorRam = memProgress >= 95 ? dangerColor : (memProgress >= 90 ? warnColor : baseColorRam); @@ -344,9 +344,9 @@ private void updateSystemStats() { if (batLevel <= 33) { colorBattery = warnColor; // Orange (1-33%) } else if (batLevel <= 66) { - colorBattery = Color.parseColor("#4CAF50"); // Green (34-66%) + colorBattery = ContextCompat.getColor(requireContext(), R.color.status_success); // Green (34-66%) } else { - colorBattery = Color.parseColor("#2196F3"); // Blue (67-100%) + colorBattery = ContextCompat.getColor(requireContext(), R.color.status_info); // Blue (67-100%) } // Update the gauge with the newly assigned 4-parameter method diff --git a/controller/app/src/main/res/layout/fragment_dashboard.xml b/controller/app/src/main/res/layout/fragment_dashboard.xml index ba78564..efe991a 100644 --- a/controller/app/src/main/res/layout/fragment_dashboard.xml +++ b/controller/app/src/main/res/layout/fragment_dashboard.xml @@ -117,14 +117,14 @@ android:layout_width="16dp" android:layout_height="16dp" android:src="@drawable/outline_android_wifi_3_bar_24" - app:tint="#888888" + app:tint="@color/text_secondary" android:layout_centerVertical="true" /> + app:tint="@color/text_secondary" /> @@ -339,7 +339,7 @@ android:layout_height="wrap_content" android:text="@string/dash_offline" android:background="@drawable/rounded_button" - android:backgroundTint="#555555" + android:backgroundTint="@color/surface_section" android:textColor="@color/dash_text_primary" android:fontFamily="@font/orbitron" android:textSize="11sp" diff --git a/controller/app/src/main/res/values-night/colors.xml b/controller/app/src/main/res/values-night/colors.xml index 82a7751..049b625 100644 --- a/controller/app/src/main/res/values-night/colors.xml +++ b/controller/app/src/main/res/values-night/colors.xml @@ -18,4 +18,27 @@ #444444 #333333 + + + #121212 + #1E1E1E + #2A2A2A + #FAFAFA + #BDBDBD + #7A7A7A + #FFFFFF + #4CAF50 + #2E5A30 + #66BB6A + #FFB300 + #EF5350 + #42A5F5 + #333333 + #4DB6AC + #FFB300 + #7986CB + #26C6DA + #FF9800 + #66BB6A + #333333 diff --git a/controller/app/src/main/res/values/colors.xml b/controller/app/src/main/res/values/colors.xml index e7f118c..127023e 100644 --- a/controller/app/src/main/res/values/colors.xml +++ b/controller/app/src/main/res/values/colors.xml @@ -44,6 +44,41 @@ #5F6368 + + + + + + #F4F5F7 + #FFFFFF + #E8EAED + + #202124 + #5F6368 + #9AA0A6 + #FFFFFF + + #2E7D32 + #A5D6A7 + + #2E7D32 + #E65100 + #C62828 + #1565C0 + + #E0E0E0 + + #00897B + #FF8F00 + #5C6BC0 + #00838F + #EF6C00 + #2E7D32 + #E0E0E0 + + #000000 + #FFFFFF From f27492628e0d7a2d03dace68f145774d7f13b3ec Mon Sep 17 00:00:00 2001 From: Luis Guzman Date: Thu, 18 Jun 2026 21:49:24 +0000 Subject: [PATCH 2/7] fix(controller): make Dashboard gauge text legible (theming) ResourceGaugeView hardcoded the gauge title (#CCCCCC) and subtitle (#AAAAAA) in a light grey that is unreadable on the light surface, and the track arc in #333333 (a heavy black arc on a light card). Route them through tokens: title/subtitle -> text_secondary, percent -> text_primary, track -> chart_track, default arc -> status_success. Removes the last hardcoded colours from the Dashboard's gauges; verify both modes. --- .../java/org/iiab/controller/ResourceGaugeView.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/controller/app/src/main/java/org/iiab/controller/ResourceGaugeView.java b/controller/app/src/main/java/org/iiab/controller/ResourceGaugeView.java index cc507cc..01c3044 100644 --- a/controller/app/src/main/java/org/iiab/controller/ResourceGaugeView.java +++ b/controller/app/src/main/java/org/iiab/controller/ResourceGaugeView.java @@ -11,7 +11,6 @@ import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Canvas; -import android.graphics.Color; import android.graphics.Paint; import android.graphics.RectF; import android.graphics.Typeface; @@ -30,7 +29,7 @@ public class ResourceGaugeView extends View { private String centerText = "0%"; private String bottomText = "-- / --"; - private int currentColor = Color.parseColor("#4CAF50"); + private int currentColor; public ResourceGaugeView(Context context, AttributeSet attrs) { super(context, attrs); @@ -40,10 +39,11 @@ public ResourceGaugeView(Context context, AttributeSet attrs) { private void init(Context context) { setLayerType(View.LAYER_TYPE_SOFTWARE, null); rectF = new RectF(); + currentColor = androidx.core.content.ContextCompat.getColor(context, R.color.status_success); bgArcPaint = new Paint(Paint.ANTI_ALIAS_FLAG); bgArcPaint.setStyle(Paint.Style.STROKE); - bgArcPaint.setColor(Color.parseColor("#333333")); + bgArcPaint.setColor(androidx.core.content.ContextCompat.getColor(context, R.color.chart_track)); bgArcPaint.setStrokeCap(Paint.Cap.ROUND); bgArcPaint.setPathEffect(null); @@ -57,16 +57,16 @@ private void init(Context context) { percentPaint = new Paint(Paint.ANTI_ALIAS_FLAG); percentPaint.setTextAlign(Paint.Align.CENTER); - percentPaint.setColor(androidx.core.content.ContextCompat.getColor(context, R.color.dash_text_inverted)); + percentPaint.setColor(androidx.core.content.ContextCompat.getColor(context, R.color.text_primary)); percentPaint.setFakeBoldText(true); valuePaint = new Paint(Paint.ANTI_ALIAS_FLAG); valuePaint.setTextAlign(Paint.Align.CENTER); - valuePaint.setColor(Color.parseColor("#AAAAAA")); + valuePaint.setColor(androidx.core.content.ContextCompat.getColor(context, R.color.text_secondary)); titlePaint = new Paint(Paint.ANTI_ALIAS_FLAG); titlePaint.setTextAlign(Paint.Align.CENTER); - titlePaint.setColor(Color.parseColor("#CCCCCC")); + titlePaint.setColor(androidx.core.content.ContextCompat.getColor(context, R.color.text_secondary)); titlePaint.setFakeBoldText(true); // LOAD AND APPLY ORBITRON From ec68f2de561603623d2c83259fb6ab10640fc782 Mon Sep 17 00:00:00 2001 From: Luis Guzman Date: Thu, 18 Jun 2026 22:01:03 +0000 Subject: [PATCH 3/7] fix(controller): darken gauge track in light theme for visibility The light-theme track (#E0E0E0) blended into the card background, so the gauge 'path' was barely distinguishable from the surface. Bump to #C4C7CC so the track reads clearly as the channel the colored arc follows. Dark theme track (#333333) is unchanged. --- controller/app/src/main/res/values/colors.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller/app/src/main/res/values/colors.xml b/controller/app/src/main/res/values/colors.xml index 127023e..c5a80ce 100644 --- a/controller/app/src/main/res/values/colors.xml +++ b/controller/app/src/main/res/values/colors.xml @@ -74,7 +74,7 @@ #00838F #EF6C00 #2E7D32 - #E0E0E0 + #C4C7CC #000000 From d11ed4bfd798b500d7de02f01c7a96c97b493d33 Mon Sep 17 00:00:00 2001 From: Luis Guzman Date: Thu, 18 Jun 2026 22:19:48 +0000 Subject: [PATCH 4/7] feat(controller): migrate Usage screen to semantic colour tokens Refactor-by-feature theming slice for the USAGE tab (UsageFragment + fragment_usage.xml), following the Dashboard pilot pattern. Adds the round-2 token block (twins in values + values-night) for needs not covered by the foundation: - surface_active_success/info/danger: tinted 'active' card states - status_pending: amber in-progress LED - accent_secondary: purple secondary actions - btn_neutral: neutral button fill that stays dark in BOTH themes so white button text stays legible (light surfaces would hide it) - terminal_background/terminal_text: log console (green-on-black, fixed) - overlay_scrim: modal dim Usage migration: - fragment_usage.xml: 14 hardcoded hex -> tokens (button text -> text_on_accent, #673AB7 apps button -> accent_secondary, warning text/progress -> status_warning, save button #555555 -> btn_neutral, log console #000000/#00FF00 -> terminal_*, #00000000 -> @android:color/transparent). - UsageFragment.java: Color.WHITE button text -> text_on_accent; swipe-hint #00E5FF -> status_info. Color.TRANSPARENT kept (theme-independent). No layout/behaviour change intended; pure colour-source migration. Verify in light AND dark on device. --- .../org/iiab/controller/UsageFragment.java | 8 +++--- .../src/main/res/layout/fragment_usage.xml | 28 +++++++++---------- .../app/src/main/res/values-night/colors.xml | 10 +++++++ controller/app/src/main/res/values/colors.xml | 12 ++++++++ 4 files changed, 40 insertions(+), 18 deletions(-) diff --git a/controller/app/src/main/java/org/iiab/controller/UsageFragment.java b/controller/app/src/main/java/org/iiab/controller/UsageFragment.java index 10d02b3..d7803de 100644 --- a/controller/app/src/main/java/org/iiab/controller/UsageFragment.java +++ b/controller/app/src/main/java/org/iiab/controller/UsageFragment.java @@ -267,13 +267,13 @@ public void updateUIColorsAndVisibility() { button_browse_content.setEnabled(true); button_browse_content.setBackgroundTintList(ContextCompat.getColorStateList(requireContext(), R.color.btn_explore_disabled)); button_browse_content.setAlpha(1.0f); - button_browse_content.setTextColor(Color.WHITE); + button_browse_content.setTextColor(ContextCompat.getColor(requireContext(), R.color.text_on_accent)); } else if (mainActivity.isNegotiating) { button_browse_content.setEnabled(true); - button_browse_content.setTextColor(Color.WHITE); + button_browse_content.setTextColor(ContextCompat.getColor(requireContext(), R.color.text_on_accent)); } else { button_browse_content.setEnabled(true); - button_browse_content.setTextColor(Color.WHITE); + button_browse_content.setTextColor(ContextCompat.getColor(requireContext(), R.color.text_on_accent)); button_browse_content.setBackgroundTintList(ContextCompat.getColorStateList(requireContext(), R.color.btn_explore_ready)); button_browse_content.setAlpha(1.0f); } @@ -467,7 +467,7 @@ public void highlightServerButton() { // We use ofArgb for a perfect color transition android.animation.ValueAnimator colorAnim = android.animation.ValueAnimator.ofArgb( Color.TRANSPARENT, - Color.parseColor("#00E5FF") // Color Cyan + ContextCompat.getColor(requireContext(), R.color.status_info) // Color Cyan ); colorAnim.setDuration(350); colorAnim.setRepeatCount(5); diff --git a/controller/app/src/main/res/layout/fragment_usage.xml b/controller/app/src/main/res/layout/fragment_usage.xml index c5ea9aa..9c7fcab 100644 --- a/controller/app/src/main/res/layout/fragment_usage.xml +++ b/controller/app/src/main/res/layout/fragment_usage.xml @@ -109,7 +109,7 @@ android:text="@string/control_enable" android:textSize="20sp" android:textStyle="bold" - android:textColor="#FFFFFF" + android:textColor="@color/text_on_accent" android:background="@drawable/rounded_button" android:backgroundTint="@color/btn_vpn_off" android:textAllCaps="false" @@ -138,7 +138,7 @@ android:text="@string/browse_content" android:textSize="21sp" android:textStyle="bold" - android:textColor="#FFFFFF" + android:textColor="@color/text_on_accent" android:background="@drawable/rounded_button" android:backgroundTint="@color/btn_explore_disabled" android:textAllCaps="false" @@ -179,8 +179,8 @@ android:text="@string/apps" android:layout_marginBottom="12dp" android:background="@drawable/rounded_button" - android:backgroundTint="#673AB7" - android:textColor="#FFFFFF" + android:backgroundTint="@color/accent_secondary" + android:textColor="@color/text_on_accent" android:textAllCaps="false" /> @@ -238,8 +238,8 @@ android:text="@string/save" android:layout_marginTop="8dp" android:background="@drawable/rounded_button" - android:backgroundTint="#555555" - android:textColor="#FFFFFF" + android:backgroundTint="@color/btn_neutral" + android:textColor="@color/text_on_accent" android:textAllCaps="false" /> @@ -407,13 +407,13 @@ android:text="Launch Server" android:textSize="20sp" android:textStyle="bold" - android:textColor="#FFFFFF" + android:textColor="@color/text_on_accent" android:background="@drawable/rounded_button" android:backgroundTint="@color/btn_success" android:textAllCaps="false" app:progressButtonHeight="6dp" app:progressButtonDuration="@integer/server_cool_off_duration_ms" - app:progressButtonColor="#FF9800" /> + app:progressButtonColor="@color/status_warning" /> @@ -515,7 +515,7 @@ android:textSize="12sp" android:background="@drawable/rounded_button" android:backgroundTint="@color/btn_success" - android:textColor="#FFFFFF" + android:textColor="@color/text_on_accent" android:textAllCaps="false" android:layout_marginStart="4dp" /> diff --git a/controller/app/src/main/res/values-night/colors.xml b/controller/app/src/main/res/values-night/colors.xml index 049b625..c6725cf 100644 --- a/controller/app/src/main/res/values-night/colors.xml +++ b/controller/app/src/main/res/values-night/colors.xml @@ -41,4 +41,14 @@ #FF9800 #66BB6A #333333 + + #173317 + #17263A + #5C1A1A + #FFCA28 + #B39DDB + #424242 + #0D0D0D + #33FF33 + #CC000000 diff --git a/controller/app/src/main/res/values/colors.xml b/controller/app/src/main/res/values/colors.xml index c5a80ce..e439cae 100644 --- a/controller/app/src/main/res/values/colors.xml +++ b/controller/app/src/main/res/values/colors.xml @@ -75,6 +75,18 @@ #EF6C00 #2E7D32 #C4C7CC + + + #E6F4EA + #E3F0FB + #FDECEA + #F9A825 + #6A3FB5 + #616161 + #0D0D0D + #33FF33 + #CC000000 + #000000 From 3f22c1ec04e6ab902351f9ab7bae05a038ebfea4 Mon Sep 17 00:00:00 2001 From: Luis Guzman Date: Fri, 19 Jun 2026 01:42:39 +0000 Subject: [PATCH 5/7] style(controller): more defined LED panel in light theme The status LED panel barely separated from the page in light mode (#E8EAED panel on #F4F5F7 page). Darken dash_module_bg #E8EAED -> #DEE1E6 (light only; dark twin unchanged) so the panel reads as a card. Same token as the Install LED panel, so both panels stay identical and equally defined. --- controller/app/src/main/res/values/colors.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller/app/src/main/res/values/colors.xml b/controller/app/src/main/res/values/colors.xml index e439cae..a09da18 100644 --- a/controller/app/src/main/res/values/colors.xml +++ b/controller/app/src/main/res/values/colors.xml @@ -36,7 +36,7 @@ #5F6368 #202124 #5F6368 - #E8EAED + #DEE1E6 #202124 #E0E0E0 #E65100 From 328caddf98cade7dd4dd72e28b58a7545ce38b2c Mon Sep 17 00:00:00 2001 From: Luis Guzman Date: Fri, 19 Jun 2026 03:08:08 +0000 Subject: [PATCH 6/7] chore(controller): harmonize round-2 colour token block across theming PRs Make the round-2 token block byte-identical in the Usage/Install/Share theming branches (12 tokens incl. toggle_selected, plus dash_module_bg #DEE1E6) so colors.xml merges cleanly regardless of merge order. Tokens not referenced by this screen are harmless definitions (other screens use them). --- controller/app/src/main/res/values-night/colors.xml | 3 +++ controller/app/src/main/res/values/colors.xml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/controller/app/src/main/res/values-night/colors.xml b/controller/app/src/main/res/values-night/colors.xml index c6725cf..7b2f5bb 100644 --- a/controller/app/src/main/res/values-night/colors.xml +++ b/controller/app/src/main/res/values-night/colors.xml @@ -45,10 +45,13 @@ #173317 #17263A #5C1A1A + #3A2A0E #FFCA28 #B39DDB #424242 + #212121 #0D0D0D #33FF33 #CC000000 + #3A3A3A diff --git a/controller/app/src/main/res/values/colors.xml b/controller/app/src/main/res/values/colors.xml index a09da18..2ebf94a 100644 --- a/controller/app/src/main/res/values/colors.xml +++ b/controller/app/src/main/res/values/colors.xml @@ -80,12 +80,15 @@ #E6F4EA #E3F0FB #FDECEA + #FFF3E0 #F9A825 #6A3FB5 #616161 + #212121 #0D0D0D #33FF33 #CC000000 + #FFFFFF From f94ec008557a57108d067833e7ecbbded9a51a82 Mon Sep 17 00:00:00 2001 From: Luis Guzman Date: Fri, 19 Jun 2026 03:09:30 +0000 Subject: [PATCH 7/7] chore(controller): normalize colors.xml whitespace to match Install/Share Make values/ and values-night/ colors.xml byte-identical across the three theming branches (only stray blank lines differed) so they merge cleanly in any order with zero conflicts. --- controller/app/src/main/res/values/colors.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/controller/app/src/main/res/values/colors.xml b/controller/app/src/main/res/values/colors.xml index 2ebf94a..00a986c 100644 --- a/controller/app/src/main/res/values/colors.xml +++ b/controller/app/src/main/res/values/colors.xml @@ -75,7 +75,6 @@ #EF6C00 #2E7D32 #C4C7CC - #E6F4EA #E3F0FB